From 1ca2d986ce2e9f4de652a90f55e8b290eac6bee0 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Sun, 24 May 2026 15:39:32 +0200 Subject: [PATCH] =?UTF-8?q?feat(shopping):=20client=20API=20TypeScript=20t?= =?UTF-8?q?yp=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/shopping.ts | 143 +++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 frontend/src/api/shopping.ts diff --git a/frontend/src/api/shopping.ts b/frontend/src/api/shopping.ts new file mode 100644 index 0000000..c887378 --- /dev/null +++ b/frontend/src/api/shopping.ts @@ -0,0 +1,143 @@ +// frontend/src/api/shopping.ts + +export interface Store { + id: string + name: string + location: string | null +} + +export interface Product { + id: string + name: string + brand: string | null + category: string | null + default_unit: string | null + frequency_score: number +} + +export interface ShoppingItem { + id: string + product_id: string | null + custom_name: string | null + display_name: string + quantity: string | null + unit: string | null + is_checked: boolean + price_recorded: string | null + carried_over: boolean + sort_order: number | null +} + +export interface ShoppingList { + id: string + name: string | null + store_id: string | null + week_date: string | null + status: 'draft' | 'active' | 'done' + created_at: string + item_count: number + checked_count: number +} + +export interface ShoppingListDetail extends ShoppingList { + items: ShoppingItem[] +} + +export interface ShoppingListCreate { + name?: string + store_id?: string + week_date?: string +} + +export interface ShoppingListUpdate { + name?: string + store_id?: string + status?: 'draft' | 'active' | 'done' +} + +export interface ShoppingItemCreate { + product_id?: string + custom_name?: string + quantity?: string + unit?: string +} + +export interface ShoppingItemUpdate { + is_checked?: boolean + quantity?: string + unit?: string + price_recorded?: string +} + +const BASE = '/api/shopping' + +async function handleResponse(res: Response): Promise { + if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) + if (res.status === 204) return undefined as T + return res.json() as Promise +} + +export async function fetchStores(): Promise { + return handleResponse(await fetch(`${BASE}/stores`)) +} + +export async function searchProducts(q?: string): Promise { + const qs = q ? `?q=${encodeURIComponent(q)}&limit=30` : '?limit=30' + return handleResponse(await fetch(`${BASE}/products${qs}`)) +} + +export async function fetchLists(): Promise { + return handleResponse(await fetch(`${BASE}/lists`)) +} + +export async function createList(data: ShoppingListCreate): Promise { + return handleResponse(await fetch(`${BASE}/lists`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + })) +} + +export async function fetchListDetail(id: string): Promise { + return handleResponse(await fetch(`${BASE}/lists/${id}`)) +} + +export async function updateList(id: string, data: ShoppingListUpdate): Promise { + return handleResponse(await fetch(`${BASE}/lists/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + })) +} + +export async function deleteList(id: string): Promise { + return handleResponse(await fetch(`${BASE}/lists/${id}`, { method: 'DELETE' })) +} + +export async function addItem(listId: string, data: ShoppingItemCreate): Promise { + return handleResponse(await fetch(`${BASE}/lists/${listId}/items`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + })) +} + +export async function updateItem(listId: string, itemId: string, data: ShoppingItemUpdate): Promise { + return handleResponse(await fetch(`${BASE}/lists/${listId}/items/${itemId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + })) +} + +export async function deleteItem(listId: string, itemId: string): Promise { + return handleResponse(await fetch(`${BASE}/lists/${listId}/items/${itemId}`, { method: 'DELETE' })) +} + +export async function finishShopping(listId: string): Promise { + return handleResponse(await fetch(`${BASE}/lists/${listId}/finish`, { method: 'POST' })) +} + +export async function generateMagicList(): Promise { + return handleResponse(await fetch(`${BASE}/lists/generate`, { method: 'POST' })) +}