114 lines
3.5 KiB
TypeScript
Executable File
114 lines
3.5 KiB
TypeScript
Executable File
import { useState, useEffect } from 'react';
|
|
import {
|
|
LineChart,
|
|
Line,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
ResponsiveContainer,
|
|
} from 'recharts';
|
|
import { api } from '../../api/client';
|
|
import { useExerciseStore } from '../../stores/exerciseStore';
|
|
import type { SessionLog } from '../../types';
|
|
|
|
export function ExerciseChart() {
|
|
const { exercises, fetchExercises } = useExerciseStore();
|
|
const [selectedId, setSelectedId] = useState<number | null>(null);
|
|
const [chartData, setChartData] = useState<{ date: string; weight: number }[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetchExercises();
|
|
}, [fetchExercises]);
|
|
|
|
useEffect(() => {
|
|
if (!selectedId) {
|
|
setChartData([]);
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
api.exercises
|
|
.history(selectedId, 50)
|
|
.then((logs: SessionLog[]) => {
|
|
// Gruppiere nach Datum, nehme max Gewicht pro Tag
|
|
const byDate = new Map<string, number>();
|
|
for (const log of logs) {
|
|
const date = new Date(log.logged_at).toLocaleDateString('de-DE', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
});
|
|
const current = byDate.get(date) || 0;
|
|
if (log.weight_kg > current) {
|
|
byDate.set(date, log.weight_kg);
|
|
}
|
|
}
|
|
const data = Array.from(byDate.entries())
|
|
.map(([date, weight]) => ({ date, weight }))
|
|
.reverse();
|
|
setChartData(data);
|
|
})
|
|
.catch(() => setChartData([]))
|
|
.finally(() => setLoading(false));
|
|
}, [selectedId]);
|
|
|
|
return (
|
|
<div className="bg-gray-900 border border-gray-800 rounded-xl p-4">
|
|
<h3 className="text-lg font-semibold text-gray-100 mb-3">Gewichtsverlauf</h3>
|
|
|
|
<select
|
|
value={selectedId ?? ''}
|
|
onChange={(e) =>
|
|
setSelectedId(e.target.value ? parseInt(e.target.value) : null)
|
|
}
|
|
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-gray-100 focus:outline-none focus:border-blue-500 min-h-[44px] mb-4"
|
|
>
|
|
<option value="">Übung auswählen...</option>
|
|
{exercises.map((ex) => (
|
|
<option key={ex.id} value={ex.id}>
|
|
{ex.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
|
|
{loading ? (
|
|
<div className="text-center text-gray-500 py-8">Laden...</div>
|
|
) : chartData.length === 0 ? (
|
|
<div className="text-center text-gray-500 py-8">
|
|
{selectedId
|
|
? 'Keine Daten vorhanden'
|
|
: 'Wähle eine Übung aus'}
|
|
</div>
|
|
) : (
|
|
<div className="h-64">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<LineChart data={chartData}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
|
|
<XAxis dataKey="date" stroke="#9CA3AF" fontSize={12} />
|
|
<YAxis stroke="#9CA3AF" fontSize={12} unit=" kg" />
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: '#1F2937',
|
|
border: '1px solid #374151',
|
|
borderRadius: '8px',
|
|
color: '#F3F4F6',
|
|
}}
|
|
formatter={(value) => [`${value} kg`, 'Max. Gewicht']}
|
|
/>
|
|
<Line
|
|
type="monotone"
|
|
dataKey="weight"
|
|
stroke="#3B82F6"
|
|
strokeWidth={2}
|
|
dot={{ fill: '#3B82F6', r: 4 }}
|
|
activeDot={{ r: 6 }}
|
|
/>
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|