From eb39346f028082145436befdc6cd71a3f9011a80 Mon Sep 17 00:00:00 2001 From: CCS Admin Date: Sat, 28 Feb 2026 14:07:33 +0000 Subject: [PATCH] feat: add batch ICD input mode to ICD page Adds a Switch toggle for "Batch-Modus" on the ICD page that enables inline ICD code entry directly in the table. Features: Enter to save, Escape to cancel, Tab to navigate, visual feedback (green/red border, spinner, checkmark/error icon with tooltip). Co-Authored-By: Claude Opus 4.6 --- docs/todo.md | 2 +- frontend/src/pages/CasesPage.tsx | 101 +++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/docs/todo.md b/docs/todo.md index 0f7c3d0..c210836 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -9,7 +9,7 @@ ## Mittlere Priorität - [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. +- [x] **Dashboard: Vorjahresvergleich bei KPIs** — ✅ Implementiert: prev_kpis im Dashboard-Endpoint, KpiCards zeigen farbige Trend-Indikatoren (+X% grün, -X% rot) mit Vorjahresvergleich. - [ ] **Batch-ICD-Eingabe** — Inline-Tabelle auf der ICD-Seite mit direkter ICD-Eingabe pro Zeile statt Einzelklick auf jeden Fall. - [x] **Dark Mode Toggle** — ✅ Bereits implementiert: Sun/Moon-Toggle im Header, useTheme Hook aktiv. diff --git a/frontend/src/pages/CasesPage.tsx b/frontend/src/pages/CasesPage.tsx index 37ef15d..8c29981 100644 --- a/frontend/src/pages/CasesPage.tsx +++ b/frontend/src/pages/CasesPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' +import type { KeyboardEvent } from 'react' import { - Search, ChevronLeft, ChevronRight, Save, CheckCircle, XCircle, Pencil, X, Info, + Search, ChevronLeft, ChevronRight, Save, CheckCircle, XCircle, Pencil, X, Info, Loader2, } from 'lucide-react' import type { Case } from '@/types' import { useAuth } from '@/context/AuthContext' @@ -18,6 +19,8 @@ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, } from '@/components/ui/sheet' import { Skeleton } from '@/components/ui/skeleton' +import { Switch } from '@/components/ui/switch' +import { Label } from '@/components/ui/label' import { Alert, AlertDescription } from '@/components/ui/alert' import { Tooltip, TooltipContent, TooltipTrigger, @@ -67,6 +70,7 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) { const [perPage] = useState(50) const [selectedCase, setSelectedCase] = useState(null) const [sheetOpen, setSheetOpen] = useState(false) + const [batchMode, setBatchMode] = useState(false) // Debounce search const debounceRef = useRef | null>(null) @@ -110,14 +114,24 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) { return (
-
-

- {pendingIcdOnly ? 'ICD-Eingabe' : 'Fälle'} -

+
+
+

+ {pendingIcdOnly ? 'ICD-Eingabe' : 'Fälle'} +

+ {pendingIcdOnly && ( +

+ {batchMode + ? 'ICD-Codes direkt in der Tabelle eingeben. Enter zum Speichern, Tab zum nächsten Feld.' + : 'Fälle, die noch einen ICD-10-Diagnosecode benötigen. Klicken Sie auf einen Fall, um den Code einzugeben.'} +

+ )} +
{pendingIcdOnly && ( -

- Fälle, die noch einen ICD-10-Diagnosecode benötigen. Klicken Sie auf einen Fall, um den Code einzugeben. -

+
+ + +
)}
@@ -244,8 +258,8 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) { {data.items.map((c) => ( openDetail(c)} + className={pendingIcdOnly && batchMode ? '' : 'cursor-pointer'} + onClick={() => { if (!(pendingIcdOnly && batchMode)) openDetail(c) }} > {c.fall_id || '-'} @@ -262,7 +276,9 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) { - {c.icd ? ( + {pendingIcdOnly && batchMode ? ( + + ) : c.icd ? ( {c.icd} ) : ( - @@ -329,6 +345,69 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) { ) } +function InlineIcdInput({ caseId, initialValue }: { caseId: number; initialValue: string }) { + const [value, setValue] = useState(initialValue) + const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle') + const [errorMsg, setErrorMsg] = useState('') + const icdMutation = useIcdUpdate() + const inputRef = useRef(null) + + const save = async () => { + const trimmed = value.trim() + if (!trimmed || trimmed === initialValue) return + setStatus('saving') + setErrorMsg('') + try { + await icdMutation.mutateAsync({ id: caseId, icd: trimmed }) + setStatus('saved') + } catch (err: any) { + const detail = err?.response?.data?.detail + setErrorMsg(typeof detail === 'string' ? detail : 'Ungültiger ICD-Code') + setStatus('error') + } + } + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault() + save() + } + if (e.key === 'Escape') { + setValue(initialValue) + setStatus('idle') + setErrorMsg('') + } + } + + return ( +
e.stopPropagation()}> + { setValue(e.target.value); if (status === 'saved' || status === 'error') setStatus('idle') }} + onKeyDown={handleKeyDown} + onBlur={save} + placeholder="z.B. C50.9" + className={`h-8 w-32 font-mono text-sm ${ + status === 'saved' ? 'border-green-500 focus-visible:ring-green-500' : + status === 'error' ? 'border-red-500 focus-visible:ring-red-500' : '' + }`} + disabled={status === 'saving'} + /> + {status === 'saving' && } + {status === 'saved' && } + {status === 'error' && ( + + + + + {errorMsg} + + )} +
+ ) +} + function StatusBadges({ c }: { c: Case }) { return (