fix+ux(shopping): suppression via bottom sheet, majuscule auto, icône FAB v0.4.6
- Bug: décrémentation à 0 d'un article pré-chargé (existingItemId) conserve la sélection à qty=0 (marqué pour DELETE) → bouton ✓ devient accessible Visuel: fond rouge + barré + opacité 0.6 pour les articles à supprimer - UX: première lettre en majuscule auto lors de l'ajout d'un article libre - UX: FAB remplace '+' par fa-cart-plus pour mieux signifier l'ajout à la liste Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homehub-frontend",
|
||||
"private": true,
|
||||
"version": "0.4.5",
|
||||
"version": "0.4.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -173,14 +173,20 @@ export default function ShoppingPage() {
|
||||
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)
|
||||
const sel = prev[idx]
|
||||
if (sel.qty <= 1) {
|
||||
// article pré-chargé : qty=0 = marqué pour suppression
|
||||
if (sel.existingItemId) return prev.map((s, i) => i === idx ? { ...s, qty: 0 } : s)
|
||||
return prev.filter((_, i) => i !== idx)
|
||||
}
|
||||
return prev.map((s, i) => i === idx ? { ...s, qty: s.qty - 1 } : s)
|
||||
})
|
||||
}
|
||||
|
||||
function addCustomItem() {
|
||||
const name = itemSearch.trim()
|
||||
if (!name) return
|
||||
const raw = itemSearch.trim()
|
||||
if (!raw) return
|
||||
const name = raw.charAt(0).toUpperCase() + raw.slice(1)
|
||||
setSelections(prev => {
|
||||
const exists = prev.some(s => s.type === 'custom' && s.name === name)
|
||||
if (exists) return prev
|
||||
@@ -203,7 +209,11 @@ export default function ShoppingPage() {
|
||||
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)
|
||||
const sel = prev[idx]
|
||||
if (sel.qty <= 1) {
|
||||
if (sel.existingItemId) return prev.map((s, i) => i === idx ? { ...s, qty: 0 } : s)
|
||||
return prev.filter((_, i) => i !== idx)
|
||||
}
|
||||
return prev.map((s, i) => i === idx ? { ...s, qty: s.qty - 1 } : s)
|
||||
})
|
||||
}
|
||||
@@ -217,7 +227,8 @@ export default function ShoppingPage() {
|
||||
for (const sel of toProcess) {
|
||||
if (sel.type === 'product') {
|
||||
if (sel.existingItemId) {
|
||||
await updateItem(currentList.id, sel.existingItemId, { quantity: String(sel.qty) })
|
||||
if (sel.qty === 0) await deleteItem(currentList.id, sel.existingItemId)
|
||||
else await updateItem(currentList.id, sel.existingItemId, { quantity: String(sel.qty) })
|
||||
} else {
|
||||
await addItem(currentList.id, {
|
||||
product_id: sel.product.id,
|
||||
@@ -227,7 +238,8 @@ export default function ShoppingPage() {
|
||||
}
|
||||
} else {
|
||||
if (sel.existingItemId) {
|
||||
await updateItem(currentList.id, sel.existingItemId, { quantity: String(sel.qty) })
|
||||
if (sel.qty === 0) await deleteItem(currentList.id, sel.existingItemId)
|
||||
else await updateItem(currentList.id, sel.existingItemId, { quantity: String(sel.qty) })
|
||||
} else if (sel.addToCatalogue) {
|
||||
const newProduct = await createProduct({ name: sel.name })
|
||||
await addItem(currentList.id, { product_id: newProduct.id, quantity: String(sel.qty) })
|
||||
@@ -514,7 +526,7 @@ export default function ShoppingPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* FAB + — masqué quand le sheet est ouvert */}
|
||||
{/* FAB — masqué quand le sheet est ouvert */}
|
||||
{!showAddSheet && (
|
||||
<button
|
||||
onClick={openAddSheet}
|
||||
@@ -523,10 +535,12 @@ export default function ShoppingPage() {
|
||||
position: 'fixed', bottom: 72, right: 20,
|
||||
width: 56, height: 56, borderRadius: '50%',
|
||||
background: 'var(--accent)', color: '#1d2021', border: 'none',
|
||||
fontSize: 28, cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
|
||||
fontSize: 22, cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}
|
||||
>+</button>
|
||||
>
|
||||
<i className="fa-solid fa-cart-plus" />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
@@ -620,18 +634,21 @@ export default function ShoppingPage() {
|
||||
<div style={{ flex: 1, overflowY: 'auto' }}>
|
||||
|
||||
{/* Articles libres sélectionnés */}
|
||||
{(selections.filter(s => s.type === 'custom') as Extract<typeof selections[number], { type: 'custom' }>[]).map(s => (
|
||||
{(selections.filter(s => s.type === 'custom') as Extract<typeof selections[number], { type: 'custom' }>[]).map(s => {
|
||||
const willDelete = s.qty === 0 && !!s.existingItemId
|
||||
return (
|
||||
<div
|
||||
key={`custom:${s.name}`}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 12,
|
||||
padding: '10px 16px', minHeight: 52,
|
||||
background: 'rgba(142,192,124,0.12)',
|
||||
background: willDelete ? 'rgba(251,73,52,0.08)' : 'rgba(142,192,124,0.12)',
|
||||
borderBottom: '1px solid var(--bg-4)',
|
||||
opacity: willDelete ? 0.6 : 1,
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontFamily: 'var(--font-ui)', fontSize: 15, color: 'var(--ink-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.name}</div>
|
||||
<div style={{ fontFamily: 'var(--font-ui)', fontSize: 15, color: 'var(--ink-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textDecoration: willDelete ? 'line-through' : 'none' }}>{s.name}</div>
|
||||
{!s.existingItemId ? (
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 5, marginTop: 3, cursor: 'pointer' }} onClick={e => e.stopPropagation()}>
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user