diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dff6c22..875e8c5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,7 +8,9 @@ "name": "muh-frontend", "version": "0.0.1", "dependencies": { + "chart.js": "^4.5.1", "react": "18.2.0", + "react-chartjs-2": "^5.3.1", "react-dom": "18.2.0", "react-router-dom": "6.23.1" }, @@ -745,6 +747,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@remix-run/router": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", @@ -1290,6 +1298,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1538,6 +1558,16 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", + "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2b1410c..8f48839 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,9 @@ "preview": "vite preview" }, "dependencies": { + "chart.js": "^4.5.1", "react": "18.2.0", + "react-chartjs-2": "^5.3.1", "react-dom": "18.2.0", "react-router-dom": "6.23.1" }, diff --git a/frontend/src/pages/AdminDashboardPage.tsx b/frontend/src/pages/AdminDashboardPage.tsx index 289f3ff..ba9b2b4 100644 --- a/frontend/src/pages/AdminDashboardPage.tsx +++ b/frontend/src/pages/AdminDashboardPage.tsx @@ -1,7 +1,27 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; +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; @@ -36,6 +56,99 @@ export default function AdminDashboardPage() { 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 (
Statistik
-| Tierarzt | -Firma | -Anzahl Proben | -
|---|---|---|
| - {vet.displayName} - | -{vet.companyName ?? "-"} | -- {vet.sampleCount} - | -