a9fbf25d9f
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import {
|
|
LineChart,
|
|
Line,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
ResponsiveContainer,
|
|
} from 'recharts';
|
|
|
|
type Measurement = {
|
|
timestamp: number;
|
|
soil_moisture: number;
|
|
};
|
|
|
|
function formatTime(ts: number): string {
|
|
return new Date(ts).toLocaleTimeString('de-DE', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
}
|
|
|
|
export default function Home() {
|
|
const [data, setData] = useState<Measurement[]>([]);
|
|
const [lastUpdate, setLastUpdate] = useState<string>('');
|
|
|
|
const fetchData = async () => {
|
|
const res = await fetch('/bodenfeuchte/api/data');
|
|
const json: Measurement[] = await res.json();
|
|
setData(json);
|
|
setLastUpdate(new Date().toLocaleTimeString('de-DE'));
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
const interval = setInterval(fetchData, 60_000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const latest = data.at(-1);
|
|
|
|
return (
|
|
<main className="min-h-screen bg-gray-950 text-gray-100 p-8">
|
|
<h1 className="text-2xl font-semibold mb-1">Bodenfeuchte</h1>
|
|
<p className="text-sm text-gray-400 mb-6">
|
|
Letzte 6 Stunden · Aktualisierung: {lastUpdate || '…'}
|
|
</p>
|
|
|
|
{latest && (
|
|
<div className="mb-8 inline-block bg-gray-800 rounded-xl px-6 py-4">
|
|
<span className="text-5xl font-bold text-green-400">
|
|
{latest.soil_moisture.toFixed(1)}
|
|
</span>
|
|
<span className="text-2xl text-gray-400 ml-2">%</span>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
{formatTime(latest.timestamp)} Uhr
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="bg-gray-900 rounded-2xl p-4" style={{ height: 360 }}>
|
|
{data.length === 0 ? (
|
|
<div className="flex items-center justify-center h-full text-gray-500">
|
|
Noch keine Daten vorhanden
|
|
</div>
|
|
) : (
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<LineChart data={data} margin={{ top: 8, right: 16, bottom: 8, left: 0 }}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
|
|
<XAxis
|
|
dataKey="timestamp"
|
|
tickFormatter={formatTime}
|
|
stroke="#6b7280"
|
|
tick={{ fontSize: 12 }}
|
|
minTickGap={40}
|
|
/>
|
|
<YAxis
|
|
domain={[0, 100]}
|
|
unit="%"
|
|
stroke="#6b7280"
|
|
tick={{ fontSize: 12 }}
|
|
width={45}
|
|
/>
|
|
<Tooltip
|
|
formatter={(v) => [`${Number(v).toFixed(1)} %`, 'Bodenfeuchte']}
|
|
labelFormatter={(ts) => formatTime(Number(ts))}
|
|
contentStyle={{ background: '#1f2937', border: 'none', borderRadius: 8 }}
|
|
/>
|
|
<Line
|
|
type="monotone"
|
|
dataKey="soil_moisture"
|
|
stroke="#4ade80"
|
|
strokeWidth={2}
|
|
dot={false}
|
|
activeDot={{ r: 4 }}
|
|
/>
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
)}
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|