E-Auto-Lade-Erkennung im 31d-Diagramm
Markiert im 31d-Diagramm die Tage rot, an denen das E-Auto geladen wurde: Leistung (Feld 'leistung', W) lag laenger als LADEN_MIN_HOURS (Default 2h) ununterbrochen ueber LADEN_THRESHOLD_KW (Default 11 kW). - Erkennung serverseitig per Flux stateDuration auf den Sekundendaten (24h Vorlauf fuer ueber Mitternacht laufende Ladungen an der Monatsgrenze) - /api/laden?month=YYYY-MM, Ergebnis gecacht (abgeschlossene Monate dauerhaft, laufender Monat 5 min) -> ~15s nur beim ersten Aufruf - Markierung wird asynchron nachgeladen, Balken erscheinen sofort - Schwelle/Dauer per Env (LADEN_THRESHOLD_KW, LADEN_MIN_HOURS) einstellbar - BarChart: optionales highlight-Set faerbt passende Balken Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,32 @@ function ChartCard({ range, every, title, color, selector }: ChartCardProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// E-Auto-Ladetage (nur 31d): werden asynchron nachgeladen
|
||||
const [ladeDays, setLadeDays] = useState<Set<number>>(new Set());
|
||||
const [ladeLoading, setLadeLoading] = useState(false);
|
||||
const [ladeMeta, setLadeMeta] = useState<{ thresholdKw: number; minHours: number } | null>(null);
|
||||
|
||||
const fetchLaden = useCallback(
|
||||
async (sel: string) => {
|
||||
if (range !== '31d') return;
|
||||
setLadeLoading(true);
|
||||
setLadeDays(new Set());
|
||||
try {
|
||||
const res = await fetch(`/api/laden?month=${sel}`, { cache: 'no-store' });
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
setLadeDays(new Set<number>(json.days));
|
||||
setLadeMeta({ thresholdKw: json.thresholdKw, minHours: json.minHours });
|
||||
}
|
||||
} catch {
|
||||
// Markierung ist sekundaer -> Fehler still ignorieren
|
||||
} finally {
|
||||
setLadeLoading(false);
|
||||
}
|
||||
},
|
||||
[range]
|
||||
);
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (sel: string) => {
|
||||
setIsLoading(true);
|
||||
@@ -83,9 +109,15 @@ function ChartCard({ range, every, title, color, selector }: ChartCardProps) {
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(value);
|
||||
fetchLaden(value);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleApply = () => {
|
||||
fetchData(value);
|
||||
fetchLaden(value);
|
||||
};
|
||||
|
||||
// Jahres-Chart: Achse immer ueber das komplette Kalenderjahr -> alle 12 Monate
|
||||
// sichtbar, Restmonate bleiben ohne Balken.
|
||||
let xMin: number | undefined;
|
||||
@@ -138,7 +170,7 @@ function ChartCard({ range, every, title, color, selector }: ChartCardProps) {
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => fetchData(value)}
|
||||
onClick={handleApply}
|
||||
disabled={isLoading}
|
||||
className="bg-[#85B7D7] hover:bg-[#6a9fc5] px-5 py-2 text-sm rounded-lg transition-colors shadow-md disabled:opacity-50"
|
||||
>
|
||||
@@ -156,13 +188,36 @@ function ChartCard({ range, every, title, color, selector }: ChartCardProps) {
|
||||
<div className="text-center py-16 text-gray-500">Lade Daten…</div>
|
||||
) : (
|
||||
<>
|
||||
<BarChart title={title} data={data} every={every} color={color} xMin={xMin} xMax={xMax} />
|
||||
{data.length > 0 && (
|
||||
<div className="text-right text-sm font-semibold mt-2 px-2">
|
||||
{totalLabel}:{' '}
|
||||
{total.toLocaleString('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 1 })} kWh
|
||||
</div>
|
||||
)}
|
||||
<BarChart
|
||||
title={title}
|
||||
data={data}
|
||||
every={every}
|
||||
color={color}
|
||||
xMin={xMin}
|
||||
xMax={xMax}
|
||||
highlight={range === '31d' ? ladeDays : undefined}
|
||||
/>
|
||||
<div className="flex justify-between items-center mt-2 px-2 text-sm">
|
||||
{/* Legende: E-Auto-Ladetage (nur 31d) */}
|
||||
{range === '31d' ? (
|
||||
<span className="text-gray-600 flex items-center gap-1.5">
|
||||
<span className="inline-block w-3 h-3 rounded-sm" style={{ backgroundColor: '#c0392b' }} />
|
||||
{ladeLoading
|
||||
? 'E-Auto-Ladung wird geprüft…'
|
||||
: ladeDays.size > 0
|
||||
? `E-Auto geladen (> ${ladeMeta?.thresholdKw ?? ''} kW, ≥ ${ladeMeta?.minHours ?? ''} h)`
|
||||
: 'Keine E-Auto-Ladung erkannt'}
|
||||
</span>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{data.length > 0 && (
|
||||
<span className="font-semibold whitespace-nowrap">
|
||||
{totalLabel}:{' '}
|
||||
{total.toLocaleString('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 1 })} kWh
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user