import { useEffect, useMemo, useState } from "react"; import type { WorkspaceChartInterval, WorkspaceChartPoint, WorkspaceChartTimeRange, } from "../../../components/charts/WorkspaceChart"; import { authFetch } from "../../../lib/api/authFetch"; import { getBackendApiUrl } from "../../../lib/api/gatewayConfig"; import { readOptionalJsonResponse } from "../../../lib/api/readJsonResponse"; type HistorianPoint = { timestamp: string; numericValue: number | null; booleanValue: boolean | null; textValue: string | null; }; export function useClimateChartSeries( sensorKeys: string[], timeRange: WorkspaceChartTimeRange, interval: WorkspaceChartInterval, ) { const [seriesByKey, setSeriesByKey] = useState>({}); const [loading, setLoading] = useState(true); const [initialized, setInitialized] = useState(false); const keySignature = useMemo( () => sensorKeys.slice().sort().join(","), [sensorKeys], ); useEffect(() => { if (sensorKeys.length === 0) { setSeriesByKey({}); setLoading(false); return; } const controller = new AbortController(); async function loadHistory() { try { const to = new Date(); const from = new Date(to.getTime() - rangeToMs(timeRange)); if (!initialized) { setLoading(true); } const entries = await Promise.all( sensorKeys.map(async (key) => { const params = new URLSearchParams({ key, from: from.toISOString(), to: to.toISOString(), }); const response = await authFetch( getBackendApiUrl(`/api/historian/series?${params.toString()}`), { signal: controller.signal }, ); const payload = await readOptionalJsonResponse( response, `Failed to load climate history for ${key}`, [], ); const points = payload .filter( (point) => point.numericValue !== null && Number.isFinite(point.numericValue), ) .map((point) => ({ timestamp: point.timestamp, value: point.numericValue as number, })); return [key, points] as const; }), ); setSeriesByKey(Object.fromEntries(entries)); setInitialized(true); } catch (error) { if (controller.signal.aborted) return; console.error("Failed to load climate chart history", error); if (!initialized) { setSeriesByKey({}); } } finally { if (!controller.signal.aborted) { setLoading(false); } } } void loadHistory(); const intervalId = window.setInterval(() => { void loadHistory(); }, 10000); return () => { controller.abort(); window.clearInterval(intervalId); }; }, [keySignature, timeRange, interval, initialized]); return { seriesByKey, loading, }; } function rangeToMs(range: WorkspaceChartTimeRange) { switch (range) { case "15m": return 15 * 60 * 1000; case "1h": return 60 * 60 * 1000; case "6h": return 6 * 60 * 60 * 1000; case "24h": return 24 * 60 * 60 * 1000; case "7d": return 7 * 24 * 60 * 60 * 1000; case "30d": return 30 * 24 * 60 * 60 * 1000; } }