feat: add tests for normalizeName and RecipesService methods, including unit conversion and alias normalization
This commit is contained in:
@@ -29,21 +29,30 @@ function RecipePlaceholder({ name }: { name: string }) {
|
||||
|
||||
export default function RecipeGrid({ recipes }: { recipes: Recipe[] }) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [sort, setSort] = useState<'name' | 'newest' | 'oldest' | 'ingredients'>('newest');
|
||||
const [onlyWithImage, setOnlyWithImage] = useState(false);
|
||||
|
||||
const filtered = recipes.filter((r) =>
|
||||
r.name.toLowerCase().includes(search.toLowerCase()),
|
||||
);
|
||||
const filtered = recipes
|
||||
.filter((r) => r.name.toLowerCase().includes(search.toLowerCase()))
|
||||
.filter((r) => !onlyWithImage || !!r.imageUrl)
|
||||
.sort((a, b) => {
|
||||
if (sort === 'name') return a.name.localeCompare(b.name, 'sv');
|
||||
if (sort === 'newest') return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
||||
if (sort === 'oldest') return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||
if (sort === 'ingredients') return (b.ingredients?.length ?? 0) - (a.ingredients?.length ?? 0);
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: '1.25rem' }}>
|
||||
<div style={{ display: 'flex', gap: '0.75rem', marginBottom: '1rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Sök efter recept..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
flex: '1 1 200px',
|
||||
padding: '0.6rem 1rem',
|
||||
fontSize: '1rem',
|
||||
border: '1px solid #ced4da',
|
||||
@@ -52,11 +61,36 @@ export default function RecipeGrid({ recipes }: { recipes: Recipe[] }) {
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
/>
|
||||
<select
|
||||
value={sort}
|
||||
onChange={(e) => setSort(e.target.value as typeof sort)}
|
||||
style={{
|
||||
padding: '0.55rem 0.75rem',
|
||||
fontSize: '0.9rem',
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '8px',
|
||||
background: '#fff',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<option value="newest">Senast tillagda</option>
|
||||
<option value="oldest">Äldst först</option>
|
||||
<option value="name">Namn (A–Ö)</option>
|
||||
<option value="ingredients">Flest ingredienser</option>
|
||||
</select>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', fontSize: '0.9rem', cursor: 'pointer', userSelect: 'none' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={onlyWithImage}
|
||||
onChange={(e) => setOnlyWithImage(e.target.checked)}
|
||||
/>
|
||||
Endast med bild
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{filtered.length === 0 && (
|
||||
<p style={{ color: '#868e96', textAlign: 'center', marginTop: '2rem' }}>
|
||||
{search ? 'Inga recept matchar sökningen.' : 'Inga recept tillagda ännu.'}
|
||||
{search || onlyWithImage ? 'Inga recept matchar filtren.' : 'Inga recept tillagda ännu.'}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user