diff --git a/frontend/package.json b/frontend/package.json index 8f4bd24..c857f8d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "homehub-frontend", "private": true, - "version": "0.4.2", + "version": "0.4.3", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/pages/ShoppingPage.tsx b/frontend/src/pages/ShoppingPage.tsx index 7c13e5d..5c9c63d 100644 --- a/frontend/src/pages/ShoppingPage.tsx +++ b/frontend/src/pages/ShoppingPage.tsx @@ -28,6 +28,28 @@ const inputStyle: React.CSSProperties = { const noSelect: React.CSSProperties = { userSelect: 'none' } +function QtyControls({ qty, onDecrement, onIncrement }: { qty: number; onDecrement: () => void; onIncrement: () => void }) { + const btnBase: React.CSSProperties = { + width: 32, height: 32, borderRadius: 8, border: 'none', + display: 'flex', alignItems: 'center', justifyContent: 'center', + fontSize: 18, fontWeight: 700, cursor: 'pointer', flexShrink: 0, + } + return ( +
e.stopPropagation()}> + {qty > 0 && ( + <> + + {qty} + + )} + +
+ ) +} + export default function ShoppingPage() { const [currentList, setCurrentList] = useState(null) const [allLists, setAllLists] = useState([]) @@ -45,7 +67,7 @@ export default function ShoppingPage() { const [editQty, setEditQty] = useState('') const [editUnit, setEditUnit] = useState('') - type Selection = { type: 'product'; product: Product } | { type: 'custom'; name: string } + type Selection = { type: 'product'; product: Product; qty: number } | { type: 'custom'; name: string; qty: number } const [itemSearch, setItemSearch] = useState('') const [selections, setSelections] = useState([]) const [addSaving, setAddSaving] = useState(false) @@ -115,11 +137,24 @@ export default function ShoppingPage() { setShowAddSheet(false) } - function toggleProduct(p: Product) { + function getProductQty(id: string): number { + return (selections.find(s => s.type === 'product' && s.product.id === id) as Extract | undefined)?.qty ?? 0 + } + + function incrementProduct(p: Product) { setSelections(prev => { - const exists = prev.some(s => s.type === 'product' && s.product.id === p.id) - if (exists) return prev.filter(s => !(s.type === 'product' && s.product.id === p.id)) - return [...prev, { type: 'product' as const, product: p }] + const idx = prev.findIndex(s => s.type === 'product' && s.product.id === p.id) + if (idx === -1) return [...prev, { type: 'product' as const, product: p, qty: 1 }] + return prev.map((s, i) => i === idx ? { ...s, qty: s.qty + 1 } : s) + }) + } + + function decrementProduct(p: Product) { + setSelections(prev => { + const idx = prev.findIndex(s => s.type === 'product' && s.product.id === p.id) + if (idx === -1) return prev + if (prev[idx].qty <= 1) return prev.filter((_, i) => i !== idx) + return prev.map((s, i) => i === idx ? { ...s, qty: s.qty - 1 } : s) }) } @@ -129,16 +164,22 @@ export default function ShoppingPage() { setSelections(prev => { const exists = prev.some(s => s.type === 'custom' && s.name === name) if (exists) return prev - return [...prev, { type: 'custom' as const, name }] + return [...prev, { type: 'custom' as const, name, qty: 1 }] }) setItemSearch('') } - function removeSelection(key: string) { - setSelections(prev => prev.filter(s => { - if (s.type === 'product') return s.product.id !== key - return s.name !== key - })) + function incrementCustom(name: string) { + setSelections(prev => prev.map(s => s.type === 'custom' && s.name === name ? { ...s, qty: s.qty + 1 } : s)) + } + + function decrementCustom(name: string) { + setSelections(prev => { + const idx = prev.findIndex(s => s.type === 'custom' && s.name === name) + if (idx === -1) return prev + if (prev[idx].qty <= 1) return prev.filter((_, i) => i !== idx) + return prev.map((s, i) => i === idx ? { ...s, qty: s.qty - 1 } : s) + }) } async function handleConfirmAdd() { @@ -149,10 +190,14 @@ export default function ShoppingPage() { if (sel.type === 'product') { await addItem(currentList.id, { product_id: sel.product.id, + quantity: String(sel.qty), unit: sel.product.default_unit || undefined, }) } else { - await addItem(currentList.id, { custom_name: sel.name }) + await addItem(currentList.id, { + custom_name: sel.name, + quantity: String(sel.qty), + }) } } closeAddSheet() @@ -505,58 +550,46 @@ export default function ShoppingPage() { {/* Liste scrollable */}
- {/* Articles libres sélectionnés (affichés en tête) */} - {selections.filter(s => s.type === 'custom').map(s => ( + {/* Articles libres sélectionnés */} + {(selections.filter(s => s.type === 'custom') as Extract[]).map(s => (
removeSelection(s.name)} style={{ display: 'flex', alignItems: 'center', gap: 12, - padding: '12px 16px', cursor: 'pointer', minHeight: 56, + padding: '10px 16px', minHeight: 52, background: 'rgba(142,192,124,0.12)', borderBottom: '1px solid var(--bg-4)', }} > -
- -
-
-
{s.name}
+
+
{s.name}
article libre
+ decrementCustom(s.name)} + onIncrement={() => incrementCustom(s.name)} + />
))} {/* Produits du catalogue */} {filteredProducts.map(p => { - const selected = selections.some(s => s.type === 'product' && s.product.id === p.id) + const qty = getProductQty(p.id) + const selected = qty > 0 return (
toggleProduct(p)} style={{ display: 'flex', alignItems: 'center', gap: 12, - padding: '12px 16px', cursor: 'pointer', minHeight: 56, + padding: '10px 16px', minHeight: 52, background: selected ? 'rgba(142,192,124,0.12)' : 'transparent', borderBottom: '1px solid var(--bg-4)', transition: 'background 0.1s', }} > -
- {selected && } -
-
+
{p.name}
{(p.brand || p.default_unit) && ( @@ -565,6 +598,11 @@ export default function ShoppingPage() {
)}
+ decrementProduct(p)} + onIncrement={() => incrementProduct(p)} + />
) })} @@ -576,11 +614,11 @@ export default function ShoppingPage() { style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 16px', cursor: 'pointer', - color: 'var(--info)', borderBottom: '1px solid var(--bg-4)', + borderBottom: '1px solid var(--bg-4)', }} > - + - + + + Ajouter "{itemSearch.trim()}"