import { useState } from "react"; import { Alert, Pressable, Text, View } from "react-native"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useLocalSearchParams } from "expo-router"; import { api } from "@/lib/api"; import { t } from "@/lib/i18n"; import { formatMinor } from "@/lib/money"; import { Body, Button, Card, EmptyState, ErrorView, Heading, Input, LoadingView, Row, Screen, Small, Spacer, } from "@/components/ui"; import { colors, spacing } from "@/lib/theme"; import { formatQuantity } from "@/lib/units"; /** Inköpslista (spec §27): delad, avdelningssorterad, uppdaterar lagret vid avslut. */ interface ShoppingItem { id: string; displayName: string; quantity: number; unit: string; storeSection: string; estimatedPriceMinor: number | null; checked: boolean; } interface ListResponse { list: { id: string; name: string }; items: ShoppingItem[]; estimatedTotalMinor: number; currency: string; } /** Butikssektionernas etiketter bor i i18n-katalogen: shopping.section. (12 språk). */ const SECTION_IDS = new Set([ "frukt_gront", "brod", "mejeri", "kott_fagel", "fisk", "chark", "frys", "skafferi", "konserver", "kryddor_bak", "dryck", "snacks", "hygien_ovrigt", ]); const sectionLabel = (id: string): string => SECTION_IDS.has(id) ? t(`shopping.section.${id}`) : id; export default function ShoppingScreen() { const queryClient = useQueryClient(); const [newItem, setNewItem] = useState(""); const params = useLocalSearchParams<{ listId?: string }>(); const paramListId = typeof params.listId === "string" ? params.listId : undefined; const lists = useQuery({ queryKey: ["shopping-lists"], queryFn: async () => { const result = await api<{ lists: Array<{ id: string }> }>("/v1/shopping-lists"); if (result.lists.length === 0) { const created = await api<{ list: { id: string } }>("/v1/shopping-lists", { method: "POST", body: { name: t("shopping.title") }, }); return [created.list]; } return result.lists; }, }); // Prioritera listId från navigering (t.ex. nygenererad från veckoplan), // annars nyaste aktiva listan. const listId = paramListId ?? lists.data?.[0]?.id; const list = useQuery({ queryKey: ["shopping-list", listId], queryFn: () => api(`/v1/shopping-lists/${listId}`), enabled: Boolean(listId), }); const invalidate = () => { void queryClient.invalidateQueries({ queryKey: ["shopping-list", listId] }); void queryClient.invalidateQueries({ queryKey: ["shopping-lists"] }); }; const addItem = useMutation({ mutationFn: (displayName: string) => api(`/v1/shopping-lists/${listId}/items`, { method: "POST", body: { displayName } }), onSuccess: () => { setNewItem(""); invalidate(); }, onError: (err) => Alert.alert(t("common.oops"), err instanceof Error ? err.message : t("common.error")), }); const toggle = useMutation({ mutationFn: (item: ShoppingItem) => api(`/v1/shopping-lists/${listId}/items/${item.id}`, { method: "PATCH", body: { checked: !item.checked }, }), onSuccess: invalidate, onError: (err) => 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`, { method: "POST", body: { addToInventory: true }, }), onSuccess: async (result) => { const added = (result as { itemsAddedToInventory?: number }).itemsAddedToInventory ?? 0; await queryClient.invalidateQueries({ queryKey: ["inventory"] }); await queryClient.invalidateQueries({ queryKey: ["what-to-eat"] }); invalidate(); Alert.alert(t("shopping.completedTitle"), t("shopping.completedBody", { count: added })); }, onError: (err) => Alert.alert(t("common.oops"), err instanceof Error ? err.message : t("common.error")), }); if (lists.isLoading || list.isLoading) return ; if (list.isError || !list.data) return void list.refetch()} />; const grouped = new Map(); for (const item of list.data.items) { const arr = grouped.get(item.storeSection) ?? []; arr.push(item); grouped.set(item.storeSection, arr); } const checkedCount = list.data.items.filter((i) => i.checked).length; return ( newItem.trim() && addItem.mutate(newItem.trim())} returnKeyType="done" />