mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 16:03:41 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
2f0a556371
commit
eb39346f02
2 changed files with 91 additions and 12 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Case | null>(null)
|
||||
const [sheetOpen, setSheetOpen] = useState(false)
|
||||
const [batchMode, setBatchMode] = useState(false)
|
||||
|
||||
// Debounce search
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
|
@ -110,16 +114,26 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) {
|
|||
|
||||
return (
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">
|
||||
{pendingIcdOnly ? 'ICD-Eingabe' : 'Fälle'}
|
||||
</h1>
|
||||
{pendingIcdOnly && (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Fälle, die noch einen ICD-10-Diagnosecode benötigen. Klicken Sie auf einen Fall, um den Code einzugeben.
|
||||
{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.'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{pendingIcdOnly && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch id="batch-mode" checked={batchMode} onCheckedChange={setBatchMode} />
|
||||
<Label htmlFor="batch-mode" className="text-sm cursor-pointer">Batch-Modus</Label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Filter bar */}
|
||||
{!pendingIcdOnly && (
|
||||
|
|
@ -244,8 +258,8 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) {
|
|||
{data.items.map((c) => (
|
||||
<TableRow
|
||||
key={c.id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => openDetail(c)}
|
||||
className={pendingIcdOnly && batchMode ? '' : 'cursor-pointer'}
|
||||
onClick={() => { if (!(pendingIcdOnly && batchMode)) openDetail(c) }}
|
||||
>
|
||||
<TableCell className="font-mono text-sm">
|
||||
{c.fall_id || '-'}
|
||||
|
|
@ -262,7 +276,9 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) {
|
|||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{c.icd ? (
|
||||
{pendingIcdOnly && batchMode ? (
|
||||
<InlineIcdInput caseId={c.id} initialValue={c.icd || ''} />
|
||||
) : c.icd ? (
|
||||
<span className="font-mono text-sm">{c.icd}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
|
|
@ -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<HTMLInputElement>(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<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
save()
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
setValue(initialValue)
|
||||
setStatus('idle')
|
||||
setErrorMsg('')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5" onClick={(e) => e.stopPropagation()}>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
onChange={(e) => { 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' && <Loader2 className="size-4 text-muted-foreground animate-spin" />}
|
||||
{status === 'saved' && <CheckCircle className="size-4 text-green-600" />}
|
||||
{status === 'error' && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<XCircle className="size-4 text-red-500 cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{errorMsg}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StatusBadges({ c }: { c: Case }) {
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
|
|
|
|||
Loading…
Reference in a new issue