feat(shopping): bouton liste magique + modal édition liste avec suppression
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { useState, useEffect, useCallback } from 'react'
|
|||||||
import type { ShoppingList, ShoppingListDetail, ShoppingItem, Store } from '../api/shopping'
|
import type { ShoppingList, ShoppingListDetail, ShoppingItem, Store } from '../api/shopping'
|
||||||
import {
|
import {
|
||||||
fetchLists, createList, fetchListDetail, updateList, deleteList,
|
fetchLists, createList, fetchListDetail, updateList, deleteList,
|
||||||
addItem, updateItem, deleteItem, finishShopping, fetchStores,
|
addItem, updateItem, deleteItem, finishShopping, fetchStores, generateMagicList,
|
||||||
} from '../api/shopping'
|
} from '../api/shopping'
|
||||||
import Modal from '../components/Modal'
|
import Modal from '../components/Modal'
|
||||||
import ItemRow from '../components/shopping/ItemRow'
|
import ItemRow from '../components/shopping/ItemRow'
|
||||||
@@ -44,6 +44,8 @@ export default function ShoppingPage() {
|
|||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||||
const [showAddItemModal, setShowAddItemModal] = useState(false)
|
const [showAddItemModal, setShowAddItemModal] = useState(false)
|
||||||
|
const [showEditListModal, setShowEditListModal] = useState(false)
|
||||||
|
const [generating, setGenerating] = useState(false)
|
||||||
|
|
||||||
const [newListName, setNewListName] = useState('')
|
const [newListName, setNewListName] = useState('')
|
||||||
const [newListStore, setNewListStore] = useState('')
|
const [newListStore, setNewListStore] = useState('')
|
||||||
@@ -164,6 +166,33 @@ export default function ShoppingPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleGenerateMagicList() {
|
||||||
|
setGenerating(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
const newList = await generateMagicList()
|
||||||
|
void loadLists()
|
||||||
|
setActiveList(newList)
|
||||||
|
setView('detail')
|
||||||
|
} catch {
|
||||||
|
setError('Erreur lors de la génération')
|
||||||
|
} finally {
|
||||||
|
setGenerating(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeleteActiveList() {
|
||||||
|
if (!activeList) return
|
||||||
|
try {
|
||||||
|
await deleteList(activeList.id)
|
||||||
|
setView('lists')
|
||||||
|
setActiveList(null)
|
||||||
|
void loadLists()
|
||||||
|
} catch {
|
||||||
|
setError('Erreur lors de la suppression')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Vue mode magasin ──────────────────────────────────────────────────────
|
// ── Vue mode magasin ──────────────────────────────────────────────────────
|
||||||
if (view === 'store' && activeList) {
|
if (view === 'store' && activeList) {
|
||||||
const unchecked = activeList.items.filter(i => !i.is_checked)
|
const unchecked = activeList.items.filter(i => !i.is_checked)
|
||||||
@@ -250,6 +279,22 @@ export default function ShoppingPage() {
|
|||||||
{activeList.checked_count}/{activeList.item_count} cochés
|
{activeList.checked_count}/{activeList.item_count} cochés
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowEditListModal(true)}
|
||||||
|
title="Modifier / gérer la liste"
|
||||||
|
style={{
|
||||||
|
background: 'var(--bg-3)',
|
||||||
|
border: '1px solid var(--bg-5)',
|
||||||
|
borderRadius: 8,
|
||||||
|
color: 'var(--ink-2)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '8px 12px',
|
||||||
|
fontSize: 16,
|
||||||
|
minHeight: 44,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-pen" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setView('store')}
|
onClick={() => setView('store')}
|
||||||
style={{
|
style={{
|
||||||
@@ -331,6 +376,75 @@ export default function ShoppingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showEditListModal && (
|
||||||
|
<Modal title="Gérer la liste" onClose={() => setShowEditListModal(false)}>
|
||||||
|
<p style={{ color: 'var(--ink-3)', fontSize: 12, fontFamily: 'var(--font-ui)', margin: 0, textTransform: 'uppercase', letterSpacing: 1 }}>
|
||||||
|
Ajouter un article
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
style={inputStyle}
|
||||||
|
placeholder="Nom de l'article *"
|
||||||
|
value={newItemName}
|
||||||
|
onChange={e => setNewItemName(e.target.value)}
|
||||||
|
autoFocus
|
||||||
|
onKeyDown={async e => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
await handleAddItem()
|
||||||
|
setShowEditListModal(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
|
||||||
|
<input
|
||||||
|
style={inputStyle}
|
||||||
|
placeholder="Quantité"
|
||||||
|
value={newItemQty}
|
||||||
|
onChange={e => setNewItemQty(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
style={inputStyle}
|
||||||
|
placeholder="Unité (kg, L…)"
|
||||||
|
value={newItemUnit}
|
||||||
|
onChange={e => setNewItemUnit(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
await handleAddItem()
|
||||||
|
if (!error) setShowEditListModal(false)
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
padding: '10px 20px', borderRadius: 8, border: 'none',
|
||||||
|
background: 'var(--accent)', color: '#1d2021', cursor: 'pointer',
|
||||||
|
fontFamily: 'var(--font-ui)', fontWeight: 600, minHeight: 48, width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-plus" style={{ marginRight: 8 }} />
|
||||||
|
Ajouter
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div style={{ borderTop: '1px solid var(--bg-4)', margin: '4px 0' }} />
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowEditListModal(false)
|
||||||
|
void handleDeleteActiveList()
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
padding: '10px 20px', borderRadius: 8,
|
||||||
|
border: '1px solid var(--err)',
|
||||||
|
background: 'transparent', color: 'var(--err)',
|
||||||
|
cursor: 'pointer', fontFamily: 'var(--font-ui)',
|
||||||
|
fontWeight: 600, minHeight: 48, width: '100%',
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-trash" />
|
||||||
|
Supprimer la liste en cours
|
||||||
|
</button>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -338,8 +452,34 @@ export default function ShoppingPage() {
|
|||||||
// ── Vue liste des listes ───────────────────────────────────────────────────
|
// ── Vue liste des listes ───────────────────────────────────────────────────
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
|
||||||
<h1 style={{ color: 'var(--accent)', fontFamily: 'var(--font-mono)', margin: 0, flex: 1 }}>Courses</h1>
|
<h1 style={{ color: 'var(--accent)', fontFamily: 'var(--font-mono)', margin: 0, flex: 1 }}>Courses</h1>
|
||||||
|
<button
|
||||||
|
onClick={() => void handleGenerateMagicList()}
|
||||||
|
disabled={generating || lists.some(l => l.status === 'draft' || l.status === 'active')}
|
||||||
|
title="Générer une liste automatiquement"
|
||||||
|
style={{
|
||||||
|
background: 'var(--bg-3)',
|
||||||
|
border: '1px solid var(--bg-5)',
|
||||||
|
borderRadius: 8,
|
||||||
|
color: lists.some(l => l.status === 'draft' || l.status === 'active')
|
||||||
|
? 'var(--ink-4)'
|
||||||
|
: 'var(--accent)',
|
||||||
|
cursor: lists.some(l => l.status === 'draft' || l.status === 'active')
|
||||||
|
? 'not-allowed'
|
||||||
|
: 'pointer',
|
||||||
|
padding: '8px 12px',
|
||||||
|
fontFamily: 'var(--font-ui)',
|
||||||
|
fontSize: 13,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
minHeight: 44,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-wand-magic-sparkles" />
|
||||||
|
{generating ? 'Génération…' : 'Liste magique'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
Reference in New Issue
Block a user