Files
2026-10-15 19:40:28 +05:30

108 lines
3.3 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { api, AdminError, type Stats } from "@/lib/api";
export default function OverviewPage() {
const [stats, setStats] = useState<Stats | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
api
.stats()
.then((s) => {
if (!cancelled) setStats(s);
})
.catch((e) => {
if (!cancelled)
setError(e instanceof AdminError ? e.message : "Could not load stats.");
});
return () => {
cancelled = true;
};
}, []);
return (
<div className="space-y-8">
<h1 className="text-2xl font-semibold text-foreground">Overview</h1>
{error ? (
<p className="text-sm text-destructive">{error}</p>
) : !stats ? (
<p className="text-sm text-muted-foreground">Loading…</p>
) : (
<>
<section className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
<StatCard label="Users" value={stats.users} delta={`+${stats.users_last_7_days} / 7d`} />
<StatCard label="Posts" value={stats.posts} delta={`+${stats.posts_last_7_days} / 7d`} />
<StatCard
label="Collections"
value={stats.collections}
delta={`${stats.public_collections} public`}
/>
<StatCard label="Bookmarks" value={stats.bookmarks} />
<StatCard label="Reshares" value={stats.reshares} />
<StatCard
label="Feedback"
value={stats.feedback}
delta={`+${stats.feedback_last_7_days} / 7d`}
/>
</section>
<section className="space-y-3">
<h2 className="text-lg font-semibold text-foreground">
Feedback by category
</h2>
{!stats.feedback_by_category?.length ? (
<p className="text-sm text-muted-foreground">No feedback yet.</p>
) : (
<div className="max-w-md divide-y divide-border rounded-xl border border-border bg-card">
{stats.feedback_by_category.map((c) => (
<div
key={c.category}
className="flex items-center justify-between px-4 py-2.5 text-sm"
>
<span className="capitalize text-foreground">
{c.category.replace(/_/g, " ")}
</span>
<span className="font-medium text-muted-foreground">
{c.count}
</span>
</div>
))}
</div>
)}
</section>
</>
)}
</div>
);
}
function StatCard({
label,
value,
delta,
}: {
label: string;
value: number;
delta?: string;
}) {
return (
<div className="rounded-xl border border-border bg-card p-4">
<div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
{label}
</div>
<div className="mt-1 flex items-baseline gap-2">
<span className="text-2xl font-semibold text-foreground">
{value.toLocaleString()}
</span>
{delta ? (
<span className="text-xs font-medium text-primary">{delta}</span>
) : null}
</div>
</div>
);
}