Freigabe-Anfragen
+
setStatusFilter(v as StatusFilter)}>
+
+ Alle
+ Ausstehend
+ Genehmigt
+ Abgelehnt
+
+
+
{isLoading ? (
{[1, 2, 3].map((i) => (
-
+
))}
) : requests.length === 0 ? (
-
Keine offenen Anfragen.
+
Keine Anfragen gefunden.
) : (
-
- {requests.map((dr) => (
-
-
-
-
- Fall {dr.fall_id || dr.case_id}
-
- {dr.status}
-
-
-
-
- Angefragt von:{' '}
- {dr.requester_username || `User #${dr.requester_id}`}
-
-
- Begründung:{' '}
- {dr.reason}
-
-
- {new Date(dr.created_at).toLocaleString('de-DE')}
-
-
-
-
-
-
-
- ))}
+
+
+
+
+ Fall-ID
+ Antragsteller
+ Begründung
+ Status
+ Datum
+ Gültig bis
+ Aktionen
+
+
+
+ {requests.map((dr) => (
+
+ {dr.fall_id || `#${dr.case_id}`}
+ {dr.requester_username || `User #${dr.requester_id}`}
+ {dr.reason}
+ {statusBadge(dr)}
+ {new Date(dr.created_at).toLocaleDateString('de-DE')}
+
+ {dr.expires_at
+ ? new Date(dr.expires_at).toLocaleString('de-DE')
+ : '—'}
+
+
+
+ {dr.status === 'pending' && (
+ <>
+
+
+ >
+ )}
+ {isActiveApproved(dr) && (
+
+ )}
+
+
+
+ ))}
+
+
)}
diff --git a/frontend/src/pages/GutachtenStatistikPage.tsx b/frontend/src/pages/GutachtenStatistikPage.tsx
new file mode 100644
index 0000000..d51e9aa
--- /dev/null
+++ b/frontend/src/pages/GutachtenStatistikPage.tsx
@@ -0,0 +1,17 @@
+import { BarChart3 } from 'lucide-react'
+import { Card, CardContent } from '@/components/ui/card'
+
+export function GutachtenStatistikPage() {
+ return (
+
+
Gutachten-Statistik
+
+
+
+ Kommt bald
+ Diese Seite wird in Kürze mit detaillierten Gutachten-Statistiken verfügbar sein.
+
+
+
+ )
+}
diff --git a/frontend/src/pages/MyDisclosuresPage.tsx b/frontend/src/pages/MyDisclosuresPage.tsx
new file mode 100644
index 0000000..a776055
--- /dev/null
+++ b/frontend/src/pages/MyDisclosuresPage.tsx
@@ -0,0 +1,94 @@
+import { ShieldOff } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import { Badge } from '@/components/ui/badge'
+import { Skeleton } from '@/components/ui/skeleton'
+import {
+ Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
+} from '@/components/ui/table'
+import { useMyDisclosures, useRevokeDisclosure } from '@/hooks/useDisclosures'
+import type { DisclosureRequest } from '@/types'
+
+function statusBadge(dr: DisclosureRequest) {
+ const now = new Date()
+ if (dr.status === 'pending') {
+ return
Ausstehend
+ }
+ if (dr.status === 'rejected') {
+ return
Abgelehnt
+ }
+ // approved
+ if (dr.expires_at && new Date(dr.expires_at) <= now) {
+ return
Abgelaufen
+ }
+ return
Genehmigt
+}
+
+function isActive(dr: DisclosureRequest): boolean {
+ if (dr.status !== 'approved') return false
+ if (!dr.expires_at) return false
+ return new Date(dr.expires_at) > new Date()
+}
+
+export function MyDisclosuresPage() {
+ const { data: requests = [], isLoading } = useMyDisclosures()
+ const revokeMutation = useRevokeDisclosure()
+
+ return (
+
+
Meine Freigaben
+
+ {isLoading ? (
+
+ {[1, 2, 3].map((i) => (
+
+ ))}
+
+ ) : requests.length === 0 ? (
+
Sie haben noch keine Freigaben angefragt.
+ ) : (
+
+
+
+
+ Fall-ID
+ Begründung
+ Status
+ Angefragt am
+ Gültig bis
+ Aktion
+
+
+
+ {requests.map((dr) => (
+
+ {dr.fall_id || `#${dr.case_id}`}
+ {dr.reason}
+ {statusBadge(dr)}
+ {new Date(dr.created_at).toLocaleDateString('de-DE')}
+
+ {dr.expires_at
+ ? new Date(dr.expires_at).toLocaleString('de-DE')
+ : '—'}
+
+
+ {isActive(dr) && (
+
+ )}
+
+
+ ))}
+
+
+
+ )}
+
+ )
+}
diff --git a/frontend/src/pages/__tests__/DisclosuresPage.test.tsx b/frontend/src/pages/__tests__/DisclosuresPage.test.tsx
index 5ca8797..9778a56 100644
--- a/frontend/src/pages/__tests__/DisclosuresPage.test.tsx
+++ b/frontend/src/pages/__tests__/DisclosuresPage.test.tsx
@@ -15,7 +15,7 @@ describe('DisclosuresPage', () => {
await waitFor(() => {
// The mock handler returns [mockDisclosureRequest] with fall_id '2026-06-onko-A123456789'
- expect(screen.getByText('Fall 2026-06-onko-A123456789')).toBeInTheDocument()
+ expect(screen.getByText('2026-06-onko-A123456789')).toBeInTheDocument()
})
// Should show requester username
@@ -24,8 +24,9 @@ describe('DisclosuresPage', () => {
// Should show the reason
expect(screen.getByText('Benötige vollständige Patientendaten für Rückruf.')).toBeInTheDocument()
- // Should show the status badge
- expect(screen.getByText('pending')).toBeInTheDocument()
+ // Should show the status badge (translated) — multiple "Ausstehend" exist (tab + badge)
+ const badges = screen.getAllByText('Ausstehend')
+ expect(badges.length).toBeGreaterThanOrEqual(1)
})
it('shows approve and reject buttons', async () => {
@@ -48,7 +49,7 @@ describe('DisclosuresPage', () => {
renderWithProviders(
)
await waitFor(() => {
- expect(screen.getByText('Keine offenen Anfragen.')).toBeInTheDocument()
+ expect(screen.getByText('Keine Anfragen gefunden.')).toBeInTheDocument()
})
})
@@ -66,8 +67,8 @@ describe('DisclosuresPage', () => {
// The heading is always visible
expect(screen.getByText('Freigabe-Anfragen')).toBeInTheDocument()
- // During loading, the "Keine offenen Anfragen." message should not be visible yet
- expect(screen.queryByText('Keine offenen Anfragen.')).not.toBeInTheDocument()
+ // During loading, the empty message should not be visible yet
+ expect(screen.queryByText('Keine Anfragen gefunden.')).not.toBeInTheDocument()
// Nor should any disclosure item be visible
expect(screen.queryByText('Genehmigen')).not.toBeInTheDocument()
})
diff --git a/frontend/src/services/disclosureService.ts b/frontend/src/services/disclosureService.ts
index d9433e0..6e8dbf3 100644
--- a/frontend/src/services/disclosureService.ts
+++ b/frontend/src/services/disclosureService.ts
@@ -21,3 +21,18 @@ export async function reviewDisclosure(requestId: number, status: 'approved' | '
const res = await api.put
(`/admin/disclosure-requests/${requestId}`, { status })
return res.data
}
+
+export async function getMyDisclosureRequests(): Promise {
+ const res = await api.get('/cases/my-disclosure-requests')
+ return res.data
+}
+
+export async function revokeDisclosure(requestId: number): Promise {
+ const res = await api.put(`/cases/disclosure-requests/${requestId}/revoke`)
+ return res.data
+}
+
+export async function adminRevokeDisclosure(requestId: number): Promise {
+ const res = await api.put(`/admin/disclosure-requests/${requestId}/revoke`)
+ return res.data
+}