Files
muh/frontend/src/pages/AdminDashboardPage.tsx

200 lines
4.6 KiB
TypeScript

import { useEffect, useState } from "react";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from "chart.js";
import { Bar } from "react-chartjs-2";
import { apiGet } from "../lib/api";
// Chart.js Komponenten registrieren
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
interface VetSampleStats {
userId: string;
displayName: string;
companyName: string | null;
sampleCount: number;
}
interface AdminStatistics {
totalVets: number;
totalSamples: number;
samplesPerVet: VetSampleStats[];
}
export default function AdminDashboardPage() {
const [stats, setStats] = useState<AdminStatistics | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function loadStats() {
try {
const response = await apiGet<AdminStatistics>("/admin/statistics");
setStats(response);
} catch (err) {
setError((err as Error).message);
} finally {
setLoading(false);
}
}
void loadStats();
}, []);
// Chart Daten vorbereiten
const chartData = {
labels: stats?.samplesPerVet.map((vet) => vet.displayName) || [],
datasets: [
{
label: "Anzahl Proben",
data: stats?.samplesPerVet.map((vet) => vet.sampleCount) || [],
backgroundColor: "rgba(90, 123, 168, 0.8)",
borderColor: "rgba(90, 123, 168, 1)",
borderWidth: 1,
borderRadius: 8,
borderSkipped: false,
},
],
};
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
title: {
display: true,
text: "Proben pro Tierarzt",
font: {
size: 16,
weight: "bold" as const,
},
padding: {
top: 10,
bottom: 20,
},
color: "#1d2428",
},
tooltip: {
backgroundColor: "rgba(29, 36, 40, 0.9)",
padding: 12,
cornerRadius: 8,
titleFont: {
size: 14,
},
bodyFont: {
size: 13,
},
callbacks: {
label: (context: { parsed: { y: number } }) => {
return `${context.parsed.y} Proben`;
},
},
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1,
color: "#666",
},
grid: {
color: "rgba(0, 0, 0, 0.05)",
},
title: {
display: true,
text: "Anzahl Proben",
color: "#666",
font: {
size: 12,
},
},
},
x: {
ticks: {
color: "#666",
maxRotation: 45,
minRotation: 45,
},
grid: {
display: false,
},
title: {
display: true,
text: "Tierarzt",
color: "#666",
font: {
size: 12,
},
},
},
},
};
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">
Übersicht über Tierärzte und Proben im System.
</p>
</div>
</section>
{error ? (
<div className="alert alert--error">{error}</div>
) : null}
{/* Statistik-Karten */}
<section className="metrics-grid admin-metrics">
<article className="metric-card metric-card--primary">
<span className="metric-card__label">Tierärzte</span>
<strong className="metric-card__value--large">
{loading ? "..." : stats?.totalVets ?? 0}
</strong>
</article>
<article className="metric-card metric-card--secondary">
<span className="metric-card__label">Proben insgesamt</span>
<strong className="metric-card__value--large">
{loading ? "..." : stats?.totalSamples ?? 0}
</strong>
</article>
</section>
{/* Chart Bereich */}
<section className="section-card">
<div className="chart-container">
{loading ? (
<div className="empty-state">Chart wird geladen...</div>
) : stats?.samplesPerVet.length === 0 ? (
<div className="empty-state">Noch keine Proben vorhanden.</div>
) : (
<Bar data={chartData} options={chartOptions} />
)}
</div>
</section>
</div>
);
}