From fab8fa5ba9e6dcc138c26b5ab1ca9e3bdfdf55d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 00:52:10 +0000 Subject: [PATCH] fix(inkop+recept): delvis-kop, radera-vara, bort med plattformen-taggen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Inkopslista /complete: kopte (markerade) varor laggs i lagret OCH tas bort ur listan; OMARKERADE varor blir kvar (handla i flera butiker). Listan arkiveras forst nar den ar tom. Tidigare markerades HELA listan completed sa allt forsvann. - Ny radera-knapp (x) per vara med bekraftelse - ta bort nagot man angrat utan att kopa det (DELETE-endpointen fanns redan). - Bort med '✓ plattformen'-taggen pa recept + bladdra (satt pa nastan alla editorial-recept och sag efterblivet ut). Namn visas fortfarande bara for anvandarskapade recept (creatorDisplayName). Typecheck: api + mobil gron. --- apps/api/src/routes/shopping.ts | 27 ++++++++++++++--- apps/mobile/src/app/recipe/[id].tsx | 1 - apps/mobile/src/app/recipes.tsx | 6 +--- apps/mobile/src/app/shopping.tsx | 47 ++++++++++++++++++++++------- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/apps/api/src/routes/shopping.ts b/apps/api/src/routes/shopping.ts index 2c5a20b..f909576 100644 --- a/apps/api/src/routes/shopping.ts +++ b/apps/api/src/routes/shopping.ts @@ -258,10 +258,29 @@ export async function shoppingRoutes(app: FastifyInstance) { } } + // Ta bort de köpta (markerade) varorna – de ligger nu i lagret. OMARKERADE + // varor blir kvar i listan (man kan handla i flera butiker och radera själv). await app.db - .update(schema.shoppingLists) - .set({ status: "completed", updatedAt: new Date() }) - .where(eq(schema.shoppingLists.id, id)); + .delete(schema.shoppingListItems) + .where( + and( + eq(schema.shoppingListItems.shoppingListId, id), + eq(schema.shoppingListItems.checked, true), + ), + ); + + // Markera hela listan som klar först när den är tömd; annars aktiv för fortsatt handling. + const remainingItems = await app.db + .select({ id: schema.shoppingListItems.id }) + .from(schema.shoppingListItems) + .where(eq(schema.shoppingListItems.shoppingListId, id)); + const remaining = remainingItems.length; + if (remaining === 0) { + await app.db + .update(schema.shoppingLists) + .set({ status: "completed", updatedAt: new Date() }) + .where(eq(schema.shoppingLists.id, id)); + } await emitEvent(app.db, { type: "SHOPPING_COMPLETED", @@ -270,7 +289,7 @@ export async function shoppingRoutes(app: FastifyInstance) { householdId: list.householdId, correlationId: req.correlationId, }); - return { ok: true, itemsAddedToInventory: added }; + return { ok: true, itemsAddedToInventory: added, remaining }; }); } diff --git a/apps/mobile/src/app/recipe/[id].tsx b/apps/mobile/src/app/recipe/[id].tsx index cbcacaa..654be7b 100644 --- a/apps/mobile/src/app/recipe/[id].tsx +++ b/apps/mobile/src/app/recipe/[id].tsx @@ -203,7 +203,6 @@ export default function RecipeScreen() { })} /> )} - {recipe.verificationStatus === "editorial" && } {recipe.description ?? recipe.descriptionSv} {recipe.creatorDisplayName && Skapat av {recipe.creatorDisplayName}} diff --git a/apps/mobile/src/app/recipes.tsx b/apps/mobile/src/app/recipes.tsx index d608c0c..cda583c 100644 --- a/apps/mobile/src/app/recipes.tsx +++ b/apps/mobile/src/app/recipes.tsx @@ -15,7 +15,6 @@ import { Screen, Small, Spacer, - Tag, } from "@/components/ui"; import { spacing } from "@/lib/theme"; @@ -164,10 +163,7 @@ export default function BrowseRecipesScreen() { onPress={() => router.push(`/recipe/${r.id}`)} style={{ gap: spacing.xs }} > - - {r.title ?? r.titleSv} - {r.verificationStatus === "editorial" && } - + {r.title ?? r.titleSv} {[ r.totalTimeMinutes != null ? `${r.totalTimeMinutes} min` : null, diff --git a/apps/mobile/src/app/shopping.tsx b/apps/mobile/src/app/shopping.tsx index 9601f85..94e335c 100644 --- a/apps/mobile/src/app/shopping.tsx +++ b/apps/mobile/src/app/shopping.tsx @@ -111,6 +111,20 @@ export default function ShoppingScreen() { Alert.alert(t("common.oops"), err instanceof Error ? err.message : t("common.error")), }); + const remove = useMutation({ + mutationFn: (itemId: string) => + api(`/v1/shopping-lists/${listId}/items/${itemId}`, { method: "DELETE" }), + onSuccess: invalidate, + onError: (err) => + Alert.alert(t("common.oops"), err instanceof Error ? err.message : t("common.error")), + }); + + const confirmRemove = (item: ShoppingItem) => + Alert.alert(item.displayName, "Ta bort varan från listan?", [ + { text: t("common.cancel"), style: "cancel" }, + { text: "Ta bort", style: "destructive", onPress: () => remove.mutate(item.id) }, + ]); + const complete = useMutation({ mutationFn: () => api(`/v1/shopping-lists/${listId}/complete`, { @@ -160,8 +174,11 @@ export default function ShoppingScreen() { {sectionLabel(section)} {items.map((item) => ( - toggle.mutate(item)}> - + + toggle.mutate(item)}> {item.checked ? "☑" : "☐"} @@ -176,15 +193,23 @@ export default function ShoppingScreen() { {item.displayName} · {formatQuantity(item.quantity, item.unit)} - {item.estimatedPriceMinor != null && ( - - {t("shopping.estimated", { - amount: formatMinor(item.estimatedPriceMinor, list.data.currency), - })} - - )} - - + + {item.estimatedPriceMinor != null && ( + + {t("shopping.estimated", { + amount: formatMinor(item.estimatedPriceMinor, list.data.currency), + })} + + )} + {/* Radera en vara man ångrat (utan att köpa den). */} + confirmRemove(item)} + hitSlop={8} + style={{ paddingHorizontal: spacing.sm }} + > + + + ))} ))}