diff --git a/backend/routers/nutrition.py b/backend/routers/nutrition.py index 223dacc..6738331 100644 --- a/backend/routers/nutrition.py +++ b/backend/routers/nutrition.py @@ -163,3 +163,61 @@ def nutrition_weekly(weeks: int=16, x_profile_id: Optional[str]=Header(default=N def avg(k): return round(sum(float(e.get(k) or 0) for e in en)/n,1) result.append({'week':wk,'days':n,'kcal':avg('kcal'),'protein_g':avg('protein_g'),'fat_g':avg('fat_g'),'carbs_g':avg('carbs_g')}) return result + + +@router.get("/import-history") +def import_history(x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)): + """Get import history by grouping entries by created timestamp.""" + pid = get_pid(x_profile_id) + with get_db() as conn: + cur = get_cursor(conn) + cur.execute(""" + SELECT + DATE(created) as import_date, + COUNT(*) as count, + MIN(date) as date_from, + MAX(date) as date_to, + MAX(created) as last_created + FROM nutrition_log + WHERE profile_id=%s AND source='csv' + GROUP BY DATE(created) + ORDER BY DATE(created) DESC + """, (pid,)) + return [r2d(r) for r in cur.fetchall()] + + +@router.put("/{entry_id}") +def update_nutrition(entry_id: str, kcal: float, protein_g: float, fat_g: float, carbs_g: float, + x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)): + """Update nutrition entry macros.""" + pid = get_pid(x_profile_id) + with get_db() as conn: + cur = get_cursor(conn) + # Verify ownership + cur.execute("SELECT id FROM nutrition_log WHERE id=%s AND profile_id=%s", (entry_id, pid)) + if not cur.fetchone(): + raise HTTPException(404, "Eintrag nicht gefunden") + + cur.execute(""" + UPDATE nutrition_log + SET kcal=%s, protein_g=%s, fat_g=%s, carbs_g=%s + WHERE id=%s AND profile_id=%s + """, (round(kcal,1), round(protein_g,1), round(fat_g,1), round(carbs_g,1), entry_id, pid)) + + return {"success": True} + + +@router.delete("/{entry_id}") +def delete_nutrition(entry_id: str, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)): + """Delete nutrition entry.""" + pid = get_pid(x_profile_id) + with get_db() as conn: + cur = get_cursor(conn) + # Verify ownership + cur.execute("SELECT id FROM nutrition_log WHERE id=%s AND profile_id=%s", (entry_id, pid)) + if not cur.fetchone(): + raise HTTPException(404, "Eintrag nicht gefunden") + + cur.execute("DELETE FROM nutrition_log WHERE id=%s AND profile_id=%s", (entry_id, pid)) + + return {"success": True} diff --git a/frontend/src/pages/NutritionPage.jsx b/frontend/src/pages/NutritionPage.jsx index c2531fa..7ab28e9 100644 --- a/frontend/src/pages/NutritionPage.jsx +++ b/frontend/src/pages/NutritionPage.jsx @@ -18,6 +18,220 @@ function rollingAvg(arr, key, window=7) { }) } +// ── Data Tab (Editable Entry List) ─────────────────────────────────────────── +function DataTab({ entries, onUpdate }) { + const [editId, setEditId] = useState(null) + const [editValues, setEditValues] = useState({}) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + + const startEdit = (e) => { + setEditId(e.id) + setEditValues({ + kcal: e.kcal || 0, + protein_g: e.protein_g || 0, + fat_g: e.fat_g || 0, + carbs_g: e.carbs_g || 0 + }) + } + + const cancelEdit = () => { + setEditId(null) + setEditValues({}) + setError(null) + } + + const saveEdit = async (id) => { + setSaving(true) + setError(null) + try { + await nutritionApi.updateNutrition( + id, + editValues.kcal, + editValues.protein_g, + editValues.fat_g, + editValues.carbs_g + ) + setEditId(null) + setEditValues({}) + onUpdate() + } catch(e) { + setError('Speichern fehlgeschlagen: ' + e.message) + } finally { + setSaving(false) + } + } + + const deleteEntry = async (id, date) => { + if (!confirm(`Eintrag vom ${dayjs(date).format('DD.MM.YYYY')} wirklich löschen?`)) return + try { + await nutritionApi.deleteNutrition(id) + onUpdate() + } catch(e) { + setError('Löschen fehlgeschlagen: ' + e.message) + } + } + + if (entries.length === 0) { + return ( +
Noch keine Ernährungsdaten. Importiere FDDB CSV oben.
+Noch keine Ernährungsdaten. Importiere FDDB CSV oben.
- )} - {entries.map((e, i) => ( -