fix: ui fixes
This commit is contained in:
+105
-4
@@ -1,6 +1,107 @@
|
||||
import { redirect } from "next/navigation";
|
||||
"use client";
|
||||
|
||||
// The dashboard root just lands on Users.
|
||||
export default function DashboardIndex() {
|
||||
redirect("/users");
|
||||
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 === 0 ? (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user