fix(inkop+recept): delvis-kop, radera-vara, bort med plattformen-taggen
- 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.
This commit is contained in:
@@ -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 };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,6 @@ export default function RecipeScreen() {
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{recipe.verificationStatus === "editorial" && <Tag label="✓ plattformen" tone="success" />}
|
||||
</Row>
|
||||
<Body muted>{recipe.description ?? recipe.descriptionSv}</Body>
|
||||
{recipe.creatorDisplayName && <Small>Skapat av {recipe.creatorDisplayName}</Small>}
|
||||
|
||||
@@ -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 }}
|
||||
>
|
||||
<Row style={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Heading>{r.title ?? r.titleSv}</Heading>
|
||||
{r.verificationStatus === "editorial" && <Tag label="✓ plattformen" tone="success" />}
|
||||
</Row>
|
||||
<Heading>{r.title ?? r.titleSv}</Heading>
|
||||
<Small>
|
||||
{[
|
||||
r.totalTimeMinutes != null ? `${r.totalTimeMinutes} min` : null,
|
||||
|
||||
@@ -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() {
|
||||
<Card key={section}>
|
||||
<Heading>{sectionLabel(section)}</Heading>
|
||||
{items.map((item) => (
|
||||
<Pressable key={item.id} onPress={() => toggle.mutate(item)}>
|
||||
<Row style={{ justifyContent: "space-between", paddingVertical: 6 }}>
|
||||
<Row
|
||||
key={item.id}
|
||||
style={{ justifyContent: "space-between", alignItems: "center", paddingVertical: 6 }}
|
||||
>
|
||||
<Pressable style={{ flex: 1 }} onPress={() => toggle.mutate(item)}>
|
||||
<Body>
|
||||
<Text style={{ color: item.checked ? colors.primary : colors.textMuted }}>
|
||||
{item.checked ? "☑" : "☐"}
|
||||
@@ -176,15 +193,23 @@ export default function ShoppingScreen() {
|
||||
{item.displayName} · {formatQuantity(item.quantity, item.unit)}
|
||||
</Text>
|
||||
</Body>
|
||||
{item.estimatedPriceMinor != null && (
|
||||
<Small>
|
||||
{t("shopping.estimated", {
|
||||
amount: formatMinor(item.estimatedPriceMinor, list.data.currency),
|
||||
})}
|
||||
</Small>
|
||||
)}
|
||||
</Row>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
{item.estimatedPriceMinor != null && (
|
||||
<Small>
|
||||
{t("shopping.estimated", {
|
||||
amount: formatMinor(item.estimatedPriceMinor, list.data.currency),
|
||||
})}
|
||||
</Small>
|
||||
)}
|
||||
{/* Radera en vara man ångrat (utan att köpa den). */}
|
||||
<Pressable
|
||||
onPress={() => confirmRemove(item)}
|
||||
hitSlop={8}
|
||||
style={{ paddingHorizontal: spacing.sm }}
|
||||
>
|
||||
<Text style={{ color: colors.textMuted, fontSize: 18 }}>✕</Text>
|
||||
</Pressable>
|
||||
</Row>
|
||||
))}
|
||||
</Card>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user