feat(shopping): refonte UX + CRUD catalogue/boutiques + champs enrichis
- UX : vue par défaut = liste en cours, landing si pas de liste (+ vert + baguette magique), suppression des vues "listes" et "mode magasin" séparés - Articles cochés barrés et déplacés en bas, tri alphabétique par section - Nom de liste auto avec numéro de semaine ISO (S21 2026) - Wake lock activé dès qu'une liste est ouverte - CRUD boutiques : POST/PATCH/DELETE /stores + modal Boutiques - CRUD articles : POST/PATCH/DELETE /products + modal Catalogue - Champs enrichis produits : description, prix, quantité/unité, boutique défaut - Champs enrichis boutiques : url, store_type (alimentaire, bricolage…) - Migration 003 : nouveaux champs en base Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,22 @@ export interface Store {
|
||||
id: string
|
||||
name: string
|
||||
location: string | null
|
||||
url: string | null
|
||||
store_type: string | null
|
||||
}
|
||||
|
||||
export interface StoreCreate {
|
||||
name: string
|
||||
location?: string
|
||||
url?: string
|
||||
store_type?: string
|
||||
}
|
||||
|
||||
export interface StoreUpdate {
|
||||
name?: string
|
||||
location?: string
|
||||
url?: string
|
||||
store_type?: string
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
@@ -11,10 +27,39 @@ export interface Product {
|
||||
name: string
|
||||
brand: string | null
|
||||
category: string | null
|
||||
description: string | null
|
||||
default_unit: string | null
|
||||
barcode: string | null
|
||||
price: string | null
|
||||
quantity_per_unit: string | null
|
||||
default_store_id: string | null
|
||||
frequency_score: number
|
||||
}
|
||||
|
||||
export interface ProductCreate {
|
||||
name: string
|
||||
brand?: string
|
||||
category?: string
|
||||
description?: string
|
||||
default_unit?: string
|
||||
barcode?: string
|
||||
price?: string
|
||||
quantity_per_unit?: string
|
||||
default_store_id?: string
|
||||
}
|
||||
|
||||
export interface ProductUpdate {
|
||||
name?: string
|
||||
brand?: string
|
||||
category?: string
|
||||
description?: string
|
||||
default_unit?: string
|
||||
barcode?: string
|
||||
price?: string
|
||||
quantity_per_unit?: string
|
||||
default_store_id?: string
|
||||
}
|
||||
|
||||
export interface ShoppingItem {
|
||||
id: string
|
||||
product_id: string | null
|
||||
@@ -77,15 +122,62 @@ async function handleResponse<T>(res: Response): Promise<T> {
|
||||
return res.json() as Promise<T>
|
||||
}
|
||||
|
||||
// ── Stores ───────────────────────────────────────────────────────────────────
|
||||
|
||||
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 createStore(data: StoreCreate): Promise<Store> {
|
||||
return handleResponse(await fetch(`${BASE}/stores`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function updateStore(id: string, data: StoreUpdate): Promise<Store> {
|
||||
return handleResponse(await fetch(`${BASE}/stores/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function deleteStore(id: string): Promise<void> {
|
||||
return handleResponse(await fetch(`${BASE}/stores/${id}`, { method: 'DELETE' }))
|
||||
}
|
||||
|
||||
// ── Products ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function searchProducts(q?: string, limit = 50): Promise<Product[]> {
|
||||
const qs = new URLSearchParams({ limit: String(limit) })
|
||||
if (q) qs.set('q', q)
|
||||
return handleResponse(await fetch(`${BASE}/products?${qs}`))
|
||||
}
|
||||
|
||||
export async function createProduct(data: ProductCreate): Promise<Product> {
|
||||
return handleResponse(await fetch(`${BASE}/products`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function updateProduct(id: string, data: ProductUpdate): Promise<Product> {
|
||||
return handleResponse(await fetch(`${BASE}/products/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function deleteProduct(id: string): Promise<void> {
|
||||
return handleResponse(await fetch(`${BASE}/products/${id}`, { method: 'DELETE' }))
|
||||
}
|
||||
|
||||
// ── Lists ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function fetchLists(): Promise<ShoppingList[]> {
|
||||
return handleResponse(await fetch(`${BASE}/lists`))
|
||||
}
|
||||
@@ -114,6 +206,8 @@ export async function deleteList(id: string): Promise<void> {
|
||||
return handleResponse(await fetch(`${BASE}/lists/${id}`, { method: 'DELETE' }))
|
||||
}
|
||||
|
||||
// ── Items ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function addItem(listId: string, data: ShoppingItemCreate): Promise<ShoppingItem> {
|
||||
return handleResponse(await fetch(`${BASE}/lists/${listId}/items`, {
|
||||
method: 'POST',
|
||||
|
||||
Reference in New Issue
Block a user