source: frontend/src/pages/Recipes.jsx

Last change on this file was 98cd519, checked in by Acko <lavurovski.a65@…>, 17 hours ago

Shema update + project push

  • Property mode set to 100644
File size: 5.5 KB
Line 
1import { useEffect, useState } from 'react'
2import { api } from '../lib/api.js'
3
4export default function Recipes() {
5 const [mine, setMine] = useState([])
6 const [catalog, setCatalog] = useState([])
7 const [restrictions, setRestrictions] = useState([])
8 const [detail, setDetail] = useState(null)
9 const [error, setError] = useState(null)
10 const [form, setForm] = useState({ name: '', guide: '', servings: 4, restrictionIds: [] })
11 const [lines, setLines] = useState([{ ingredientName: '', quantity: '' }])
12
13 const load = () => api.get('/recipes/mine').then(setMine).catch(e => setError(e.message))
14
15 useEffect(() => {
16 load()
17 api.get('/catalog/ingredients').then(setCatalog).catch(() => {})
18 api.get('/catalog/restrictions').then(setRestrictions).catch(() => {})
19 }, [])
20
21 const setLine = (i, key) => (e) => {
22 const copy = [...lines]
23 copy[i] = { ...copy[i], [key]: e.target.value }
24 setLines(copy)
25 }
26
27 const create = async () => {
28 setError(null)
29 try {
30 const body = {
31 name: form.name,
32 guide: form.guide,
33 servings: Number(form.servings),
34 ingredients: lines
35 .filter(l => l.ingredientName && l.quantity)
36 .map(l => ({ ingredientName: l.ingredientName, quantity: Number(l.quantity) })),
37 restrictionIds: form.restrictionIds.map(Number)
38 }
39 const created = await api.post('/recipes', body)
40 setDetail(created)
41 setForm({ name: '', guide: '', servings: 4, restrictionIds: [] })
42 setLines([{ ingredientName: '', quantity: '' }])
43 load()
44 } catch (e) { setError(e.message) }
45 }
46
47 const share = async (id) => {
48 setError(null)
49 try {
50 await api.post('/posts', { recipeId: id, isPrivate: false, isFavourite: false, status: 'published' })
51 alert('Shared to the feed')
52 } catch (e) { setError(e.message) }
53 }
54
55 return (
56 <div>
57 {error && <div className="error">{error}</div>}
58
59 <div className="card">
60 <h3>New recipe</h3>
61 <p className="muted">
62 Energy is not entered: the server sums it from the ingredients and divides by servings.
63 </p>
64 <div className="stack" style={{ maxWidth: 560 }}>
65 <input placeholder="name" value={form.name}
66 onChange={e => setForm({ ...form, name: e.target.value })} />
67 <textarea placeholder="preparation steps" rows={3} value={form.guide}
68 onChange={e => setForm({ ...form, guide: e.target.value })} />
69 <input type="number" min="1" placeholder="servings" value={form.servings}
70 onChange={e => setForm({ ...form, servings: e.target.value })} />
71
72 {lines.map((l, i) => (
73 <div className="row" key={i}>
74 <select value={l.ingredientName} onChange={setLine(i, 'ingredientName')} style={{ flex: 1 }}>
75 <option value="">— ingredient —</option>
76 {catalog.map(c => <option key={c.name} value={c.name}>{c.name} ({c.kcal} kcal)</option>)}
77 </select>
78 <input type="number" placeholder="grams" style={{ width: 110 }}
79 value={l.quantity} onChange={setLine(i, 'quantity')} />
80 </div>
81 ))}
82 <button className="small" onClick={() => setLines([...lines, { ingredientName: '', quantity: '' }])}>
83 + another ingredient
84 </button>
85
86 <select multiple size={4} value={form.restrictionIds}
87 onChange={e => setForm({
88 ...form,
89 restrictionIds: Array.from(e.target.selectedOptions, o => o.value)
90 })}>
91 {restrictions.map(r => <option key={r.id} value={r.id}>{r.type} — {r.description}</option>)}
92 </select>
93
94 <button className="primary" onClick={create}>Create recipe</button>
95 </div>
96 </div>
97
98 {detail && (
99 <div className="card">
100 <h3>{detail.name}</h3>
101 <div className="muted">
102 total <span className="kcal">{detail.kcalSum} kcal</span> ·
103 {' '}{String(detail.servings)} servings ·
104 {' '}<span className="kcal">{detail.kcalPerServing} kcal / portion</span>
105 </div>
106 <table>
107 <thead><tr><th>ingredient</th><th>grams</th><th>kcal/100g</th><th>contributes</th></tr></thead>
108 <tbody>
109 {detail.ingredients.map(i => (
110 <tr key={i.ingredientName}>
111 <td>{i.ingredientName}</td><td>{i.quantity}</td>
112 <td>{i.kcalPer100g}</td><td>{i.kcalContributed}</td>
113 </tr>
114 ))}
115 </tbody>
116 </table>
117 {detail.restrictions.length > 0 &&
118 <p className="muted">tags: {detail.restrictions.join(', ')}</p>}
119 </div>
120 )}
121
122 <div className="card">
123 <h3>My recipes</h3>
124 <table>
125 <tbody>
126 {mine.map(r => (
127 <tr key={r.id}>
128 <td>{r.name}</td>
129 <td className="kcal">{r.kcalPerServing} kcal / portion</td>
130 <td style={{ width: 1 }}>
131 <button className="small" onClick={() => api.get(`/recipes/${r.id}`).then(setDetail)}>
132 view
133 </button>
134 </td>
135 <td style={{ width: 1 }}>
136 <button className="small" onClick={() => share(r.id)}>share</button>
137 </td>
138 </tr>
139 ))}
140 </tbody>
141 </table>
142 {mine.length === 0 && <p className="muted">No recipes yet.</p>}
143 </div>
144 </div>
145 )
146}
Note: See TracBrowser for help on using the repository browser.