import { useState, type ReactNode } from "react"; import { openUrl } from "@tauri-apps/plugin-opener"; import { useDashboard, type DashboardRange } from "../../hooks/useDashboard"; import { useTranslation } from "../../i18n"; import { fmtInt, fmtPct, fmtTime, fmtTokens, fmtUsd } from "../../lib/dashboard-format"; import { DailyBars } from "./DailyBars"; import { Heatmap } from "./Heatmap"; import { TrendLineChart } from "./TrendLineChart"; const RANGES: DashboardRange[] = ["today", "7d", "30d", "all"]; function Card({ title, subtitle, children, className, }: { title: string; subtitle?: string; children: ReactNode; className?: string; }) { return (

{title}

{subtitle && {subtitle}}
{children}
); } function KpiCard({ label, value, sub }: { label: string; value: string; sub?: string }) { return (
{label}
{value}
{sub &&
{sub}
}
); } export function DashboardPage() { const { t } = useTranslation(); const { range, changeRange, data, loading, error, refresh } = useDashboard(); const [showAllModels, setShowAllModels] = useState(false); const [recentPage, setRecentPage] = useState(1); const [trendTab, setTrendTab] = useState<"daily" | "model" | "provider">("daily"); const RANGE_LABELS: Record = { today: t("rangeToday"), "7d": t("range7d"), "30d": t("range30d"), all: t("rangeAll"), }; if (loading && !data) { return (
); } if (error) { return (
{t("dashLoadFailed")}
{error}
); } if (!data) return null; const totals = data.stats.totals; const daily = data.stats.daily; const models = data.stats.models; const recent = data.stats.recent; const heatmap = data.heatmap; const rangeTotals = data.rangeTotals; const visibleModels = showAllModels ? models : models.slice(0, 8); // Recent pagination const RECENT_PAGE_SIZE = 30; const recentTotal = data.stats.recentTotal; const recentTotalPages = Math.max(1, Math.ceil(recent.length / RECENT_PAGE_SIZE)); const recentCurrentPage = Math.min(recentPage, recentTotalPages); const recentStart = (recentCurrentPage - 1) * RECENT_PAGE_SIZE; const recentPageRows = recent.slice(recentStart, recentStart + RECENT_PAGE_SIZE); // Ordered model names for chart colour mapping const modelNames = models.map((m) => m.model); const trendDaily = daily.length > 30 ? daily.slice(-30) : daily; const providerTotals: Record = {}; for (const d of trendDaily) { for (const [p, tok] of Object.entries(d.byProvider || {})) { providerTotals[p] = (providerTotals[p] || 0) + tok; } } const providerNames = Object.entries(providerTotals) .sort((a, b) => b[1] - a[1]) .map(([p]) => p); const kpiItems = [ { label: t("kpiRequests"), value: fmtInt(totals.requests) }, { label: t("kpiInputOther"), value: fmtTokens(totals.inputOther), sub: fmtInt(totals.inputOther) }, { label: t("kpiOutput"), value: fmtTokens(totals.output), sub: fmtInt(totals.output) }, { label: t("kpiCacheRead"), value: fmtTokens(totals.inputCacheRead), sub: fmtInt(totals.inputCacheRead) }, { label: t("kpiCacheCreation"), value: fmtTokens(totals.inputCacheCreation), sub: fmtInt(totals.inputCacheCreation) }, { label: t("kpiCacheHit"), value: fmtPct(totals.cacheHitRate) }, { label: t("totalTokens"), value: fmtTokens(totals.totalTokens), sub: fmtInt(totals.totalTokens) }, { label: t("kpiCost"), value: fmtUsd(totals.costUsd) }, ]; return (
{/* Range tabs + refresh */}
{RANGES.map((r) => ( ))}
{/* Range overview cards */}
{RANGES.map((r) => { const tot = rangeTotals[r]; if (!tot) return null; const active = range === r; return ( ); })}
{/* KPI strip */}
{kpiItems.map((item) => ( ))}
{/* Heatmap — full row */} {/* Usage-trend tabs + Model table side by side */}
{/* Usage-trend tab container */}
{/* Tab bar header */}
{trendTab === "daily" ? t("trendLineSub", { n: trendDaily.length }) : daily.length > 30 ? t("cardDailyTrendSubCapped", { n: daily.length }) : t("cardDailyTrendSub", { n: daily.length })}
{/* Active tab content */}
{trendTab === "daily" ? (
) : (
)}
{models.length === 0 ? (
{t("noData")}
) : (
{visibleModels.map((m) => ( ))}
{t("colModel")} {t("requests")} {t("colTokens")} {t("colCacheHit")} {t("cost")}
{m.modelDisplay || m.model} {m.costEstimated && ( ({t("estimate")}) )}
{fmtInt(m.requests)} {fmtTokens(m.totalTokens)} {fmtPct(m.cacheHitRate)} {fmtUsd(m.costUsd)}
{models.length > 8 && ( )}
)}
{/* Recent requests */} recent.length ? t("cardRecentCapped", { n: recent.length, m: recentTotal }) : t("cardRecentCount", { n: recent.length }) } > {recent.length === 0 ? (
{t("noData")}
) : ( <>
{recentPageRows.map((r, i) => ( ))}
{t("colTime")} {t("colModel")} {t("colInput")} {t("colOutput")} {t("colCacheRead")} {t("cost")}
{fmtTime(r.time)} {r.modelDisplay || r.model} {fmtTokens(r.inputOther)} {fmtTokens(r.output)} {fmtTokens(r.inputCacheRead)} {fmtUsd(r.costUsd)}
{/* Pagination footer */}
{t("paginationPage", { x: recentCurrentPage, y: recentTotalPages })} ·{" "} {t("paginationRange", { x: recentStart + 1, y: Math.min(recentStart + RECENT_PAGE_SIZE, recent.length), })}{" "} · {t("paginationPerPage", { n: RECENT_PAGE_SIZE })}
)}
{/* Footer — meta + attribution at the end of the content flow */}
{t("footerScanned", { n: data.meta.filesScanned, m: fmtInt(data.meta.recordCount) })} ·{" "} {data.meta.home}
{t("dashboardAttribution")}{" "} {" "} {t("attributionSuffix")}
); }