94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import type { ActivitySeries } from "@/types/analytics";
|
|
import {
|
|
ResponsiveContainer,
|
|
AreaChart,
|
|
Area,
|
|
XAxis,
|
|
YAxis,
|
|
Tooltip,
|
|
CartesianGrid,
|
|
Legend,
|
|
} from "recharts";
|
|
|
|
async function fetchActivity(
|
|
from?: string,
|
|
to?: string,
|
|
interval: "day" | "week" | "month" = "day",
|
|
): Promise<ActivitySeries> {
|
|
const url = new URL("/api/analytics/activity", window.location.origin);
|
|
if (from) url.searchParams.set("from", from);
|
|
if (to) url.searchParams.set("to", to);
|
|
url.searchParams.set("interval", interval);
|
|
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 activity (${res.status})`);
|
|
}
|
|
return (await res.json()) as ActivitySeries;
|
|
}
|
|
|
|
export function ActivityChart() {
|
|
const q = useQuery({
|
|
queryKey: ["analytics", "activity", "day"],
|
|
queryFn: () => fetchActivity(undefined, undefined, "day"),
|
|
});
|
|
|
|
const data =
|
|
q.data?.points.map((p) => ({
|
|
date: p.date.slice(0, 10),
|
|
uploaded: p.uploaded ?? 0,
|
|
modified: p.modified ?? 0,
|
|
})) ?? [];
|
|
|
|
return (
|
|
<div className="rounded-md border p-3">
|
|
<div className="text-sm font-medium mb-2">Activity</div>
|
|
<div className="h-64">
|
|
{q.isLoading ? (
|
|
<div className="text-xs text-muted-foreground">Loading…</div>
|
|
) : data.length === 0 ? (
|
|
<div className="text-xs text-muted-foreground">No data</div>
|
|
) : (
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<AreaChart data={data}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
|
|
<XAxis dataKey="date" tick={{ fontSize: 11 }} />
|
|
<YAxis tick={{ fontSize: 11 }} />
|
|
<Tooltip
|
|
contentStyle={{
|
|
background: "hsl(var(--popover))",
|
|
border: "1px solid hsl(var(--border))",
|
|
color: "hsl(var(--popover-foreground))",
|
|
}}
|
|
/>
|
|
<Legend />
|
|
<Area
|
|
type="monotone"
|
|
dataKey="uploaded"
|
|
name="Uploaded"
|
|
stroke="hsl(var(--chart-2))"
|
|
fill="hsl(var(--chart-2))"
|
|
fillOpacity={0.25}
|
|
strokeWidth={2}
|
|
/>
|
|
<Area
|
|
type="monotone"
|
|
dataKey="modified"
|
|
name="Modified"
|
|
stroke="hsl(var(--chart-1))"
|
|
fill="hsl(var(--chart-1))"
|
|
fillOpacity={0.25}
|
|
strokeWidth={2}
|
|
/>
|
|
</AreaChart>
|
|
</ResponsiveContainer>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|