MBO-Tech-IT-Webseite/app/admin/statistik/page.tsx

155 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createServiceClient } from "@/lib/supabase";
import { verifySessionToken } from "@/lib/admin-auth";
import { AdminNav } from "@/components/admin/AdminNav";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import type { Metadata } from "next";
export const metadata: Metadata = { title: "Statistik MBO Tech IT" };
export const dynamic = "force-dynamic";
function fmt(n: number) {
return n.toLocaleString("de-DE");
}
export default async function StatistikPage() {
const cookieStore = await cookies();
const token = cookieStore.get("admin_session")?.value;
if (!token) redirect("/admin/login");
const session = await verifySessionToken(token);
if (!session) redirect("/admin/login?session_expired=true");
const db = createServiceClient();
const { data: alleAnfragen } = await db
.from("anfragen")
.select("id, status, created_at, name, betreff");
const anfragen = alleAnfragen ?? [];
const gesamt = anfragen.length;
const offen = anfragen.filter((a) => a.status === "offen").length;
const inBearbeitung = anfragen.filter((a) => a.status === "in_bearbeitung").length;
const abgeschlossen = anfragen.filter((a) => a.status === "abgeschlossen").length;
const { data: recentRaw } = await db
.from("anfragen")
.select("id, created_at, name, betreff, status, email")
.order("created_at", { ascending: false })
.limit(20);
const recentAnfragen = recentRaw ?? [];
const heute = new Date();
const monatsStats: { monat: string; label: string; count: number }[] = [];
for (let i = 5; i >= 0; i--) {
const d = new Date(heute);
d.setMonth(d.getMonth() - i);
const monat = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
const label = d.toLocaleDateString("de-DE", { month: "short", year: "2-digit" });
const count = anfragen.filter((a) => a.created_at.startsWith(monat)).length;
monatsStats.push({ monat, label, count });
}
const maxCount = Math.max(...monatsStats.map((m) => m.count), 1);
return (
<div className="min-h-screen bg-[#111925]">
<AdminNav />
<div className="max-w-7xl mx-auto px-4 py-8 space-y-8">
<div>
<h1 className="text-2xl font-bold text-white tracking-tight">Statistik</h1>
<p className="text-sm text-slate-500 mt-0.5">Anfragen-Übersicht</p>
</div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{[
{ label: "Anfragen gesamt", wert: fmt(gesamt), farbe: "border-l-slate-400" },
{ label: "Offen", wert: fmt(offen), farbe: "border-l-amber-500" },
{ label: "In Bearbeitung", wert: fmt(inBearbeitung), farbe: "border-l-blue-500" },
{ label: "Abgeschlossen", wert: fmt(abgeschlossen), farbe: "border-l-green-500" },
].map((k) => (
<div key={k.label} className={`bg-[#18212f] border border-gray-800 border-l-4 ${k.farbe} p-4`}>
<p className="text-xs text-slate-500 uppercase tracking-wide font-medium mb-1">{k.label}</p>
<p className="text-2xl font-bold text-white font-mono">{k.wert}</p>
</div>
))}
</div>
<div className="bg-[#18212f] border border-gray-800 p-5">
<h2 className="font-semibold text-white mb-5">Anfragen letzte 6 Monate</h2>
<div className="flex items-end gap-3 h-32">
{monatsStats.map((m) => (
<div key={m.monat} className="flex-1 flex flex-col items-center gap-1">
<span className="text-[10px] font-mono text-slate-500">
{m.count > 0 ? m.count : ""}
</span>
<div className="w-full flex items-end" style={{ height: 80 }}>
<div
className="w-full bg-orange-500 rounded-sm transition-all"
style={{
height: `${Math.max(m.count > 0 ? 8 : 0, Math.round((m.count / maxCount) * 80))}px`,
}}
/>
</div>
<span className="text-[10px] text-slate-400 font-medium">{m.label}</span>
</div>
))}
</div>
</div>
<div className="bg-[#18212f] border border-gray-800">
<div className="p-5 border-b border-gray-800">
<h2 className="font-semibold text-white">Letzte 20 Anfragen</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="border-b border-gray-800">
<tr className="text-xs text-slate-500 uppercase tracking-wide">
<th className="text-left py-3 px-5 font-medium">Datum</th>
<th className="text-left py-3 px-5 font-medium">Name</th>
<th className="text-left py-3 px-5 font-medium hidden sm:table-cell">Betreff</th>
<th className="text-left py-3 px-5 font-medium">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-800/50">
{recentAnfragen.length === 0 ? (
<tr>
<td colSpan={4} className="px-5 py-8 text-center text-slate-500">
Keine Anfragen vorhanden
</td>
</tr>
) : (
recentAnfragen.map((a) => (
<tr key={a.id} className="hover:bg-[#111925] transition-colors">
<td className="px-5 py-3 text-slate-400 whitespace-nowrap">
{new Date(a.created_at).toLocaleDateString("de-DE")}
</td>
<td className="px-5 py-3 font-medium text-white">{a.name}</td>
<td className="px-5 py-3 text-slate-400 hidden sm:table-cell truncate max-w-xs">{a.betreff}</td>
<td className="px-5 py-3">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
a.status === "offen"
? "bg-amber-500/10 text-amber-400 border border-amber-500/20"
: a.status === "in_bearbeitung"
? "bg-blue-500/10 text-blue-400 border border-blue-500/20"
: "bg-green-500/10 text-green-400 border border-green-500/20"
}`}>
{a.status === "offen"
? "Offen"
: a.status === "in_bearbeitung"
? "In Bearbeitung"
: "Abgeschlossen"}
</span>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}