Admin Dashboard hinzugefügt: Modernes Dashboard für Administratoren mit Statistiken, Verwaltungsmodulen und Schnellzugriffen

This commit is contained in:
2026-03-16 16:51:15 +01:00
parent 2deafd219b
commit 40de46588e
5 changed files with 417 additions and 7 deletions

View File

@@ -0,0 +1,192 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { apiGet } from "../lib/api";
import type { DashboardOverview } from "../lib/types";
interface AdminStats {
totalSamples: number;
openSamples: number;
completedToday: number;
nextSampleNumber: number;
}
export default function AdminDashboardPage() {
const navigate = useNavigate();
const [stats, setStats] = useState<AdminStats | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadStats() {
try {
const dashboard = await apiGet<DashboardOverview>("/dashboard");
setStats({
totalSamples: dashboard.nextSampleNumber - 1,
openSamples: dashboard.openSamples,
completedToday: dashboard.completedToday,
nextSampleNumber: dashboard.nextSampleNumber,
});
} finally {
setLoading(false);
}
}
void loadStats();
}, []);
const adminModules = [
{
title: "Landwirte",
description: "Verwaltung der Landwirte und Betriebe",
icon: "👨‍🌾",
route: "/admin/landwirte",
color: "#4a7c59",
},
{
title: "Benutzer",
description: "Benutzerverwaltung und Berechtigungen",
icon: "👥",
route: "/admin/benutzer",
color: "#5b7ba8",
},
{
title: "Medikamente",
description: "Verwaltung der Medikamentenkataloge",
icon: "💊",
route: "/admin/medikamente",
color: "#8b5a7c",
},
{
title: "Erreger",
description: "Verwaltung der Erreger und Kürzel",
icon: "🦠",
route: "/admin/erreger",
color: "#7c5a5a",
},
{
title: "Antibiogramm",
description: "Verwaltung der Antibiotika-Kataloge",
icon: "📊",
route: "/admin/antibiogramm",
color: "#5a7c7c",
},
{
title: "Berichtsvorlage",
description: "Gestaltung der PDF-Berichtsvorlagen",
icon: "📄",
route: "/report-template",
color: "#7c7c5a",
},
];
return (
<div className="page-stack">
{/* Header Bereich */}
<section className="hero-card admin-hero">
<div>
<p className="eyebrow">Administration</p>
<h3>Administrator Dashboard</h3>
<p className="muted-text">
Verwalten Sie Landwirte, Benutzer, Medikamente, Erreger und Systemeinstellungen.
</p>
</div>
</section>
{/* Statistik-Karten */}
<section className="metrics-grid admin-metrics">
<article className="metric-card metric-card--primary">
<span className="metric-card__label">Nächste Probennummer</span>
<strong className="metric-card__value--large">
{loading ? "..." : stats?.nextSampleNumber}
</strong>
</article>
<article className="metric-card">
<span className="metric-card__label">Offene Proben</span>
<strong>{loading ? "..." : stats?.openSamples}</strong>
</article>
<article className="metric-card">
<span className="metric-card__label">Heute abgeschlossen</span>
<strong>{loading ? "..." : stats?.completedToday}</strong>
</article>
<article className="metric-card">
<span className="metric-card__label">Gesamtproben</span>
<strong>{loading ? "..." : stats?.totalSamples}</strong>
</article>
</section>
{/* Admin Module Grid */}
<section className="admin-modules-section">
<div className="section-card__header">
<div>
<p className="eyebrow">Verwaltung</p>
<h3>Verwaltungsmodule</h3>
</div>
</div>
<div className="admin-modules-grid">
{adminModules.map((module) => (
<button
key={module.route}
type="button"
className="admin-module-card"
onClick={() => navigate(module.route)}
style={{ "--module-color": module.color } as React.CSSProperties}
>
<span className="admin-module-card__icon">{module.icon}</span>
<div className="admin-module-card__content">
<strong>{module.title}</strong>
<span className="muted-text">{module.description}</span>
</div>
<span className="admin-module-card__arrow"></span>
</button>
))}
</div>
</section>
{/* Schnellzugriffe */}
<section className="quick-actions-section">
<div className="section-card">
<div className="section-card__header">
<div>
<p className="eyebrow">Schnellzugriff</p>
<h3>Häufige Aktionen</h3>
</div>
</div>
<div className="quick-actions-grid">
<button
type="button"
className="quick-action-button"
onClick={() => navigate("/samples/new")}
>
<span></span>
<span>Neue Probe anlegen</span>
</button>
<button
type="button"
className="quick-action-button"
onClick={() => navigate("/search/probe")}
>
<span>🔍</span>
<span>Probe suchen</span>
</button>
<button
type="button"
className="quick-action-button"
onClick={() => navigate("/portal")}
>
<span>📧</span>
<span>Portal & Berichte</span>
</button>
<button
type="button"
className="quick-action-button"
onClick={() => navigate("/search/landwirt")}
>
<span>👤</span>
<span>Landwirt suchen</span>
</button>
</div>
</div>
</section>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import { FormEvent, useState } from "react";
import { useNavigate } from "react-router-dom";
import { apiPost } from "../lib/api";
import { useSession } from "../lib/session";
import type { SessionResponse } from "../lib/types";
@@ -28,6 +29,7 @@ export default function LoginPage() {
});
const [feedback, setFeedback] = useState<FeedbackState>(null);
const { setSession } = useSession();
const navigate = useNavigate();
function unlockLoginInputs() {
setLoginInputsUnlocked(true);
@@ -50,6 +52,8 @@ export default function LoginPage() {
password,
});
setSession(response);
// Admin zum Dashboard, Kunden zur Startseite
navigate(response.user.role === "ADMIN" ? "/admin/dashboard" : "/home");
} catch (loginError) {
setFeedback({ type: "error", text: (loginError as Error).message });
}
@@ -88,6 +92,8 @@ export default function LoginPage() {
text: `Registrierung erfolgreich. Willkommen ${response.user.companyName ?? response.user.displayName}.`,
});
setSession(response);
// Admin zum Dashboard, Kunden zur Startseite
navigate(response.user.role === "ADMIN" ? "/admin/dashboard" : "/home");
} catch (registrationError) {
setFeedback({ type: "error", text: (registrationError as Error).message });
}