import { useState, useEffect, useRef } from 'react' import { Upload, Pencil, Trash2, Check, X, CheckCircle } from 'lucide-react' import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from 'recharts' import { api } from '../utils/api' import dayjs from 'dayjs' import 'dayjs/locale/de' dayjs.locale('de') const ACTIVITY_TYPES = [ 'Traditionelles Krafttraining','Matrial Arts','Outdoor Spaziergang', 'Innenräume Spaziergang','Laufen','Radfahren','Schwimmen', 'Cardio Dance','Geist & Körper','Sonstiges' ] function empty() { return { date: dayjs().format('YYYY-MM-DD'), activity_type: 'Traditionelles Krafttraining', duration_min: '', kcal_active: '', hr_avg: '', hr_max: '', rpe: '', notes: '' } } // ── Import Panel ────────────────────────────────────────────────────────────── function ImportPanel({ onImported }) { const fileRef = useRef() const [status, setStatus] = useState(null) const [error, setError] = useState(null) const [dragging, setDragging] = useState(false) const runImport = async (file) => { setStatus('loading'); setError(null) try { const result = await api.importActivityCsv(file) setStatus(result); onImported() } catch(err) { setError('Import fehlgeschlagen: ' + err.message); setStatus(null) } } return (
📥 Apple Health Import

Health Auto Export App → Workouts exportieren → CSV → hier hochladen.
Nur die Workouts-…csv Datei wird benötigt (nicht die Detaildateien).

{ const f=e.target.files[0]; if(f) runImport(f); e.target.value='' }}/>
{e.preventDefault();setDragging(true)}} onDragLeave={()=>setDragging(false)} onDrop={e=>{e.preventDefault();setDragging(false);const f=e.dataTransfer.files[0];if(f)runImport(f)}} onClick={()=>fileRef.current.click()} style={{border:`2px dashed ${dragging?'var(--accent)':'var(--border2)'}`,borderRadius:10, padding:'20px 16px',textAlign:'center',background:dragging?'var(--accent-light)':'var(--surface2)', cursor:'pointer',transition:'all 0.15s'}}>
{dragging?'Datei loslassen…':'CSV hierher ziehen oder tippen'}
{status==='loading' && (
Importiere…
)} {error &&
{error}
} {status && status!=='loading' && (
Import erfolgreich
{status.inserted} Trainings importiert · {status.skipped} übersprungen
)}
) } // ── Manual Entry ────────────────────────────────────────────────────────────── function EntryForm({ form, setForm, onSave, onCancel, saveLabel='Speichern' }) { const set = (k,v) => setForm(f=>({...f,[k]:v})) return (
set('date',e.target.value)}/>
set('duration_min',e.target.value)}/> Min
set('kcal_active',e.target.value)}/> kcal
set('hr_avg',e.target.value)}/> bpm
set('hr_max',e.target.value)}/> bpm
set('rpe',e.target.value)}/> RPE
set('notes',e.target.value)}/>
{onCancel && }
) } // ── Main Page ───────────────────────────────────────────────────────────────── export default function ActivityPage() { const [entries, setEntries] = useState([]) const [stats, setStats] = useState(null) const [tab, setTab] = useState('list') const [form, setForm] = useState(empty()) const [editing, setEditing] = useState(null) const [saved, setSaved] = useState(false) const load = async () => { const [e, s] = await Promise.all([api.listActivity(), api.activityStats()]) setEntries(e); setStats(s) } useEffect(()=>{ load() },[]) const handleSave = async () => { const payload = {...form} if(payload.duration_min) payload.duration_min = parseFloat(payload.duration_min) if(payload.kcal_active) payload.kcal_active = parseFloat(payload.kcal_active) if(payload.hr_avg) payload.hr_avg = parseFloat(payload.hr_avg) if(payload.hr_max) payload.hr_max = parseFloat(payload.hr_max) if(payload.rpe) payload.rpe = parseInt(payload.rpe) payload.source = 'manual' await api.createActivity(payload) setSaved(true); await load() setTimeout(()=>{ setSaved(false); setForm(empty()) }, 1500) } const handleUpdate = async () => { const payload = {...editing} await api.updateActivity(editing.id, payload) setEditing(null); await load() } const handleDelete = async (id) => { if(!confirm('Training löschen?')) return await api.deleteActivity(id); await load() } // Chart data: kcal per day (last 30 days) const chartData = (() => { const byDate = {} entries.forEach(e=>{ byDate[e.date] = (byDate[e.date]||0) + (e.kcal_active||0) }) return Object.entries(byDate).sort((a,b)=>a[0].localeCompare(b[0])).slice(-30).map(([date,kcal])=>({ date: dayjs(date).format('DD.MM'), kcal: Math.round(kcal) })) })() const TYPE_COLORS = { 'Traditionelles Krafttraining':'#1D9E75','Matrial Arts':'#D85A30', 'Outdoor Spaziergang':'#378ADD','Innenräume Spaziergang':'#7F77DD', 'Laufen':'#EF9F27','Radfahren':'#D4537E','Sonstiges':'#888780' } return (

Aktivität

{/* Übersicht */} {stats && stats.count>0 && (
{[['Trainings',stats.count,'var(--text1)'], ['Kcal gesamt',Math.round(stats.total_kcal),'#EF9F27'], ['Stunden',Math.round(stats.total_min/60*10)/10,'#378ADD']].map(([l,v,c])=>(
{v}
{l}
))}
)} {tab==='import' && } {tab==='add' && (
Training eintragen
)} {tab==='stats' && stats && (
{chartData.length>=2 && (
Aktive Kalorien pro Tag
[`${v} kcal`,'Aktiv']}/>
)}
Nach Trainingsart
{Object.entries(stats.by_type).sort((a,b)=>b[1].kcal-a[1].kcal).map(([type,data])=>(
{type}
{data.count}× · {Math.round(data.min)} Min · {Math.round(data.kcal)} kcal
))}
)} {tab==='list' && (
{entries.length===0 && (

Keine Trainings

Importiere deine Apple Health Daten oder trage manuell ein.

)} {entries.map(e=>{ const isEd = editing?.id===e.id const color = TYPE_COLORS[e.activity_type]||'#888' return (
{isEd ? ( setEditing(null)} saveLabel="Speichern"/> ) : (
{e.activity_type}
{dayjs(e.date).format('dd, DD. MMMM YYYY')} {e.start_time && e.start_time.length>10 && ` · ${e.start_time.slice(11,16)}`}
{e.duration_min && ⏱ {Math.round(e.duration_min)} Min} {e.kcal_active && 🔥 {Math.round(e.kcal_active)} kcal} {e.hr_avg && ❤️ Ø{Math.round(e.hr_avg)} bpm} {e.hr_max && ↑{Math.round(e.hr_max)} bpm} {e.distance_km && e.distance_km>0 && 📍 {Math.round(e.distance_km*10)/10} km} {e.rpe && RPE {e.rpe}/10} {e.source==='apple_health' && Apple Health}
{e.notes &&

"{e.notes}"

}
)}
) })}
)}
) }