mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 18:23:42 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
3216dd6d53
commit
73b0d6761c
5 changed files with 59 additions and 6 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class DashboardResponse(BaseModel):
|
|||
"""Combined dashboard payload: KPIs + weekly time-series."""
|
||||
|
||||
kpis: DashboardKPIs
|
||||
prev_kpis: Optional[DashboardKPIs] = None
|
||||
weekly: list[WeeklyDataPoint]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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={<FileText className="size-5 text-muted-foreground" />}
|
||||
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={<Clock className="size-5 text-muted-foreground" />}
|
||||
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={<Code className="size-5 text-muted-foreground" />}
|
||||
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={<Stethoscope className="size-5 text-muted-foreground" />}
|
||||
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 = (
|
||||
<Card className={href ? 'transition-colors hover:border-primary/50 hover:shadow-md' : undefined}>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
|
|
@ -348,6 +369,35 @@ function KpiCard({ title, tooltip, value, icon, href }: { title: string; tooltip
|
|||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold">{value.toLocaleString('de-DE')}</div>
|
||||
{prevValue != null && prevYear != null && (
|
||||
<div className="mt-1 flex items-center gap-1 text-xs">
|
||||
{change !== null ? (
|
||||
change > 0 ? (
|
||||
<>
|
||||
<TrendingUp className="size-3 text-green-600" />
|
||||
<span className="text-green-600 font-medium">+{Math.round(change)}%</span>
|
||||
</>
|
||||
) : change < 0 ? (
|
||||
<>
|
||||
<TrendingDown className="size-3 text-red-500" />
|
||||
<span className="text-red-500 font-medium">{Math.round(change)}%</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Minus className="size-3 text-muted-foreground" />
|
||||
<span className="text-muted-foreground">0%</span>
|
||||
</>
|
||||
)
|
||||
) : prevValue === 0 && value > 0 ? (
|
||||
<span className="text-muted-foreground">Neu in {prevYear + 1}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
)}
|
||||
{change !== null && (
|
||||
<span className="text-muted-foreground">ggü. {prevYear}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ export interface WeeklyDataPoint {
|
|||
|
||||
export interface DashboardResponse {
|
||||
kpis: DashboardKPIs
|
||||
prev_kpis?: DashboardKPIs | null
|
||||
weekly: WeeklyDataPoint[]
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue