import { useEffect, useMemo, useState } from "react"; import { useHistoricalImport } from "../../hooks"; import type { DashboardConfig, HistoricalActivityEvent, HistoricalChunkProgress } from "../../types"; import { Badge } from "../common/Badge"; import { Card } from "../common/Card"; import { formatDate, formatDateTime, formatDurationShort, formatPercent, formatValue } from "../../lib/format"; interface HistoricalImportPanelProps { config: DashboardConfig; } function eventTone(level?: string): "ok" | "warn" | "critical" | "neutral" { if (level === "success") return "ok"; if (level === "warn") return "warn"; if (level === "error") return "critical"; return "neutral"; } function chunkTone(state?: string): "ok" | "warn" | "critical" | "neutral" { if (state === "completed") return "ok"; if (state === "running") return "warn"; if (state === "failed") return "critical"; return "neutral"; } function StatCard({ label, value, helper }: { label: string; value: string; helper: string }) { return
{label}
{value}
{helper}
; } function ChunkRow({ chunk, activeChunkIndex }: { chunk: HistoricalChunkProgress; activeChunkIndex: number }) { const isActive = chunk.chunk_index === activeChunkIndex || chunk.state === "running"; return (
Chunk {chunk.chunk_index}/{chunk.total_chunks}
{formatDate(chunk.start_date)} - {formatDate(chunk.end_date)}
{isActive ? aktywny : null}{chunk.state}
Przetworzone
{chunk.processed_days}
Import
{chunk.imported_days}
Pominiete
{chunk.skipped_days}
Energia
{formatValue(chunk.energy_kwh, "kWh", 2)}
{chunk.note}{chunk.duration_seconds ? `czas ${formatDurationShort(chunk.duration_seconds)}` : "w toku"}
); } function EventRow({ event }: { event: HistoricalActivityEvent }) { return (
{event.level}
{event.title}
{formatDateTime(event.timestamp)}
{event.message}
{event.day ? Dzien: {formatDate(event.day)} : null}{event.chunk_index ? Chunk: #{event.chunk_index} : null}
); } export function HistoricalImportPanel({ config }: HistoricalImportPanelProps) { const { status, start, syncNow, cancel } = useHistoricalImport(); const payload = status.data; const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); const [chunkDays, setChunkDays] = useState(String(config.capabilities.history.default_chunk_days || 7)); const [force, setForce] = useState(false); useEffect(() => { setChunkDays(String(config.capabilities.history.default_chunk_days || 7)); }, [config.capabilities.history.default_chunk_days]); const progress = useMemo(() => { if (!payload || payload.total_days <= 0) return 0; return Math.min(100, Math.round((payload.processed_days / payload.total_days) * 100)); }, [payload]); const visibleChunks = useMemo(() => [...(payload?.recent_chunks ?? [])].sort((l, r) => r.chunk_index - l.chunk_index), [payload?.recent_chunks]); const visibleEvents = useMemo(() => [...(payload?.recent_events ?? [])].sort((l, r) => new Date(r.timestamp).getTime() - new Date(l.timestamp).getTime()), [payload?.recent_events]); const busy = start.isPending || syncNow.isPending || cancel.isPending; const mutationError = start.error?.message || syncNow.error?.message || cancel.error?.message || null; const availableRangeReady = Boolean(payload?.available_start_date && payload?.available_end_date); return (

Gdy pola dat sa puste, backend sam wykryje zakres do importu na podstawie pierwszej probki w InfluxDB i ostatniego dnia juz zapisanego w SQLite.

{availableRangeReady ? : null}
{mutationError ?
{mutationError}
: null} {status.error ?
{status.error.message}
: null}
Operacyjny status zadania
{payload?.message || "Brak aktywnego zadania"}
{payload?.job_id ? job {payload.job_id} : null}{payload?.running ? "w trakcie" : payload?.state || "idle"}
Chunk {payload?.active_chunk_index ?? 0} / {payload?.total_chunks ?? 0}{progress}%
Dostepny zakres w InfluxDB
{formatDate(payload?.available_start_date)} - {formatDate(payload?.available_end_date)}
{payload?.coverage?.available_days ?? 0} dni wykrytego archiwum
Zakres zapisany lokalnie
{formatDate(payload?.coverage?.first_day)} - {formatDate(payload?.coverage?.last_day)}
{payload?.coverage?.imported_days ?? 0} dni w cache, {formatValue(payload?.coverage?.total_energy_kwh ?? 0, "kWh", 1)}
Aktualny chunk
{formatDate(payload?.current_chunk_start)} - {formatDate(payload?.current_chunk_end)}
Ostatni dzien: {formatDate(payload?.current_date)}
Czasy i opoznienia
elapsed {formatDurationShort(payload?.elapsed_seconds)}
start {formatDateTime(payload?.started_at)} / koniec {formatDateTime(payload?.finished_at)}
{payload?.last_error ?
Blad: {payload.last_error}
: null}
{visibleChunks.length ? visibleChunks.map((chunk) => ) :
Lista chunkow pojawi sie po uruchomieniu pierwszego backfillu.
}
{visibleEvents.length ? visibleEvents.map((event, index) => ) :
Historia operacji pojawi sie po starcie zadania.
}
); }