Add recipe deletion functionality and enhance inventory consumption details
This commit is contained in:
@@ -104,7 +104,7 @@ export default function InventoryConsumptionHistory({ id }: Props) {
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<strong>Använt:</strong> {entry.amountUsed}
|
||||
<strong>Använt:</strong> {entry.amountUsed}{entry.inventoryItem?.unit ? ` ${entry.inventoryItem.unit}` : ''}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Tid:</strong> {formatDateTime(entry.createdAt)}
|
||||
|
||||
@@ -58,6 +58,32 @@ export default function RecipePreview({ recipes }: Props) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const selectAndLoad = (id: string) => {
|
||||
setSelectedRecipeId(id);
|
||||
setError(null);
|
||||
setPreview(null);
|
||||
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/recipe-preview-proxy?id=${id}`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errorMessage = await parseErrorResponse(res);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: RecipeInventoryPreview = await res.json();
|
||||
setPreview(data);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Ett okänt fel inträffade.';
|
||||
setError(message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const loadPreview = () => {
|
||||
setError(null);
|
||||
setPreview(null);
|
||||
@@ -88,46 +114,49 @@ export default function RecipePreview({ recipes }: Props) {
|
||||
});
|
||||
};
|
||||
|
||||
const listedRecipes = recipes.slice(0, 10);
|
||||
|
||||
return (
|
||||
<section style={{ display: 'grid', gap: '1rem' }}>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '8px',
|
||||
padding: '1rem',
|
||||
display: 'grid',
|
||||
gap: '0.75rem',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: 0 }}>Recept mot hemmavaror</h2>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: '1rem', alignItems: 'start' }}>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '8px',
|
||||
padding: '1rem',
|
||||
display: 'grid',
|
||||
gap: '0.75rem',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: 0 }}>Recept mot hemmavaror</h2>
|
||||
|
||||
<label>
|
||||
Recept
|
||||
<br />
|
||||
<select
|
||||
value={selectedRecipeId}
|
||||
onChange={(e) => setSelectedRecipeId(e.target.value)}
|
||||
style={{ width: '100%', padding: '0.5rem' }}
|
||||
>
|
||||
<option value="">Välj recept</option>
|
||||
{recipes.map((recipe) => (
|
||||
<option key={recipe.id} value={recipe.id}>
|
||||
{recipe.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Recept
|
||||
<br />
|
||||
<select
|
||||
value={selectedRecipeId}
|
||||
onChange={(e) => setSelectedRecipeId(e.target.value)}
|
||||
style={{ width: '100%', padding: '0.5rem' }}
|
||||
>
|
||||
<option value="">Välj recept</option>
|
||||
{recipes.map((recipe) => (
|
||||
<option key={recipe.id} value={recipe.id}>
|
||||
{recipe.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div style={{ display: 'flex', gap: '0.75rem' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={loadPreview}
|
||||
disabled={isPending}
|
||||
style={{ padding: '0.6rem 1rem' }}
|
||||
>
|
||||
{isPending ? 'Hämtar preview...' : 'Visa preview'}
|
||||
</button>
|
||||
{selectedRecipeId && (
|
||||
<div style={{ display: 'flex', gap: '0.75rem' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={loadPreview}
|
||||
disabled={isPending}
|
||||
style={{ padding: '0.6rem 1rem' }}
|
||||
>
|
||||
{isPending ? 'Hämtar preview...' : 'Visa preview'}
|
||||
</button>
|
||||
{selectedRecipeId && (
|
||||
<Link
|
||||
href={`/recipes/${selectedRecipeId}/edit`}
|
||||
style={{
|
||||
@@ -144,9 +173,48 @@ export default function RecipePreview({ recipes }: Props) {
|
||||
Redigera recept
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error ? <p style={{ color: 'crimson', margin: 0 }}>{error}</p> : null}
|
||||
</div>
|
||||
|
||||
{error ? <p style={{ color: 'crimson', margin: 0 }}>{error}</p> : null}
|
||||
{/* Receptlista till höger */}
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '8px',
|
||||
padding: '1rem',
|
||||
minWidth: '180px',
|
||||
maxWidth: '220px',
|
||||
}}
|
||||
>
|
||||
<h3 style={{ margin: '0 0 0.75rem 0', fontSize: '1rem' }}>Mina recept</h3>
|
||||
<ul style={{ margin: 0, padding: 0, listStyle: 'none', display: 'grid', gap: '0.4rem' }}>
|
||||
{listedRecipes.map((recipe) => (
|
||||
<li key={recipe.id}>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isPending}
|
||||
onClick={() => selectAndLoad(String(recipe.id))}
|
||||
style={{
|
||||
width: '100%',
|
||||
textAlign: 'left',
|
||||
padding: '0.4rem 0.6rem',
|
||||
background: String(recipe.id) === selectedRecipeId ? '#e8f0fe' : 'transparent',
|
||||
border: `1px solid ${String(recipe.id) === selectedRecipeId ? '#4285f4' : '#eee'}`,
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: String(recipe.id) === selectedRecipeId ? 600 : 400,
|
||||
color: String(recipe.id) === selectedRecipeId ? '#1a56db' : '#333',
|
||||
fontSize: '0.9rem',
|
||||
}}
|
||||
>
|
||||
{recipe.name}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{preview ? (
|
||||
|
||||
@@ -20,6 +20,7 @@ export default function EditRecipePage() {
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -239,19 +240,45 @@ export default function EditRecipePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||
<button type="button" onClick={addIngredient} style={{ padding: '0.5rem' }}>
|
||||
Lägg till ingrediens
|
||||
</button>
|
||||
<button type="submit" disabled={isSaving} style={{ padding: '0.5rem' }}>
|
||||
{isSaving ? 'Uppdaterar...' : 'Uppdatera recept'}
|
||||
</button>
|
||||
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap', justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||
<button type="button" onClick={addIngredient} style={{ padding: '0.5rem' }}>
|
||||
Lägg till ingrediens
|
||||
</button>
|
||||
<button type="submit" disabled={isSaving || isDeleting} style={{ padding: '0.5rem' }}>
|
||||
{isSaving ? 'Uppdaterar...' : 'Uppdatera recept'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push('/recipes')}
|
||||
style={{ padding: '0.5rem', background: '#f0f0f0', color: '#333', border: '1px solid #ccc', borderRadius: '4px', cursor: 'pointer' }}
|
||||
>
|
||||
Avbryt
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push('/recipes')}
|
||||
style={{ padding: '0.5rem', background: '#f0f0f0', color: '#333', border: '1px solid #ccc', borderRadius: '4px', cursor: 'pointer' }}
|
||||
disabled={isSaving || isDeleting}
|
||||
onClick={async () => {
|
||||
if (!confirm('Är du säker på att du vill radera detta recept? Detta kan inte ångras.')) return;
|
||||
setIsDeleting(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch(`/api/recipes/${recipeId}`, { method: 'DELETE' });
|
||||
if (!res.ok) {
|
||||
const errorMessage = await parseErrorResponse(res);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
router.push('/recipes');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Kunde inte radera receptet.');
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
}}
|
||||
style={{ padding: '0.5rem 1rem', background: '#c0392b', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
|
||||
>
|
||||
Avbryt
|
||||
{isDeleting ? 'Raderar...' : 'Radera recept'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user