78 lines
2.9 KiB
TypeScript

"use client";
import * as React from "react";
import { useQuery } from "@tanstack/react-query";
import type { AnalyticsSummary } from "@/types/analytics";
async function fetchSummary(ownersLimit = 8): Promise<AnalyticsSummary> {
const url = new URL("/api/analytics/summary", window.location.origin);
url.searchParams.set("ownersLimit", String(ownersLimit));
const res = await fetch(url.toString(), { cache: "no-store" });
if (!res.ok) {
const payload = await res.json().catch(() => ({}));
throw new Error(payload?.message || `Failed to load summary (${res.status})`);
}
return (await res.json()) as AnalyticsSummary;
}
function formatBytes(bytes?: number) {
const b = Number(bytes || 0);
if (!b) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB", "TB", "PB"];
const i = Math.floor(Math.log(b) / Math.log(k));
return `${parseFloat((b / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
export function KPICards() {
const q = useQuery({
queryKey: ["analytics", "summary"],
queryFn: () => fetchSummary(8),
});
const s = q.data;
return (
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<KPI title="Total Files" value={q.isLoading ? "…" : (s?.totalFiles ?? 0).toLocaleString()} />
<KPI title="Total Size" value={q.isLoading ? "…" : formatBytes(s?.totalSizeBytes)} />
<KPI title="Unique Tags" value={q.isLoading ? "…" : (s?.uniqueTags ?? 0).toLocaleString()} />
<KPI
title="Last Modified"
value={q.isLoading ? "…" : (s?.lastModifiedAt ? new Date(s.lastModifiedAt).toLocaleString() : "—")}
/>
{/* Owners list */}
<div className="rounded-md border p-3 col-span-1 md:col-span-2">
<div className="text-sm font-medium mb-1">Top Owners</div>
{q.isLoading && <div className="text-xs text-muted-foreground">Loading</div>}
{!q.isLoading && (s?.owners?.length ?? 0) === 0 && (
<div className="text-xs text-muted-foreground">No owner data</div>
)}
<ul className="text-sm space-y-1">
{(s?.owners ?? []).map((o) => (
<li key={o.owner} className="flex items-center justify-between">
<span className="truncate">{o.owner || "unknown"}</span>
<span className="text-muted-foreground">{o.count.toLocaleString()}</span>
</li>
))}
</ul>
</div>
<div className="rounded-md border p-3">
<div className="text-sm font-medium mb-1">Last Uploaded</div>
<div className="text-lg">
{q.isLoading ? "…" : (s?.lastUploadedAt ? new Date(s.lastUploadedAt).toLocaleString() : "—")}
</div>
</div>
</div>
);
}
function KPI({ title, value }: { title: string; value: React.ReactNode }) {
return (
<div className="rounded-md border p-3">
<div className="text-sm text-muted-foreground">{title}</div>
<div className="text-2xl font-semibold mt-1">{value}</div>
</div>
);
}