source: frontend/src/pages/Grocery.jsx@ 2e0035f

Last change on this file since 2e0035f was 2e0035f, checked in by Acko <lavurovski.a65@…>, 9 days ago

Initial Commit

  • Property mode set to 100644
File size: 4.1 KB
Line 
1import { useEffect, useState } from 'react'
2import { api } from '../lib/api.js'
3
4/** UseCase1003 - bulk-add recipes plus individual items, then consolidate. */
5export default function Grocery() {
6 const [lists, setLists] = useState([])
7 const [recipes, setRecipes] = useState([])
8 const [catalog, setCatalog] = useState([])
9 const [open, setOpen] = useState(null)
10 const [error, setError] = useState(null)
11 const [notes, setNotes] = useState('')
12 const [bulk, setBulk] = useState([])
13 const [item, setItem] = useState({ ingredientName: '', buyQuantity: '' })
14
15 const load = () => api.get('/grocery-lists').then(setLists).catch(e => setError(e.message))
16
17 useEffect(() => {
18 load()
19 api.get('/recipes').then(setRecipes).catch(() => {})
20 api.get('/catalog/ingredients').then(setCatalog).catch(() => {})
21 }, [])
22
23 const create = async () => {
24 setError(null)
25 try {
26 const created = await api.post('/grocery-lists', {
27 notes,
28 bulkRecipeIds: bulk.map(Number),
29 singleItems: item.ingredientName && item.buyQuantity
30 ? [{ ingredientName: item.ingredientName, buyQuantity: Number(item.buyQuantity) }]
31 : []
32 })
33 setOpen(created)
34 setNotes(''); setBulk([]); setItem({ ingredientName: '', buyQuantity: '' })
35 load()
36 } catch (e) { setError(e.message) }
37 }
38
39 return (
40 <div>
41 {error && <div className="error">{error}</div>}
42
43 <div className="card">
44 <h3>New grocery list</h3>
45 <div className="stack" style={{ maxWidth: 520 }}>
46 <input placeholder="notes" value={notes} onChange={e => setNotes(e.target.value)} />
47 <span className="muted">Add whole recipes:</span>
48 <select multiple size={5} value={bulk}
49 onChange={e => setBulk(Array.from(e.target.selectedOptions, o => o.value))}>
50 {recipes.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
51 </select>
52 <span className="muted">Plus one item on its own:</span>
53 <div className="row">
54 <select style={{ flex: 1 }} value={item.ingredientName}
55 onChange={e => setItem({ ...item, ingredientName: e.target.value })}>
56 <option value="">— ingredient —</option>
57 {catalog.map(c => <option key={c.name} value={c.name}>{c.name}</option>)}
58 </select>
59 <input type="number" placeholder="grams" style={{ width: 110 }}
60 value={item.buyQuantity}
61 onChange={e => setItem({ ...item, buyQuantity: e.target.value })} />
62 </div>
63 <button className="primary" onClick={create}>Create list</button>
64 </div>
65 </div>
66
67 {open && (
68 <div className="card">
69 <h3>Shopping list #{open.id}</h3>
70 <div className="muted">
71 {open.notes} · total <span className="kcal">{open.kcal} kcal</span>
72 {open.bulkRecipes.length > 0 && <> · from: {open.bulkRecipes.join(', ')}</>}
73 </div>
74 <table>
75 <thead><tr><th>category</th><th>ingredient</th><th>grams</th></tr></thead>
76 <tbody>
77 {open.shoppingLines.map(l => (
78 <tr key={l.ingredient}>
79 <td className="muted">{l.category}</td>
80 <td>{l.ingredient}</td>
81 <td>{l.totalGrams}</td>
82 </tr>
83 ))}
84 </tbody>
85 </table>
86 </div>
87 )}
88
89 <div className="card">
90 <h3>My lists</h3>
91 <table>
92 <tbody>
93 {lists.map(l => (
94 <tr key={l.id}>
95 <td>{new Date(l.dateTime).toLocaleDateString()}</td>
96 <td>{l.notes}</td>
97 <td className="kcal">{l.kcal} kcal</td>
98 <td>{l.bought ? 'bought' : 'open'}</td>
99 <td style={{ width: 1 }}>
100 <button className="small" onClick={() => api.get(`/grocery-lists/${l.id}`).then(setOpen)}>
101 open
102 </button>
103 </td>
104 </tr>
105 ))}
106 </tbody>
107 </table>
108 </div>
109 </div>
110 )
111}
Note: See TracBrowser for help on using the repository browser.