Initial commit
Made-with: Cursor
This commit is contained in:
45
frontend/src/hooks/useAuditDailyToday.ts
Normal file
45
frontend/src/hooks/useAuditDailyToday.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { getJson } from '../api/postgrest'
|
||||
import { instantPragueDay, pragueCalendarDay } from '../lib/pragueDate'
|
||||
import type { AuditDailyRow } from '../types/ems'
|
||||
|
||||
const POLL_MS = 30_000
|
||||
|
||||
export function useAuditDailyToday(siteId: number | null) {
|
||||
const [row, setRow] = useState<AuditDailyRow | null>(null)
|
||||
const [ready, setReady] = useState(false)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (siteId == null) {
|
||||
setRow(null)
|
||||
setReady(true)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const rows = await getJson<AuditDailyRow[]>('/vw_audit_daily', {
|
||||
site_id: `eq.${siteId}`,
|
||||
order: 'day_local.desc',
|
||||
limit: '45',
|
||||
})
|
||||
const today = pragueCalendarDay()
|
||||
const hit = Array.isArray(rows) ? rows.find((r) => instantPragueDay(r.day_local) === today) : undefined
|
||||
setRow(hit ?? null)
|
||||
} catch {
|
||||
setRow(null)
|
||||
} finally {
|
||||
setReady(true)
|
||||
}
|
||||
}, [siteId])
|
||||
|
||||
useEffect(() => {
|
||||
void load()
|
||||
const id = window.setInterval(() => void load(), POLL_MS)
|
||||
return () => window.clearInterval(id)
|
||||
}, [load])
|
||||
|
||||
return {
|
||||
daily: row,
|
||||
ready,
|
||||
hasDaily: row != null && (row.interval_count ?? 0) > 0,
|
||||
}
|
||||
}
|
||||
42
frontend/src/hooks/useSiteStatus.ts
Normal file
42
frontend/src/hooks/useSiteStatus.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { getJson } from '../api/postgrest'
|
||||
import type { SiteStatusRow } from '../types/ems'
|
||||
|
||||
const POLL_MS = 5_000
|
||||
|
||||
export function useSiteStatus() {
|
||||
const [row, setRow] = useState<SiteStatusRow | null>(null)
|
||||
const [ready, setReady] = useState(false)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const rows = await getJson<SiteStatusRow[]>('/vw_site_status')
|
||||
setRow(Array.isArray(rows) && rows.length > 0 ? rows[0]! : null)
|
||||
} catch {
|
||||
setRow(null)
|
||||
} finally {
|
||||
setReady(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
void load()
|
||||
const id = window.setInterval(() => void load(), POLL_MS)
|
||||
return () => window.clearInterval(id)
|
||||
}, [load])
|
||||
|
||||
const hasTelemetry =
|
||||
row != null &&
|
||||
(row.pv_power_w != null ||
|
||||
row.battery_power_w != null ||
|
||||
row.grid_power_w != null ||
|
||||
row.battery_soc_percent != null)
|
||||
|
||||
return {
|
||||
site: row,
|
||||
ready,
|
||||
/** Máme řádek lokality a alespoň jednu telemetrickou hodnotu (jinak skeleton). */
|
||||
hasLiveData: row != null && hasTelemetry,
|
||||
reload: load,
|
||||
}
|
||||
}
|
||||
72
frontend/src/hooks/useTelemetryToday.ts
Normal file
72
frontend/src/hooks/useTelemetryToday.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { getJson } from '../api/postgrest'
|
||||
import type { AuditTodayHourlyRow } from '../types/ems'
|
||||
|
||||
const POLL_MS = 30_000
|
||||
|
||||
function parseNum(v: string | number | null | undefined): number | null {
|
||||
if (v == null) return null
|
||||
if (typeof v === 'number' && !Number.isNaN(v)) return v
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
|
||||
export type TelemetryChartPoint = {
|
||||
timeLabel: string
|
||||
ts: number
|
||||
pv_kw: number | null
|
||||
load_kw: number | null
|
||||
battery_kw: number | null
|
||||
grid_kw: number | null
|
||||
}
|
||||
|
||||
export function useTelemetryToday(siteId: number | null) {
|
||||
const [points, setPoints] = useState<TelemetryChartPoint[]>([])
|
||||
const [ready, setReady] = useState(false)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (siteId == null) {
|
||||
setPoints([])
|
||||
setReady(true)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const rows = await getJson<AuditTodayHourlyRow[]>('/vw_audit_today_hourly', {
|
||||
site_id: `eq.${siteId}`,
|
||||
order: 'hour_local.asc',
|
||||
})
|
||||
if (!Array.isArray(rows) || rows.length === 0) {
|
||||
setPoints([])
|
||||
return
|
||||
}
|
||||
const mapped: TelemetryChartPoint[] = rows.map((r) => {
|
||||
const d = new Date(r.hour_local)
|
||||
return {
|
||||
ts: d.getTime(),
|
||||
timeLabel: d.toLocaleTimeString('cs-CZ', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Europe/Prague',
|
||||
}),
|
||||
pv_kw: parseNum(r.avg_pv_kw),
|
||||
load_kw: parseNum(r.avg_load_kw),
|
||||
battery_kw: parseNum(r.avg_battery_kw),
|
||||
grid_kw: parseNum(r.avg_grid_kw),
|
||||
}
|
||||
})
|
||||
setPoints(mapped)
|
||||
} catch {
|
||||
setPoints([])
|
||||
} finally {
|
||||
setReady(true)
|
||||
}
|
||||
}, [siteId])
|
||||
|
||||
useEffect(() => {
|
||||
void load()
|
||||
const id = window.setInterval(() => void load(), POLL_MS)
|
||||
return () => window.clearInterval(id)
|
||||
}, [load])
|
||||
|
||||
return { points, ready, hasChartData: points.length > 0 }
|
||||
}
|
||||
Reference in New Issue
Block a user