feat: full site build — Project/Melody schema (Option A), admin CRUD, public sections, uploads, email+SMTP, internal analytics, legal pages, docs
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import AdminPageHeader from "@/components/admin/admin-page-header";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { getAnalyticsSummary } from "@/lib/analytics";
|
||||
|
||||
const eventLabels: Record<string, string> = {
|
||||
PAGE_VIEW: "Page views",
|
||||
PROJECT_OPEN: "Project opens",
|
||||
MELODY_PLAY: "Melody plays",
|
||||
PROJECT_LINK_CLICK: "Project link clicks",
|
||||
CONTACT_SUBMITTED: "Contact submissions",
|
||||
};
|
||||
|
||||
export default async function AnalyticsPage() {
|
||||
const summary = await getAnalyticsSummary();
|
||||
const grouped = summary.grouped.map((item) => ({ type: item.type, count: item._count._all }));
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-7xl">
|
||||
<AdminPageHeader title="Analytics" description="Internal counters from the last 30 days. No third-party analytics service is used." />
|
||||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-5">
|
||||
{Object.entries(eventLabels).map(([type, label]) => (
|
||||
<Card key={type}><CardHeader className="pb-3"><CardTitle className="text-sm font-medium text-muted-foreground">{label}</CardTitle></CardHeader><CardContent><p className="text-3xl font-semibold">{grouped.find((item) => item.type === type)?.count ?? 0}</p><p className="mt-1 text-xs text-muted-foreground">Last 30 days</p></CardContent></Card>
|
||||
))}
|
||||
</div>
|
||||
<Card className="mt-6">
|
||||
<CardHeader><CardTitle>Recent events</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
{summary.recent.length === 0 ? <p className="text-sm text-muted-foreground">No events recorded yet.</p> : <div className="space-y-3">{summary.recent.map((event) => <div key={event.id} className="flex flex-wrap items-center justify-between gap-3 border-b border-border pb-3 text-sm last:border-0 last:pb-0"><span className="font-medium">{eventLabels[event.type] ?? event.type}</span><span className="text-muted-foreground">{event.path || "—"} · {event.createdAt.toLocaleString("en-GB")}</span></div>)}</div>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="mt-5 text-xs text-muted-foreground">Total events recorded: {summary.total}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user