From 73b0d6761c8f0ea312de9bfdeeaf5cf21f797f7e Mon Sep 17 00:00:00 2001 From: CCS Admin Date: Sat, 28 Feb 2026 13:15:05 +0000 Subject: [PATCH] feat: add year-over-year comparison to Dashboard KPI cards Dashboard endpoint now returns prev_kpis (previous year's KPIs) alongside current KPIs. KpiCard component shows percentage change with colored trend indicators (green up, red down, grey neutral). Also marks completed items in todo.md (notification center, dark mode toggle were already implemented). Co-Authored-By: Claude Opus 4.6 --- backend/app/api/reports.py | 3 +- backend/app/schemas/report.py | 1 + docs/todo.md | 6 ++-- frontend/src/pages/DashboardPage.tsx | 54 ++++++++++++++++++++++++++-- frontend/src/types/index.ts | 1 + 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/backend/app/api/reports.py b/backend/app/api/reports.py index dc98739..5c15826 100644 --- a/backend/app/api/reports.py +++ b/backend/app/api/reports.py @@ -49,8 +49,9 @@ def dashboard( ) kpis = calculate_dashboard_kpis(db, jahr) + prev_kpis = calculate_dashboard_kpis(db, jahr - 1) sheet1 = calculate_sheet1_data(db, jahr) - return DashboardResponse(kpis=kpis, weekly=sheet1.get("weekly", [])) + return DashboardResponse(kpis=kpis, prev_kpis=prev_kpis, weekly=sheet1.get("weekly", [])) except ImportError: # report_service not yet implemented (parallel task) raise HTTPException(501, "Report service not yet available") diff --git a/backend/app/schemas/report.py b/backend/app/schemas/report.py index 8ee7157..0e42235 100644 --- a/backend/app/schemas/report.py +++ b/backend/app/schemas/report.py @@ -31,6 +31,7 @@ class DashboardResponse(BaseModel): """Combined dashboard payload: KPIs + weekly time-series.""" kpis: DashboardKPIs + prev_kpis: Optional[DashboardKPIs] = None weekly: list[WeeklyDataPoint] diff --git a/docs/todo.md b/docs/todo.md index 2362d28..0f7c3d0 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -4,14 +4,14 @@ - [x] **Gutachten-Statistik Seite** — ✅ Implementiert: 4 KPIs, Stacked-Bar (Typ pro KW), Donut (Typ-Verteilung), Grouped-Bar (Therapieänderungen pro KW), Horizontal-Bars (Gründe). Backend-Endpoint + Frontend komplett. - [ ] **Fallliste als Excel exportieren** — Export-Button auf der Cases-Seite für gefilterte Falllisten als .xlsx Download. Nutzt aktive Filter (Jahr, Fallgruppe, ICD-Status). -- [ ] **E-Mail-Benachrichtigungen bei Freigabe-Entscheidung** — DAK-Mitarbeiter per E-Mail informieren, wenn ihre Freigabe-Anfrage genehmigt oder abgelehnt wurde. SMTP ist bereits konfiguriert. +- [x] **E-Mail-Benachrichtigungen bei Freigabe-Entscheidung** — ✅ Implementiert: disclosure_service nutzt nun notification_service für In-App + E-Mail. Admins erhalten E-Mail bei neuer Anfrage, Mitarbeiter bei Genehmigung/Ablehnung. ## Mittlere Priorität -- [ ] **Benachrichtigungs-Center (Bell-Icon)** — UI-Element im Header mit Badge-Counter. `useNotifications()` Hook mit 60s-Polling existiert bereits, braucht nur ein Frontend-Element. +- [x] **Benachrichtigungs-Center (Bell-Icon)** — ✅ Bereits implementiert: Bell-Icon im Header mit Badge-Counter, Popover-Dropdown, Mark-as-read, 60s-Polling. War schon in Header.tsx vorhanden. - [ ] **Dashboard: Vorjahresvergleich bei KPIs** — Prozentuale Veränderung zum Vorjahr neben den KPI-Zahlen (z.B. "+12% vs. 2025"). `vorjahr_service` existiert im Backend. - [ ] **Batch-ICD-Eingabe** — Inline-Tabelle auf der ICD-Seite mit direkter ICD-Eingabe pro Zeile statt Einzelklick auf jeden Fall. -- [ ] **Dark Mode Toggle** — Toggle in Header oder Kontoverwaltung. `useTheme` Hook existiert bereits. +- [x] **Dark Mode Toggle** — ✅ Bereits implementiert: Sun/Moon-Toggle im Header, useTheme Hook aktiv. ## Niedrige Priorität diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index af516e0..92e82f3 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -4,7 +4,7 @@ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, PieChart, Pie, Cell, ResponsiveContainer, } from 'recharts' -import { FileText, Clock, Code, Stethoscope, Info } from 'lucide-react' +import { FileText, Clock, Code, Stethoscope, Info, TrendingUp, TrendingDown, Minus } from 'lucide-react' import { useAuth } from '@/context/AuthContext' import { useDashboard, useYearlyComparison, useTopIcd } from '@/hooks/useDashboard' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' @@ -115,6 +115,8 @@ export function DashboardPage() { title="Fälle gesamt" tooltip="Gesamtzahl aller erfassten Fälle im gewählten Jahr" value={data.kpis.total_cases} + prevValue={data.prev_kpis?.total_cases} + prevYear={jahr - 1} icon={} href="/cases" /> @@ -122,6 +124,8 @@ export function DashboardPage() { title="Offene ICD" tooltip="Fälle, denen noch ein ICD-10-Diagnosecode zugewiesen werden muss" value={data.kpis.pending_icd} + prevValue={data.prev_kpis?.pending_icd} + prevYear={jahr - 1} icon={} href="/icd" /> @@ -129,6 +133,8 @@ export function DashboardPage() { title="Offene Codierung" tooltip="Fälle, die noch nicht abschließend klassifiziert wurden" value={data.kpis.pending_coding} + prevValue={data.prev_kpis?.pending_coding} + prevYear={jahr - 1} icon={} href={isAdmin ? '/coding' : undefined} /> @@ -136,6 +142,8 @@ export function DashboardPage() { title="Gutachten gesamt" tooltip="Anzahl der Fälle mit erstelltem Gutachten" value={data.kpis.total_gutachten} + prevValue={data.prev_kpis?.total_gutachten} + prevYear={jahr - 1} icon={} href="/gutachten-statistik" /> @@ -327,7 +335,20 @@ export function DashboardPage() { ) } -function KpiCard({ title, tooltip, value, icon, href }: { title: string; tooltip?: string; value: number; icon: React.ReactNode; href?: string }) { +function KpiCard({ title, tooltip, value, prevValue, prevYear, icon, href }: { + title: string + tooltip?: string + value: number + prevValue?: number + prevYear?: number + icon: React.ReactNode + href?: string +}) { + // Calculate percentage change vs previous year + const change = prevValue != null && prevValue > 0 + ? ((value - prevValue) / prevValue) * 100 + : null + const card = ( @@ -348,6 +369,35 @@ function KpiCard({ title, tooltip, value, icon, href }: { title: string; tooltip
{value.toLocaleString('de-DE')}
+ {prevValue != null && prevYear != null && ( +
+ {change !== null ? ( + change > 0 ? ( + <> + + +{Math.round(change)}% + + ) : change < 0 ? ( + <> + + {Math.round(change)}% + + ) : ( + <> + + 0% + + ) + ) : prevValue === 0 && value > 0 ? ( + Neu in {prevYear + 1} + ) : ( + + )} + {change !== null && ( + ggü. {prevYear} + )} +
+ )}
) diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 4aeca73..dd40817 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -112,6 +112,7 @@ export interface WeeklyDataPoint { export interface DashboardResponse { kpis: DashboardKPIs + prev_kpis?: DashboardKPIs | null weekly: WeeklyDataPoint[] }