148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import Link from 'next/link';
|
|
import type { Recipe } from '../../features/inventory/types';
|
|
|
|
function RecipePlaceholder({ name }: { name: string }) {
|
|
const initial = name.trim().charAt(0).toUpperCase() || '?';
|
|
return (
|
|
<div
|
|
style={{
|
|
width: '100%',
|
|
height: '160px',
|
|
background: '#e9ecef',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
fontSize: '2.5rem',
|
|
fontWeight: 700,
|
|
color: '#868e96',
|
|
borderRadius: '8px 8px 0 0',
|
|
userSelect: 'none',
|
|
}}
|
|
>
|
|
{initial}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function RecipeGrid({ recipes }: { recipes: Recipe[] }) {
|
|
const [search, setSearch] = useState('');
|
|
|
|
const filtered = recipes.filter((r) =>
|
|
r.name.toLowerCase().includes(search.toLowerCase()),
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
<div style={{ marginBottom: '1.25rem' }}>
|
|
<input
|
|
type="text"
|
|
placeholder="Sök efter recept..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
style={{
|
|
width: '100%',
|
|
padding: '0.6rem 1rem',
|
|
fontSize: '1rem',
|
|
border: '1px solid #ced4da',
|
|
borderRadius: '24px',
|
|
outline: 'none',
|
|
boxSizing: 'border-box',
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{filtered.length === 0 && (
|
|
<p style={{ color: '#868e96', textAlign: 'center', marginTop: '2rem' }}>
|
|
{search ? 'Inga recept matchar sökningen.' : 'Inga recept tillagda ännu.'}
|
|
</p>
|
|
)}
|
|
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
|
|
gap: '1rem',
|
|
}}
|
|
>
|
|
{filtered.map((recipe) => (
|
|
<Link
|
|
key={recipe.id}
|
|
href={`/recipes/${recipe.id}`}
|
|
style={{ textDecoration: 'none', color: 'inherit' }}
|
|
>
|
|
<div
|
|
style={{
|
|
border: '1px solid #dee2e6',
|
|
borderRadius: '8px',
|
|
overflow: 'hidden',
|
|
transition: 'box-shadow 0.15s',
|
|
background: '#fff',
|
|
cursor: 'pointer',
|
|
}}
|
|
onMouseEnter={(e) =>
|
|
((e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.12)')
|
|
}
|
|
onMouseLeave={(e) =>
|
|
((e.currentTarget as HTMLDivElement).style.boxShadow = 'none')
|
|
}
|
|
>
|
|
{recipe.imageUrl ? (
|
|
<img
|
|
src={recipe.imageUrl}
|
|
alt={recipe.name}
|
|
style={{
|
|
width: '100%',
|
|
height: '160px',
|
|
objectFit: 'cover',
|
|
display: 'block',
|
|
}}
|
|
/>
|
|
) : (
|
|
<RecipePlaceholder name={recipe.name} />
|
|
)}
|
|
<div style={{ padding: '0.75rem 1rem 0.85rem' }}>
|
|
<h3
|
|
style={{
|
|
margin: 0,
|
|
fontSize: '1rem',
|
|
fontWeight: 600,
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
}}
|
|
>
|
|
{recipe.name}
|
|
</h3>
|
|
{recipe.description && (
|
|
<p
|
|
style={{
|
|
margin: '0.25rem 0 0.5rem',
|
|
fontSize: '0.85rem',
|
|
color: '#868e96',
|
|
overflow: 'hidden',
|
|
display: '-webkit-box',
|
|
WebkitLineClamp: 2,
|
|
WebkitBoxOrient: 'vertical',
|
|
} as React.CSSProperties}
|
|
>
|
|
{recipe.description}
|
|
</p>
|
|
)}
|
|
<div style={{ display: 'flex', gap: '0.75rem', marginTop: recipe.description ? 0 : '0.4rem', fontSize: '0.78rem', color: '#adb5bd' }}>
|
|
{recipe.ingredients?.length > 0 && (
|
|
<span>{recipe.ingredients.length} ingredienser</span>
|
|
)}
|
|
<span>{new Date(recipe.createdAt).toLocaleDateString('sv-SE')}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|