From a86c14b0b9ceefd7f0588a6e8ca497fa36b72cd4 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Mon, 25 May 2026 06:55:33 +0200 Subject: [PATCH] feat(shopping): proposition d'ajout au catalogue pour les articles libres MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quand un article tapé n'existe pas dans le catalogue et qu'aucun produit n'est sélectionné, une case "Ajouter au catalogue" apparaît (cochée par défaut). Si cochée, le produit est créé via POST /api/shopping/products avant l'ajout à la liste, avec l'unité pré-remplie si saisie. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/pages/ShoppingPage.tsx | 43 ++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/frontend/src/pages/ShoppingPage.tsx b/frontend/src/pages/ShoppingPage.tsx index a4071e3..cbd2542 100644 --- a/frontend/src/pages/ShoppingPage.tsx +++ b/frontend/src/pages/ShoppingPage.tsx @@ -4,7 +4,7 @@ import type { ShoppingListDetail, ShoppingList, Store, Product } from '../api/sh import { fetchLists, createList, fetchListDetail, deleteList, addItem, updateItem, deleteItem, finishShopping, fetchStores, generateMagicList, - searchProducts, + searchProducts, createProduct, } from '../api/shopping' import Modal from '../components/Modal' import ItemRow from '../components/shopping/ItemRow' @@ -44,6 +44,7 @@ export default function ShoppingPage() { const [selectedProduct, setSelectedProduct] = useState(null) const [newItemQty, setNewItemQty] = useState('') const [newItemUnit, setNewItemUnit] = useState('') + const [addToCatalogue, setAddToCatalogue] = useState(true) useWakeLock(currentList !== null) @@ -109,6 +110,7 @@ export default function ShoppingPage() { setSelectedProduct(null) setNewItemQty('') setNewItemUnit('') + setAddToCatalogue(true) setShowAddItemModal(false) } @@ -120,13 +122,25 @@ export default function ShoppingPage() { async function handleAddItem() { if (!currentList) return - const hasProduct = selectedProduct !== null - const customName = hasProduct ? undefined : itemSearch.trim() - if (!hasProduct && !customName) return + const customName = itemSearch.trim() + if (!selectedProduct && !customName) return try { + let productId = selectedProduct?.id + + // Article libre + case "Ajouter au catalogue" cochée → créer le produit d'abord + if (!selectedProduct && customName && addToCatalogue) { + const newProduct = await createProduct({ + name: customName, + default_unit: newItemUnit || undefined, + }) + productId = newProduct.id + // Met à jour la liste locale des produits pour les prochains ajouts + setProducts(prev => [...prev, newProduct].sort((a, b) => a.name.localeCompare(b.name, 'fr'))) + } + await addItem(currentList.id, { - product_id: selectedProduct?.id, - custom_name: customName, + product_id: productId, + custom_name: !productId ? customName : undefined, quantity: newItemQty || undefined, unit: newItemUnit || undefined, }) @@ -425,7 +439,7 @@ export default function ShoppingPage() { color: 'var(--ink-4)', fontFamily: 'var(--font-ui)', fontSize: 13, textAlign: 'center', padding: '12px 16px', margin: 0, ...noSelect, }}> - {itemSearch.trim() ? `"${itemSearch}" — sera ajouté comme article libre` : 'Catalogue vide'} + {itemSearch.trim() ? `"${itemSearch}" — article libre` : 'Catalogue vide'}

)} {filteredProducts.map((p, idx) => { @@ -460,6 +474,21 @@ export default function ShoppingPage() { })} + {/* Case "Ajouter au catalogue" — uniquement si article libre sans correspondance */} + {!selectedProduct && itemSearch.trim() && filteredProducts.length === 0 && ( + + )} + {/* Quantité + Unité */}