diff --git a/backend/src/recipes/recipes.controller.ts b/backend/src/recipes/recipes.controller.ts
index b951e56c..26498cb5 100644
--- a/backend/src/recipes/recipes.controller.ts
+++ b/backend/src/recipes/recipes.controller.ts
@@ -1,4 +1,4 @@
-import { Body, Controller, Get, Param, ParseIntPipe, Post } from '@nestjs/common';
+import { Body, Controller, Get, Param, ParseIntPipe, Post, Patch } from '@nestjs/common';
import { RecipesService } from './recipes.service';
import { CreateRecipeDto } from './dto/create-recipe.dto';
@@ -25,4 +25,12 @@ export class RecipesController {
async create(@Body() createRecipeDto: CreateRecipeDto) {
return this.recipesService.create(createRecipeDto);
}
+
+ @Patch(':id')
+ async update(
+ @Param('id', ParseIntPipe) id: number,
+ @Body() createRecipeDto: CreateRecipeDto,
+ ) {
+ return this.recipesService.update(id, createRecipeDto);
+ }
}
\ No newline at end of file
diff --git a/backend/src/recipes/recipes.service.ts b/backend/src/recipes/recipes.service.ts
index fd9efb48..299ce0a7 100644
--- a/backend/src/recipes/recipes.service.ts
+++ b/backend/src/recipes/recipes.service.ts
@@ -302,6 +302,40 @@ export class RecipesService {
return recipe;
}
+ async update(id: number, updateRecipeDto: CreateRecipeDto) {
+ // Först, ta bort gamla ingredienser
+ await this.prisma.recipeIngredient.deleteMany({
+ where: { recipeId: id },
+ });
+
+ // Uppdatera receptet och lägg till nya ingredienser
+ const recipe = await this.prisma.recipe.update({
+ where: { id },
+ data: {
+ name: updateRecipeDto.name,
+ description: updateRecipeDto.description || null,
+ instructions: updateRecipeDto.instructions || null,
+ ingredients: {
+ create: updateRecipeDto.ingredients.map((ingredient) => ({
+ productId: ingredient.productId,
+ quantity: ingredient.quantity.toString(),
+ unit: ingredient.unit,
+ note: ingredient.note || null,
+ })),
+ },
+ },
+ include: {
+ ingredients: {
+ include: {
+ product: true,
+ },
+ },
+ },
+ });
+
+ return recipe;
+ }
+
async create(createRecipeDto: CreateRecipeDto) {
const recipe = await this.prisma.recipe.create({
data: {
diff --git a/frontend/app/recipes/RecipePreview.tsx b/frontend/app/recipes/RecipePreview.tsx
index 65529140..46ce4579 100644
--- a/frontend/app/recipes/RecipePreview.tsx
+++ b/frontend/app/recipes/RecipePreview.tsx
@@ -1,6 +1,7 @@
'use client';
import { useState, useTransition } from 'react';
+import Link from 'next/link';
import type {
Recipe,
RecipeInventoryPreview,
@@ -115,7 +116,7 @@ export default function RecipePreview({ recipes }: Props) {
-
+
{isPending ? 'Hämtar preview...' : 'Visa preview'}
+ {selectedRecipeId && (
+
+ Redigera recept
+
+ )}
{error ?
{error}
: null}
@@ -154,6 +172,31 @@ export default function RecipePreview({ recipes }: Props) {
: 'Kan inte lagas exakt ännu'}
+
+ {preview.summary.unitMismatchCount > 0 && (
+
+
⚠️ Enhetskonflikt! {preview.summary.unitMismatchCount} ingrediens
+ {preview.summary.unitMismatchCount !== 1 ? 'er har' : ' har'} olika enheter än vad som finns i hemmavaror.
+
+
+ T.ex. receptet säger "0.5 st" men du har lagrat "1.3 kg". Du kan antingen:
+
+ Redigera receptet för att matcha dina enheter
+ Lagra ingrediensen med samma enhet som receptet använder
+
+
+
+ )}
@@ -215,6 +258,22 @@ export default function RecipePreview({ recipes }: Props) {
Saknas: {ingredient.missingQuantity} {ingredient.requiredUnit}
) : null}
+
+ {ingredient.status === 'unit_mismatch' ? (
+
+ Enhetsproblem: Receptet kräver {ingredient.requiredUnit} men hemmavaror lagras i andra enheter.
+ Uppdatera receptet eller lagra ingrediensen med rätt enhet.
+
+ ) : null}
{ingredient.matchingInventoryItems.length > 0 ? (
diff --git a/frontend/app/recipes/[id]/edit/page.tsx b/frontend/app/recipes/[id]/edit/page.tsx
new file mode 100644
index 00000000..088a4f4a
--- /dev/null
+++ b/frontend/app/recipes/[id]/edit/page.tsx
@@ -0,0 +1,251 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { useRouter, useParams } from 'next/navigation';
+import { fetchJson } from '../../../../lib/api';
+import type { Product, Recipe } from '../../../../features/inventory/types';
+
+export default function EditRecipePage() {
+ const router = useRouter();
+ const params = useParams();
+ const recipeId = Array.isArray(params.id) ? params.id[0] : params.id;
+
+ const [recipe, setRecipe] = useState({
+ name: '',
+ description: '',
+ instructions: '',
+ ingredients: [{ productId: 0, quantity: '', unit: '', note: '', location: '' }],
+ });
+ const [products, setProducts] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isSaving, setIsSaving] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const loadData = async () => {
+ try {
+ // Ladda produkter
+ const productsData = await fetchJson('/api/products');
+ setProducts(productsData);
+
+ // Ladda receptet
+ const recipeData = await fetchJson(`/api/recipes/${recipeId}`);
+ setRecipe({
+ name: recipeData.name,
+ description: recipeData.description || '',
+ instructions: recipeData.instructions || '',
+ ingredients: recipeData.ingredients.map((ing: any) => ({
+ productId: ing.productId,
+ quantity: ing.quantity.toString(),
+ unit: ing.unit,
+ note: ing.note || '',
+ location: ing.location || '',
+ })),
+ });
+ } catch (err) {
+ setError((err as Error).message);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ loadData();
+ }, [recipeId]);
+
+ const handleIngredientChange = (index: number, field: string, value: string | number) => {
+ const newIngredients = [...recipe.ingredients];
+ newIngredients[index] = { ...newIngredients[index], [field]: value };
+ setRecipe({ ...recipe, ingredients: newIngredients });
+ };
+
+ const addIngredient = () => {
+ setRecipe({
+ ...recipe,
+ ingredients: [...recipe.ingredients, { productId: 0, quantity: '', unit: '', note: '', location: '' }],
+ });
+ };
+
+ const removeIngredient = (index: number) => {
+ const newIngredients = [...recipe.ingredients];
+ newIngredients.splice(index, 1);
+ setRecipe({ ...recipe, ingredients: newIngredients });
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setIsSaving(true);
+ setError(null);
+
+ // Konvertera quantity till number för varje ingrediens
+ const recipeToSend = {
+ ...recipe,
+ ingredients: recipe.ingredients.map((ing) => ({
+ ...ing,
+ quantity: Number(ing.quantity),
+ })),
+ };
+
+ try {
+ const response = await fetch(`/api/recipes/${recipeId}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(recipeToSend),
+ });
+
+ if (!response.ok) {
+ throw new Error('Kunde inte uppdatera receptet');
+ }
+
+ router.push('/recipes');
+ } catch (err) {
+ setError((err as Error).message);
+ } finally {
+ setIsSaving(false);
+ }
+ };
+
+ const UNIT_OPTIONS = [
+ { value: '', label: 'Välj enhet' },
+ { value: 'g', label: 'g (gram)' },
+ { value: 'kg', label: 'kg (kilogram)' },
+ { value: 'hg', label: 'hg (hektogram)' },
+ { value: 'ml', label: 'ml (milliliter)' },
+ { value: 'dl', label: 'dl (deciliter)' },
+ { value: 'l', label: 'l (liter)' },
+ { value: 'st', label: 'st (styck)' },
+ { value: 'tsk', label: 'tsk (tesked)' },
+ { value: 'msk', label: 'msk (matsked)' },
+ ];
+
+ const LOCATION_OPTIONS = [
+ { value: '', label: 'Välj plats' },
+ { value: 'Kyl', label: 'Kyl' },
+ { value: 'Frys', label: 'Frys' },
+ { value: 'Skafferi', label: 'Skafferi' },
+ { value: 'Annat', label: 'Annat' },
+ ];
+
+ if (isLoading) {
+ return (
+
+ Laddar recept...
+
+ );
+ }
+
+ return (
+
+ Redigera recept
+
+ {error && {error}
}
+
+
+
+ );
+}