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
;
}
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 (
{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.
}
);
}