feat: add disclosure request UI and field visibility for dak_mitarbeiter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-26 16:11:47 +00:00
parent f2219b487b
commit 62a56f3fc9
2 changed files with 98 additions and 20 deletions

View file

@ -22,6 +22,7 @@ import { Alert, AlertDescription } from '@/components/ui/alert'
import { CASE_SECTIONS } from './cases/fieldConfig' import { CASE_SECTIONS } from './cases/fieldConfig'
import { useInlineEdit } from './cases/useInlineEdit' import { useInlineEdit } from './cases/useInlineEdit'
import { EditableField } from './cases/EditableField' import { EditableField } from './cases/EditableField'
import { requestDisclosure } from '@/services/disclosureService'
const FALLGRUPPEN_LABELS: Record<string, string> = { const FALLGRUPPEN_LABELS: Record<string, string> = {
onko: 'Onkologie', onko: 'Onkologie',
@ -396,6 +397,18 @@ function CaseDetail({
<StatusBadges c={caseData} /> <StatusBadges c={caseData} />
</div> </div>
{/* Disclosure status / request */}
{!isAdmin && caseData.disclosure_granted && caseData.disclosure_expires_at && (
<Alert>
<AlertDescription>
Personendaten sichtbar bis {formatDateTime(caseData.disclosure_expires_at)}
</AlertDescription>
</Alert>
)}
{!isAdmin && !caseData.disclosure_granted && (
<DisclosureRequestButton caseId={caseData.id} />
)}
{/* Static metadata (always read-only) */} {/* Static metadata (always read-only) */}
<div className="grid grid-cols-2 gap-3 text-sm"> <div className="grid grid-cols-2 gap-3 text-sm">
<ReadOnlyField label="Fall-ID" value={caseData.fall_id} /> <ReadOnlyField label="Fall-ID" value={caseData.fall_id} />
@ -405,11 +418,17 @@ function CaseDetail({
</div> </div>
{/* Editable sections from config */} {/* Editable sections from config */}
{CASE_SECTIONS.map((section) => ( {CASE_SECTIONS.map((section) => {
const visibleFields = section.fields.filter((field) => {
if (field.visibleTo === 'admin' && !isAdmin && !caseData.disclosure_granted) return false
return true
})
if (visibleFields.length === 0) return null
return (
<div key={section.title} className="border-t pt-3 space-y-3"> <div key={section.title} className="border-t pt-3 space-y-3">
<h3 className="text-sm font-semibold">{section.title}</h3> <h3 className="text-sm font-semibold">{section.title}</h3>
<div className="grid grid-cols-2 gap-3 text-sm"> <div className="grid grid-cols-2 gap-3 text-sm">
{section.fields.map((field) => ( {visibleFields.map((field) => (
<div key={field.key} className={field.colSpan === 2 ? 'col-span-2' : ''}> <div key={field.key} className={field.colSpan === 2 ? 'col-span-2' : ''}>
<EditableField <EditableField
field={field} field={field}
@ -421,7 +440,8 @@ function CaseDetail({
))} ))}
</div> </div>
</div> </div>
))} )
})}
{/* ICD section — own endpoint with validation */} {/* ICD section — own endpoint with validation */}
<div className="border-t pt-3 space-y-2"> <div className="border-t pt-3 space-y-2">
@ -474,6 +494,63 @@ function CaseDetail({
) )
} }
function DisclosureRequestButton({ caseId }: { caseId: number }) {
const [open, setOpen] = useState(false)
const [reason, setReason] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [success, setSuccess] = useState(false)
const submit = async () => {
if (!reason.trim()) return
setLoading(true)
setError('')
try {
await requestDisclosure(caseId, reason.trim())
setSuccess(true)
setOpen(false)
} catch (err: any) {
const detail = err.response?.data?.detail
setError(typeof detail === 'string' ? detail : 'Fehler beim Senden der Anfrage')
} finally {
setLoading(false)
}
}
if (success) {
return <p className="text-sm text-muted-foreground">Freigabe-Anfrage gesendet.</p>
}
return (
<>
<Button size="sm" variant="outline" onClick={() => setOpen(true)}>
Personendaten anfordern
</Button>
{open && (
<div className="space-y-2 border rounded-lg p-3">
<p className="text-sm text-muted-foreground">
Begründung für die Einsicht in Personendaten:
</p>
<Input
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="z.B. KVNR-Fehler, Identifikation nötig"
/>
{error && <p className="text-sm text-red-600">{error}</p>}
<div className="flex gap-2">
<Button size="sm" onClick={submit} disabled={loading || !reason.trim()}>
{loading ? 'Senden...' : 'Anfrage senden'}
</Button>
<Button size="sm" variant="outline" onClick={() => { setOpen(false); setError('') }}>
Abbrechen
</Button>
</div>
</div>
)}
</>
)
}
function ReadOnlyField({ label, value }: { label: string; value: string | null | undefined }) { function ReadOnlyField({ label, value }: { label: string; value: string | null | undefined }) {
return ( return (
<div> <div>

View file

@ -8,6 +8,7 @@ export interface FieldConfig {
type: FieldType type: FieldType
colSpan?: 1 | 2 colSpan?: 1 | 2
editableBy: 'admin' | 'all' editableBy: 'admin' | 'all'
visibleTo?: 'admin' | 'all'
placeholder?: string placeholder?: string
options?: { value: string; label: string }[] options?: { value: string; label: string }[]
} }
@ -28,10 +29,10 @@ export const CASE_SECTIONS: SectionConfig[] = [
{ {
title: 'Persönliche Daten', title: 'Persönliche Daten',
fields: [ fields: [
{ key: 'anrede', label: 'Anrede', type: 'select', editableBy: 'admin', options: ANREDE_OPTIONS }, { key: 'anrede', label: 'Anrede', type: 'select', editableBy: 'admin', visibleTo: 'admin', options: ANREDE_OPTIONS },
{ key: 'vorname', label: 'Vorname', type: 'text', editableBy: 'admin', placeholder: 'Vorname' }, { key: 'vorname', label: 'Vorname', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Vorname' },
{ key: 'nachname', label: 'Nachname', type: 'text', editableBy: 'admin', placeholder: 'Nachname' }, { key: 'nachname', label: 'Nachname', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Nachname' },
{ key: 'geburtsdatum', label: 'Geburtsdatum', type: 'date', editableBy: 'admin' }, { key: 'geburtsdatum', label: 'Geburtsdatum', type: 'date', editableBy: 'admin', visibleTo: 'admin' },
{ key: 'kvnr', label: 'KVNR', type: 'text', editableBy: 'all', placeholder: 'z.B. A123456789' }, { key: 'kvnr', label: 'KVNR', type: 'text', editableBy: 'all', placeholder: 'z.B. A123456789' },
{ key: 'versicherung', label: 'Versicherung', type: 'text', editableBy: 'admin', placeholder: 'z.B. DAK' }, { key: 'versicherung', label: 'Versicherung', type: 'text', editableBy: 'admin', placeholder: 'z.B. DAK' },
], ],