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:
2026-05-24 16:21:45 +02:00
parent 925e077afe
commit 85093f1b99
8 changed files with 1056 additions and 442 deletions
+97 -3
View File
@@ -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',