mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 16:03:41 +00:00
Unicode escapes like \u00e4 render as literal text in JSX content instead of the intended German umlauts (ä, ü, Ü). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
418 lines
13 KiB
TypeScript
418 lines
13 KiB
TypeScript
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
import {
|
|
Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow,
|
|
} from '@/components/ui/table'
|
|
|
|
const FALLGRUPPEN_LABELS: Record<string, string> = {
|
|
onko: 'Onkologie',
|
|
kardio: 'Kardiologie',
|
|
intensiv: 'Intensivmedizin',
|
|
galle: 'Gallenblase',
|
|
sd: 'Schilddrüse',
|
|
}
|
|
|
|
const FALLGRUPPEN_KEYS = ['onko', 'kardio', 'intensiv', 'galle', 'sd'] as const
|
|
|
|
function fmt(n: number): string {
|
|
return n.toLocaleString('de-DE')
|
|
}
|
|
|
|
function NoData() {
|
|
return (
|
|
<p className="py-8 text-center text-muted-foreground">Keine Daten verfügbar.</p>
|
|
)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sheet 1 — KW gesamt
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function Sheet1({ data }: { data: any }) {
|
|
if (!data?.weekly?.length) return <NoData />
|
|
|
|
const COLS = [
|
|
{ key: 'erstberatungen', label: 'Erstberatungen' },
|
|
{ key: 'unterlagen', label: 'Unterlagen' },
|
|
{ key: 'ablehnungen', label: 'Ablehnungen' },
|
|
{ key: 'keine_rm', label: 'Keine Rückmeldung' },
|
|
{ key: 'gutachten', label: 'Gutachten' },
|
|
] as const
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const rows = (data.weekly as any[]).filter((row) =>
|
|
COLS.some((c) => (row[c.key] ?? 0) !== 0),
|
|
)
|
|
|
|
const totals = COLS.reduce(
|
|
(acc, c) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
acc[c.key] = rows.reduce((s: number, r: any) => s + (r[c.key] ?? 0), 0)
|
|
return acc
|
|
},
|
|
{} as Record<string, number>,
|
|
)
|
|
|
|
return (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>KW</TableHead>
|
|
{COLS.map((c) => (
|
|
<TableHead key={c.key} className="text-right">{c.label}</TableHead>
|
|
))}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rows.map((row) => (
|
|
<TableRow key={row.kw}>
|
|
<TableCell>KW {row.kw}</TableCell>
|
|
{COLS.map((c) => (
|
|
<TableCell key={c.key} className="text-right">{fmt(row[c.key] ?? 0)}</TableCell>
|
|
))}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
<TableFooter>
|
|
<TableRow>
|
|
<TableCell className="font-bold">Gesamt</TableCell>
|
|
{COLS.map((c) => (
|
|
<TableCell key={c.key} className="text-right font-bold">{fmt(totals[c.key])}</TableCell>
|
|
))}
|
|
</TableRow>
|
|
</TableFooter>
|
|
</Table>
|
|
)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sheet 2 — Fachgebiete
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function Sheet2({ data, fallgruppen }: { data: any; fallgruppen: readonly string[] }) {
|
|
if (!data?.weekly?.length) return <NoData />
|
|
|
|
const SUB_COLS = ['anzahl', 'gutachten', 'keine_rm'] as const
|
|
const SUB_LABELS: Record<string, string> = {
|
|
anzahl: 'Anzahl',
|
|
gutachten: 'Gutachten',
|
|
keine_rm: 'Keine RM',
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const isRowEmpty = (row: any) =>
|
|
fallgruppen.every((fg) =>
|
|
SUB_COLS.every((sc) => (row[fg]?.[sc] ?? 0) === 0),
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const rows = (data.weekly as any[]).filter((row) => !isRowEmpty(row))
|
|
|
|
const totals = fallgruppen.reduce(
|
|
(acc, fg) => {
|
|
acc[fg] = SUB_COLS.reduce(
|
|
(sub, sc) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
sub[sc] = rows.reduce((s: number, r: any) => s + (r[fg]?.[sc] ?? 0), 0)
|
|
return sub
|
|
},
|
|
{} as Record<string, number>,
|
|
)
|
|
return acc
|
|
},
|
|
{} as Record<string, Record<string, number>>,
|
|
)
|
|
|
|
return (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead rowSpan={2} className="align-bottom">KW</TableHead>
|
|
{fallgruppen.map((fg) => (
|
|
<TableHead key={fg} colSpan={SUB_COLS.length} className="text-center border-l">
|
|
{FALLGRUPPEN_LABELS[fg] ?? fg}
|
|
</TableHead>
|
|
))}
|
|
</TableRow>
|
|
<TableRow>
|
|
{fallgruppen.map((fg) =>
|
|
SUB_COLS.map((sc) => (
|
|
<TableHead key={`${fg}-${sc}`} className="text-right border-l first:border-l">
|
|
{SUB_LABELS[sc]}
|
|
</TableHead>
|
|
)),
|
|
)}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rows.map((row) => (
|
|
<TableRow key={row.kw}>
|
|
<TableCell>KW {row.kw}</TableCell>
|
|
{fallgruppen.map((fg) =>
|
|
SUB_COLS.map((sc) => (
|
|
<TableCell key={`${fg}-${sc}`} className="text-right border-l">
|
|
{fmt(row[fg]?.[sc] ?? 0)}
|
|
</TableCell>
|
|
)),
|
|
)}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
<TableFooter>
|
|
<TableRow>
|
|
<TableCell className="font-bold">Gesamt</TableCell>
|
|
{fallgruppen.map((fg) =>
|
|
SUB_COLS.map((sc) => (
|
|
<TableCell key={`${fg}-${sc}`} className="text-right font-bold border-l">
|
|
{fmt(totals[fg][sc])}
|
|
</TableCell>
|
|
)),
|
|
)}
|
|
</TableRow>
|
|
</TableFooter>
|
|
</Table>
|
|
)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sheet 3 — Gutachten
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function Sheet3({ data, fallgruppen }: { data: any; fallgruppen: readonly string[] }) {
|
|
if (!data?.weekly?.length) return <NoData />
|
|
|
|
const SUB_COLS = ['gutachten', 'alternative', 'bestaetigung'] as const
|
|
const SUB_LABELS: Record<string, string> = {
|
|
gutachten: 'Gutachten',
|
|
alternative: 'Alternative',
|
|
bestaetigung: 'Bestätigung',
|
|
}
|
|
|
|
const GROUP_KEYS = ['gesamt', ...fallgruppen] as const
|
|
|
|
const GROUP_LABELS: Record<string, string> = {
|
|
gesamt: 'Gesamt',
|
|
...FALLGRUPPEN_LABELS,
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const isRowEmpty = (row: any) =>
|
|
GROUP_KEYS.every((grp) =>
|
|
SUB_COLS.every((sc) => (row[grp]?.[sc] ?? 0) === 0),
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const rows = (data.weekly as any[]).filter((row) => !isRowEmpty(row))
|
|
|
|
const totals = GROUP_KEYS.reduce(
|
|
(acc, grp) => {
|
|
acc[grp] = SUB_COLS.reduce(
|
|
(sub, sc) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
sub[sc] = rows.reduce((s: number, r: any) => s + (r[grp]?.[sc] ?? 0), 0)
|
|
return sub
|
|
},
|
|
{} as Record<string, number>,
|
|
)
|
|
return acc
|
|
},
|
|
{} as Record<string, Record<string, number>>,
|
|
)
|
|
|
|
return (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead rowSpan={2} className="align-bottom">KW</TableHead>
|
|
{GROUP_KEYS.map((grp) => (
|
|
<TableHead key={grp} colSpan={SUB_COLS.length} className="text-center border-l">
|
|
{GROUP_LABELS[grp] ?? grp}
|
|
</TableHead>
|
|
))}
|
|
</TableRow>
|
|
<TableRow>
|
|
{GROUP_KEYS.map((grp) =>
|
|
SUB_COLS.map((sc) => (
|
|
<TableHead key={`${grp}-${sc}`} className="text-right border-l">
|
|
{SUB_LABELS[sc]}
|
|
</TableHead>
|
|
)),
|
|
)}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rows.map((row) => (
|
|
<TableRow key={row.kw}>
|
|
<TableCell>KW {row.kw}</TableCell>
|
|
{GROUP_KEYS.map((grp) =>
|
|
SUB_COLS.map((sc) => (
|
|
<TableCell key={`${grp}-${sc}`} className="text-right border-l">
|
|
{fmt(row[grp]?.[sc] ?? 0)}
|
|
</TableCell>
|
|
)),
|
|
)}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
<TableFooter>
|
|
<TableRow>
|
|
<TableCell className="font-bold">Gesamt</TableCell>
|
|
{GROUP_KEYS.map((grp) =>
|
|
SUB_COLS.map((sc) => (
|
|
<TableCell key={`${grp}-${sc}`} className="text-right font-bold border-l">
|
|
{fmt(totals[grp][sc])}
|
|
</TableCell>
|
|
)),
|
|
)}
|
|
</TableRow>
|
|
</TableFooter>
|
|
</Table>
|
|
)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sheet 4 — Therapieänderungen
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function Sheet4({ data }: { data: any }) {
|
|
if (!data?.weekly?.length) return <NoData />
|
|
|
|
const COLS = [
|
|
{ key: 'gutachten', label: 'Gutachten' },
|
|
{ key: 'ta_ja', label: 'Therapieänderung Ja' },
|
|
{ key: 'ta_nein', label: 'Therapieänderung Nein' },
|
|
{ key: 'diagnosekorrektur', label: 'Diagnosekorrektur' },
|
|
{ key: 'unterversorgung', label: 'Unterversorgung' },
|
|
{ key: 'uebertherapie', label: 'Übertherapie' },
|
|
] as const
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const rows = (data.weekly as any[]).filter((row) =>
|
|
COLS.some((c) => (row[c.key] ?? 0) !== 0),
|
|
)
|
|
|
|
const totals = COLS.reduce(
|
|
(acc, c) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
acc[c.key] = rows.reduce((s: number, r: any) => s + (r[c.key] ?? 0), 0)
|
|
return acc
|
|
},
|
|
{} as Record<string, number>,
|
|
)
|
|
|
|
return (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>KW</TableHead>
|
|
{COLS.map((c) => (
|
|
<TableHead key={c.key} className="text-right">{c.label}</TableHead>
|
|
))}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rows.map((row) => (
|
|
<TableRow key={row.kw}>
|
|
<TableCell>KW {row.kw}</TableCell>
|
|
{COLS.map((c) => (
|
|
<TableCell key={c.key} className="text-right">{fmt(row[c.key] ?? 0)}</TableCell>
|
|
))}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
<TableFooter>
|
|
<TableRow>
|
|
<TableCell className="font-bold">Gesamt</TableCell>
|
|
{COLS.map((c) => (
|
|
<TableCell key={c.key} className="text-right font-bold">{fmt(totals[c.key])}</TableCell>
|
|
))}
|
|
</TableRow>
|
|
</TableFooter>
|
|
</Table>
|
|
)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sheet 5 — ICD onko
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function Sheet5({ data }: { data: any }) {
|
|
if (!data?.icd_codes?.length) return <NoData />
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const rows = data.icd_codes as any[]
|
|
const total = rows.reduce((s: number, r) => s + (r.count ?? 0), 0)
|
|
|
|
return (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>ICD-Code</TableHead>
|
|
<TableHead className="text-right">Anzahl</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rows.map((row) => (
|
|
<TableRow key={row.icd}>
|
|
<TableCell>{row.icd}</TableCell>
|
|
<TableCell className="text-right">{fmt(row.count ?? 0)}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
<TableFooter>
|
|
<TableRow>
|
|
<TableCell className="font-bold">Gesamt</TableCell>
|
|
<TableCell className="text-right font-bold">{fmt(total)}</TableCell>
|
|
</TableRow>
|
|
</TableFooter>
|
|
</Table>
|
|
)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ReportViewer — Main Component
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export function ReportViewer({ data }: { data: Record<string, any> }) {
|
|
const fallgruppen: readonly string[] = data?.fallgruppen ?? FALLGRUPPEN_KEYS
|
|
const hasOnko = fallgruppen.includes('onko')
|
|
|
|
return (
|
|
<Tabs defaultValue="sheet1" className="w-full">
|
|
<TabsList className="w-full justify-start">
|
|
<TabsTrigger value="sheet1">KW gesamt</TabsTrigger>
|
|
<TabsTrigger value="sheet2">Fachgebiete</TabsTrigger>
|
|
<TabsTrigger value="sheet3">Gutachten</TabsTrigger>
|
|
<TabsTrigger value="sheet4">Therapieänderungen</TabsTrigger>
|
|
{hasOnko && <TabsTrigger value="sheet5">ICD onko</TabsTrigger>}
|
|
</TabsList>
|
|
|
|
<TabsContent value="sheet1">
|
|
<Sheet1 data={data?.sheet1} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="sheet2">
|
|
<Sheet2 data={data?.sheet2} fallgruppen={fallgruppen} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="sheet3">
|
|
<Sheet3 data={data?.sheet3} fallgruppen={fallgruppen} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="sheet4">
|
|
<Sheet4 data={data?.sheet4} />
|
|
</TabsContent>
|
|
|
|
{hasOnko && (
|
|
<TabsContent value="sheet5">
|
|
<Sheet5 data={data?.sheet5} />
|
|
</TabsContent>
|
|
)}
|
|
</Tabs>
|
|
)
|
|
}
|