From 293f32cff11f53e9b5b512ea4d3ddb7143e3553f Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Thu, 11 Jun 2026 14:23:23 +0200 Subject: [PATCH] =?UTF-8?q?v=C3=BDkon:=20dashboard=20ve=202=20vln=C3=A1ch?= =?UTF-8?q?=20(status=20hned,=20pl=C3=A1n/telemetrie=20async),=20stale=20d?= =?UTF-8?q?ata=20bez=20blik=C3=A1n=C3=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/hooks/useDashboardData.ts | 42 +++++++++++++++++--------- frontend/src/pages/Dashboard.tsx | 4 +-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/frontend/src/hooks/useDashboardData.ts b/frontend/src/hooks/useDashboardData.ts index c2f585e..06fff85 100644 --- a/frontend/src/hooks/useDashboardData.ts +++ b/frontend/src/hooks/useDashboardData.ts @@ -169,6 +169,7 @@ export function useDashboardData(siteId: number | null) { const [negPrices, setNegPrices] = useState([]) const [error, setError] = useState(null) const [ready, setReady] = useState(false) + const [slotsReady, setSlotsReady] = useState(false) const wsRef = useRef(null) const siteIdRef = useRef(siteId) @@ -182,27 +183,40 @@ export function useDashboardData(siteId: number | null) { setLiveMetrics(null) setError(null) setReady(true) + setSlotsReady(true) return } const windowStart = floorSlotUtcMs(Date.now()) - SLOT_COUNT_BACK * SLOT_MS const nIdx = currentSlotIndexInWindow(windowStart) + // Vlna 1 — kritická: vw_site_status (+ TČ) je rychlé, UI se odemkne hned. + let status: SiteStatusRow | null = null + try { + const [statusArr, hpArr] = await Promise.all([ + getJson('/vw_site_status', { site_id: `eq.${siteId}` }), + getJson('/vw_latest_heat_pump', { site_id: `eq.${siteId}` }), + ]) + status = Array.isArray(statusArr) && statusArr[0] ? statusArr[0]! : null + const hp = Array.isArray(hpArr) && hpArr[0] ? hpArr[0]! : null + setLiveMetrics(buildLiveMetrics(status, hp)) + setError(null) + } catch (e) { + setError(e instanceof Error ? e.message : 'Chyba načítání dashboardu') + } finally { + setReady(true) + } + + // Vlna 2 — extended: plán, telemetrie, audit, ceny. Při refetchi zůstávají + // zobrazená stale data (sloty se přepíšou až novými daty, žádné blikání). try { const todayPrague = pragueCalendarDay() - const dates = new Set() - for (let i = 0; i < TOTAL_SLOTS; i++) { - const ms = windowStart + i * SLOT_MS - dates.add(pragueCalendarDay(new Date(ms))) - } const [ planMaybe, - statusArr, telemetry15m7d, auditHourly, modeLog, - hpArr, priceRows, ] = await Promise.all([ getCurrentPlan(siteId).catch((e: unknown) => { @@ -211,7 +225,6 @@ export function useDashboardData(siteId: number | null) { } throw e }), - getJson('/vw_site_status', { site_id: `eq.${siteId}` }), getJson('/vw_telemetry_15m_7d', { site_id: `eq.${siteId}`, slot_start: `gte.${new Date(windowStart).toISOString()}`, @@ -227,7 +240,6 @@ export function useDashboardData(siteId: number | null) { order: 'activated_at.asc', limit: '200', }), - getJson('/vw_latest_heat_pump', { site_id: `eq.${siteId}` }), // Ceny bereme přes FastAPI range endpoint (PostgREST /rest je u vás chráněné → 401). getSitePricesSlotsRange( siteId, @@ -236,10 +248,6 @@ export function useDashboardData(siteId: number | null) { ), ]) - const status = Array.isArray(statusArr) && statusArr[0] ? statusArr[0]! : null - const hp = Array.isArray(hpArr) && hpArr[0] ? hpArr[0]! : null - setLiveMetrics(buildLiveMetrics(status, hp)) - const plan = planMaybe as { intervals: PlanningIntervalDto[] } const planBySlot = new Map() for (const iv of plan.intervals) { @@ -403,13 +411,16 @@ export function useDashboardData(siteId: number | null) { setError(null) } catch (e) { setError(e instanceof Error ? e.message : 'Chyba načítání dashboardu') - setSlots([]) + // Sloty neměnit — během chyby refetche zůstávají zobrazená poslední data. } finally { - setReady(true) + setSlotsReady(true) } }, [siteId]) useEffect(() => { + // Změna lokality: data staré site nesmí zůstat zobrazená. + setSlots([]) + setSlotsReady(false) void load() const id = window.setInterval(() => void load(), POLL_FULL_MS) return () => window.clearInterval(id) @@ -526,6 +537,7 @@ export function useDashboardData(siteId: number | null) { negPrices, error, ready, + slotsReady, reload: load, liveMetrics, buyNow, diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 85e14fd..b213201 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -315,7 +315,7 @@ export function Dashboard() {
- {data.slots.length === 0 && data.ready ? ( + {data.slots.length === 0 && data.slotsReady ? (
Nedostatek dat pro graf (zkontrolujte plán a telemetrii).
@@ -348,7 +348,7 @@ export function Dashboard() { )}
- {data.slots.length > 0 && data.ready ? ( + {data.slots.length > 0 && data.slotsReady ? (