feat(shopping): client API TypeScript typé

This commit is contained in:
2026-05-24 15:39:32 +02:00
parent e4c3edc72b
commit 1ca2d986ce
+143
View File
@@ -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<T>(res: Response): Promise<T> {
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
if (res.status === 204) return undefined as T
return res.json() as Promise<T>
}
export async function fetchStores(): Promise<Store[]> {
return handleResponse(await fetch(`${BASE}/stores`))
}
export async function searchProducts(q?: string): Promise<Product[]> {
const qs = q ? `?q=${encodeURIComponent(q)}&limit=30` : '?limit=30'
return handleResponse(await fetch(`${BASE}/products${qs}`))
}
export async function fetchLists(): Promise<ShoppingList[]> {
return handleResponse(await fetch(`${BASE}/lists`))
}
export async function createList(data: ShoppingListCreate): Promise<ShoppingListDetail> {
return handleResponse(await fetch(`${BASE}/lists`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}))
}
export async function fetchListDetail(id: string): Promise<ShoppingListDetail> {
return handleResponse(await fetch(`${BASE}/lists/${id}`))
}
export async function updateList(id: string, data: ShoppingListUpdate): Promise<ShoppingListDetail> {
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<void> {
return handleResponse(await fetch(`${BASE}/lists/${id}`, { method: 'DELETE' }))
}
export async function addItem(listId: string, data: ShoppingItemCreate): Promise<ShoppingItem> {
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<ShoppingItem> {
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<void> {
return handleResponse(await fetch(`${BASE}/lists/${listId}/items/${itemId}`, { method: 'DELETE' }))
}
export async function finishShopping(listId: string): Promise<ShoppingListDetail> {
return handleResponse(await fetch(`${BASE}/lists/${listId}/finish`, { method: 'POST' }))
}
export async function generateMagicList(): Promise<ShoppingListDetail> {
return handleResponse(await fetch(`${BASE}/lists/generate`, { method: 'POST' }))
}