78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
|
|
import { useEffect, useState } from "react";
|
||
|
|
import { getJSONAuth } from "../../api";
|
||
|
|
import { getToken } from "../../admin/token";
|
||
|
|
import { useAdminT } from "../../admin/useAdminT";
|
||
|
|
|
||
|
|
type Dash = {
|
||
|
|
totalResources: number;
|
||
|
|
published: number;
|
||
|
|
todayNew: number;
|
||
|
|
totalViews: number;
|
||
|
|
totalDownloads: number;
|
||
|
|
totalFavorites: number;
|
||
|
|
totalShares: number;
|
||
|
|
hotResources: {
|
||
|
|
id: string;
|
||
|
|
title: string;
|
||
|
|
downloads: number;
|
||
|
|
favorites: number;
|
||
|
|
views: number;
|
||
|
|
}[];
|
||
|
|
};
|
||
|
|
|
||
|
|
export function AdminDashboard() {
|
||
|
|
const t = useAdminT();
|
||
|
|
const [d, setD] = useState<Dash | null>(null);
|
||
|
|
const token = getToken();
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
getJSONAuth<Dash>("/api/admin/dashboard", token)
|
||
|
|
.then(setD)
|
||
|
|
.catch(() => setD(null));
|
||
|
|
}, [token]);
|
||
|
|
|
||
|
|
if (!d) return <div className="text-neutral-400">{t("loading")}</div>;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
<h1 className="text-2xl font-bold">{t("dashboard")}</h1>
|
||
|
|
<div className="grid gap-3 md:grid-cols-3">
|
||
|
|
<Stat label={t("total")} value={d.totalResources} />
|
||
|
|
<Stat label={t("published")} value={d.published} />
|
||
|
|
<Stat label={t("adminStatTodayNew")} value={d.todayNew} />
|
||
|
|
<Stat label={t("views")} value={d.totalViews} />
|
||
|
|
<Stat label={t("downloads")} value={d.totalDownloads} />
|
||
|
|
<Stat label={t("adminStatFavorites")} value={d.totalFavorites} />
|
||
|
|
<Stat label={t("adminMetricShares")} value={d.totalShares} />
|
||
|
|
</div>
|
||
|
|
<div className="rounded-2xl border border-ark-line bg-ark-panel p-4">
|
||
|
|
<div className="font-semibold mb-3">{t("popular")}</div>
|
||
|
|
<div className="divide-y divide-ark-line">
|
||
|
|
{(d.hotResources ?? []).map((x) => (
|
||
|
|
<div
|
||
|
|
key={x.id}
|
||
|
|
className="py-2 flex items-center justify-between gap-3"
|
||
|
|
>
|
||
|
|
<div className="text-sm">{x.title}</div>
|
||
|
|
<div className="text-xs text-neutral-400">
|
||
|
|
{t("adminMetricDownloads")} {x.downloads} ·{" "}
|
||
|
|
{t("adminMetricFavorites")} {x.favorites} ·{" "}
|
||
|
|
{t("adminMetricViews")} {x.views}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function Stat({ label, value }: { label: string; value: number }) {
|
||
|
|
return (
|
||
|
|
<div className="rounded-2xl border border-ark-line bg-ark-panel p-4">
|
||
|
|
<div className="text-xs text-neutral-400">{label}</div>
|
||
|
|
<div className="text-2xl font-bold mt-1">{value}</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|