import { useState, useEffect, useRef } from 'react'
import { Upload, CheckCircle, TrendingUp, Info } from 'lucide-react'
import {
LineChart, Line, BarChart, Bar, XAxis, YAxis, Tooltip,
ResponsiveContainer, CartesianGrid, Legend, ReferenceLine, ScatterChart, Scatter
} from 'recharts'
import { api as nutritionApi } from '../utils/api'
import dayjs from 'dayjs'
import isoWeek from 'dayjs/plugin/isoWeek'
dayjs.extend(isoWeek)
// ── Helpers ───────────────────────────────────────────────────────────────────
const KCAL_PER_KG_FAT = 7700
function rollingAvg(arr, key, window=7) {
return arr.map((d,i) => {
const slice = arr.slice(Math.max(0,i-window+1),i+1).map(x=>x[key]).filter(v=>v!=null)
return slice.length ? {...d, [`${key}_avg`]: Math.round(slice.reduce((a,b)=>a+b,0)/slice.length*10)/10} : d
})
}
// ── 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 [filter, setFilter] = useState('30') // days to show (7, 30, 90, 'all')
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)
}
}
// Filter entries by date range
const filteredEntries = filter === 'all'
? entries
: entries.filter(e => {
const daysDiff = dayjs().diff(dayjs(e.date), 'day')
return daysDiff <= parseInt(filter)
})
if (entries.length === 0) {
return (
Alle Einträge (0)
Noch keine Ernährungsdaten. Importiere FDDB CSV oben.
)
}
return (
Alle Einträge ({filteredEntries.length}{filteredEntries.length !== entries.length ? ` von ${entries.length}` : ''})
{error && (
{error}
)}
{filteredEntries.map((e, i) => {
const isEditing = editId === e.id
return (
{!isEditing ? (
<>
{dayjs(e.date).format('dd, DD. MMMM YYYY')}
{Math.round(e.kcal || 0)} kcal
🥩 Protein: {Math.round(e.protein_g || 0)}g
🫙 Fett: {Math.round(e.fat_g || 0)}g
🍞 Kohlenhydrate: {Math.round(e.carbs_g || 0)}g
{e.source && (
Quelle: {e.source}
)}
>
) : (
<>
{dayjs(e.date).format('dd, DD. MMMM YYYY')}
>
)}
)
})}
)
}
// ── Import History ────────────────────────────────────────────────────────────
function ImportHistory() {
const [history, setHistory] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const load = async () => {
try {
const data = await nutritionApi.nutritionImportHistory()
setHistory(Array.isArray(data) ? data : [])
} catch(e) {
console.error('Failed to load import history:', e)
} finally {
setLoading(false)
}
}
load()
}, [])
if (loading) return null
if (!history.length) return null
return (
Import-Historie
{history.map((h, i) => (
{dayjs(h.import_date).format('DD.MM.YYYY')}
{dayjs(h.last_created).format('HH:mm')} Uhr
{h.count} {h.count === 1 ? 'Eintrag' : 'Einträge'}
{h.date_from && h.date_to && (
({dayjs(h.date_from).format('DD.MM.YY')} – {dayjs(h.date_to).format('DD.MM.YY')})
)}
))}
)
}
// ── Import Panel ──────────────────────────────────────────────────────────────
function ImportPanel({ onImported }) {
const fileRef = useRef()
const [status, setStatus] = useState(null)
const [error, setError] = useState(null)
const [dragging,setDragging]= useState(false)
const [tab, setTab] = useState('file') // 'file' | 'paste'
const [pasteText, setPasteText] = useState('')
const runImport = async (file) => {
setStatus('loading'); setError(null)
try {
const result = await nutritionApi.importCsv(file)
if (result.days_imported === undefined) throw new Error(JSON.stringify(result))
setStatus(result)
onImported()
} catch(err) {
setError('Import fehlgeschlagen: ' + err.message)
setStatus(null)
}
}
const handleFile = async e => {
const file = e.target.files[0]; if (!file) return
await runImport(file)
e.target.value = ''
}
const handleDrop = async e => {
e.preventDefault(); setDragging(false)
const file = e.dataTransfer.files[0]
if (!file) return
await runImport(file)
}
const handlePasteImport = async () => {
if (!pasteText.trim()) return
const blob = new Blob([pasteText], { type: 'text/csv' })
const file = new File([blob], 'paste.csv', { type: 'text/csv' })
await runImport(file)
}
return (
📥 FDDB CSV Import
In FDDB: Mein Tagebuch → Exportieren → CSV — dann hier importieren.
{/* Tab switcher */}
{[['file','📁 Datei / Drag & Drop'],['paste','📋 Text einfügen']].map(([k,l])=>(
))}
{tab==='file' && (
<>
{/* Drag & Drop Zone */}
{e.preventDefault();setDragging(true)}}
onDragLeave={()=>setDragging(false)}
onDrop={handleDrop}
onClick={()=>fileRef.current.click()}
style={{
border:`2px dashed ${dragging?'var(--accent)':'var(--border2)'}`,
borderRadius:10, padding:'24px 16px', textAlign:'center',
background: dragging?'var(--accent-light)':'var(--surface2)',
cursor:'pointer', transition:'all 0.15s',
}}>
{dragging ? 'Datei loslassen…' : 'CSV hierher ziehen oder tippen zum Auswählen'}
.csv Dateien
>
)}
{tab==='paste' && (
<>