fix(PROJ-50): DSGVO-Tab lädt nicht — API-Contract-Mismatch Frontend/Backend
Backend liefert bei GET /api/admin/dsgvo ein Objekt {"requests":[...]},
Frontend erwartete rohes Array → .map() auf Nicht-Array warf Exception,
angezeigt als "DSGVO-Anträge konnten nicht geladen werden".
Zusätzlich stimmten DSGVOResultSummary/DSGVOMailResult-Feldnamen nicht mit
dem Go-JSON überein (total vs. total_hits; id/status/received_at existieren
im Backend nicht, stattdessen mail_id/deletable/deleted/date). Status wird
jetzt im Frontend aus deletable/deleted abgeleitet (mailStatus()).
Bug bestand seit Feature-Implementierung (2026-06-13), fiel erst jetzt beim
ersten Live-Test auf.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e91206c2b5
commit
804cd62201
@@ -60,6 +60,12 @@ const MAIL_STATUS_META: Record<
|
||||
deleted: { label: "Gelöscht", variant: "secondary" },
|
||||
};
|
||||
|
||||
function mailStatus(m: DSGVOMailResult): DSGVOMailStatus {
|
||||
if (m.deleted) return "deleted";
|
||||
if (m.deletable) return "deletable";
|
||||
return "rejected";
|
||||
}
|
||||
|
||||
function StatusBadge({ status }: { status: DSGVOStatus }) {
|
||||
const m = STATUS_META[status] ?? { label: status, variant: "secondary" as const };
|
||||
return <Badge variant={m.variant}>{m.label}</Badge>;
|
||||
@@ -274,7 +280,7 @@ export function DSGVOTab({ canManage }: DSGVOTabProps) {
|
||||
<StatusBadge status={r.status} />
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{r.result_summary?.total ?? 0}
|
||||
{r.result_summary?.total_hits ?? 0}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
@@ -389,7 +395,7 @@ export function DSGVOTab({ canManage }: DSGVOTabProps) {
|
||||
{detailSummary && (
|
||||
<div className="flex flex-wrap gap-2 text-sm">
|
||||
<Badge variant="secondary">
|
||||
Treffer gesamt: {detailSummary.total}
|
||||
Treffer gesamt: {detailSummary.total_hits}
|
||||
</Badge>
|
||||
<Badge variant="destructive">
|
||||
Abgelehnt: {detailSummary.rejected}
|
||||
@@ -449,13 +455,14 @@ export function DSGVOTab({ canManage }: DSGVOTabProps) {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{detailSummary.mails.map((m: DSGVOMailResult) => {
|
||||
const status = mailStatus(m);
|
||||
const sm =
|
||||
MAIL_STATUS_META[m.status] ?? {
|
||||
label: m.status,
|
||||
MAIL_STATUS_META[status] ?? {
|
||||
label: status,
|
||||
variant: "secondary" as const,
|
||||
};
|
||||
return (
|
||||
<TableRow key={m.id}>
|
||||
<TableRow key={m.mail_id}>
|
||||
<TableCell className="max-w-xs break-words">
|
||||
{m.subject || (
|
||||
<span className="text-muted-foreground">
|
||||
@@ -464,7 +471,7 @@ export function DSGVOTab({ canManage }: DSGVOTabProps) {
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
{formatDay(m.received_at)}
|
||||
{formatDay(m.date)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={sm.variant} title={m.reason}>
|
||||
|
||||
+12
-7
@@ -8,17 +8,22 @@ export type DSGVOStatus = "open" | "partial" | "completed" | "failed";
|
||||
|
||||
export type DSGVOMailStatus = "rejected" | "deletable" | "deleted";
|
||||
|
||||
// Feldnamen 1:1 zu internal/storage/dsgvo_requests.go (DSGVOAffectedMail).
|
||||
// Es gibt kein "status"-Feld im Backend — der Status wird aus
|
||||
// deletable/deleted abgeleitet (siehe mailStatus() im Tab).
|
||||
export interface DSGVOMailResult {
|
||||
id: string;
|
||||
mail_id: string;
|
||||
subject: string;
|
||||
received_at: string;
|
||||
status: DSGVOMailStatus;
|
||||
reason: string;
|
||||
date: string | null;
|
||||
retain_until: string | null;
|
||||
deletable: boolean;
|
||||
deleted: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
// Feldnamen 1:1 zu internal/storage/dsgvo_requests.go (DSGVOResultSummary).
|
||||
export interface DSGVOResultSummary {
|
||||
total: number;
|
||||
total_hits: number;
|
||||
rejected: number;
|
||||
deletable: number;
|
||||
deleted: number;
|
||||
@@ -43,8 +48,8 @@ export interface CreateDSGVORequestInput {
|
||||
}
|
||||
|
||||
export async function getDSGVORequests(): Promise<DSGVORequest[]> {
|
||||
const data = await request<DSGVORequest[] | null>("/api/admin/dsgvo");
|
||||
return data ?? [];
|
||||
const data = await request<{ requests: DSGVORequest[] | null }>("/api/admin/dsgvo");
|
||||
return data.requests ?? [];
|
||||
}
|
||||
|
||||
export async function getDSGVORequest(id: number): Promise<DSGVORequest> {
|
||||
|
||||
Reference in New Issue
Block a user