'use client'; import { useState, useEffect, useCallback } from 'react'; import Highcharts from 'highcharts'; import HighchartsReact from 'highcharts-react-official'; import type { Options } from 'highcharts'; import { WerteEntry } from '@/types/werte'; import TabLayout from '@/components/TabLayout'; const COLOR_ZUCKER = '#e67e22'; const COLOR_DRUCKS = '#c0392b'; const COLOR_DRUCKD = '#2980b9'; const COLOR_PULS = '#27ae60'; const COLOR_GEWICHT = '#8e44ad'; function getDefaultDates() { const to = new Date(); const from = new Date(); from.setDate(from.getDate() - 30); return { from: from.toISOString().split('T')[0], to: to.toISOString().split('T')[0], }; } function toTimestamp(datum: string, zeit: string): number { return new Date(`${datum}T${zeit}`).getTime(); } function hasValue(v: number | string | null | undefined): boolean { if (v == null || v === '') return false; const n = parseFloat(String(v)); return !isNaN(n) && n !== 0; } const baseChartOptions: Partial = { chart: { type: 'line', backgroundColor: '#F4F4F5', style: { fontFamily: 'Arial, Helvetica, sans-serif' }, height: 300, width: null, }, xAxis: { type: 'datetime', dateTimeLabelFormats: { day: '%d.%m.', week: '%d.%m.', month: '%m.%Y', }, title: { text: null }, labels: { style: { fontSize: '11px' } }, }, tooltip: { xDateFormat: '%d.%m.%Y %H:%M', shared: true, valueDecimals: 1, }, credits: { enabled: false }, legend: { enabled: true }, plotOptions: { line: { lineWidth: 2, marker: { enabled: true, radius: 2, symbol: 'circle' }, }, }, }; export default function ChartsClient() { const defaults = getDefaultDates(); const [from, setFrom] = useState(defaults.from); const [to, setTo] = useState(defaults.to); const [entries, setEntries] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async (fromDate: string, toDate: string) => { setIsLoading(true); setError(null); try { const res = await fetch(`/api/werte?from=${fromDate}&to=${toDate}`, { cache: 'no-store', headers: { 'Cache-Control': 'no-cache' }, }); const data = await res.json(); if (data.success) { setEntries(data.data); } else { setError('Fehler beim Laden der Daten.'); } } catch { setError('Netzwerkfehler.'); } finally { setIsLoading(false); } }, []); useEffect(() => { fetchData(from, to); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleApply = () => fetchData(from, to); // --- Build data series (sorted asc, null/zero values excluded) --- const zuckerData: [number, number][] = entries .filter(e => hasValue(e.Zucker)) .map(e => [toTimestamp(e.Datum, e.Zeit), parseFloat(String(e.Zucker))]); const druckSData: [number, number][] = entries .filter(e => hasValue(e.DruckS)) .map(e => [toTimestamp(e.Datum, e.Zeit), parseFloat(String(e.DruckS))]); const druckDData: [number, number][] = entries .filter(e => hasValue(e.DruckD)) .map(e => [toTimestamp(e.Datum, e.Zeit), parseFloat(String(e.DruckD))]); const pulsData: [number, number][] = entries .filter(e => hasValue(e.Puls)) .map(e => [toTimestamp(e.Datum, e.Zeit), parseFloat(String(e.Puls))]); const gewichtData: [number, number][] = entries .filter(e => hasValue(e.Gewicht)) .map(e => [toTimestamp(e.Datum, e.Zeit), parseFloat(String(e.Gewicht))]); // --- Chart options --- const zuckerOptions: Options = { ...baseChartOptions, title: { text: 'Blutzucker', style: { fontWeight: 'bold', fontSize: '15px' } }, yAxis: { title: { text: 'mmol/L' }, // min: 50, softMin: 100, softMax: 170, gridLineColor: '#ddd', }, series: [ { type: 'line', name: 'Zucker', data: zuckerData, color: COLOR_ZUCKER, }, ], } as Options; const druckOptions: Options = { ...baseChartOptions, title: { text: 'Blutdruck', style: { fontWeight: 'bold', fontSize: '15px' } }, yAxis: { title: { text: 'mmHg' }, softMin: 100, softMax: 160, gridLineColor: '#ddd', }, series: [ { type: 'line', name: 'Druck sys', data: druckSData, color: COLOR_DRUCKS, }, { type: 'line', name: 'Druck dia', data: druckDData, color: COLOR_DRUCKD, }, ], } as Options; const pulsOptions: Options = { ...baseChartOptions, title: { text: 'Puls', style: { fontWeight: 'bold', fontSize: '15px' } }, yAxis: { title: { text: 'bpm' }, softMin: 70, softMax: 90, gridLineColor: '#ddd', }, series: [ { type: 'line', name: 'Puls', data: pulsData, color: COLOR_PULS, }, ], } as Options; const gewichtOptions: Options = { ...baseChartOptions, title: { text: 'Gewicht', style: { fontWeight: 'bold', fontSize: '15px' } }, yAxis: { title: { text: 'kg' }, softMin: 70, softMax: 90, gridLineColor: '#ddd', }, series: [ { type: 'line', name: 'Gewicht', data: gewichtData, color: COLOR_GEWICHT, }, ], } as Options; return ( {/* Date range picker */}
setFrom(e.target.value)} className="border border-gray-400 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" />
setTo(e.target.value)} className="border border-gray-400 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" />
{error && {error}} {!isLoading && !error && entries.length === 0 && ( Keine Daten im gewählten Zeitraum. )}
{/* Charts */} {isLoading ? (
Lade Daten…
) : (
)}
); }