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" },
|
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 }) {
|
function StatusBadge({ status }: { status: DSGVOStatus }) {
|
||||||
const m = STATUS_META[status] ?? { label: status, variant: "secondary" as const };
|
const m = STATUS_META[status] ?? { label: status, variant: "secondary" as const };
|
||||||
return <Badge variant={m.variant}>{m.label}</Badge>;
|
return <Badge variant={m.variant}>{m.label}</Badge>;
|
||||||
@@ -274,7 +280,7 @@ export function DSGVOTab({ canManage }: DSGVOTabProps) {
|
|||||||
<StatusBadge status={r.status} />
|
<StatusBadge status={r.status} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
{r.result_summary?.total ?? 0}
|
{r.result_summary?.total_hits ?? 0}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Button
|
<Button
|
||||||
@@ -389,7 +395,7 @@ export function DSGVOTab({ canManage }: DSGVOTabProps) {
|
|||||||
{detailSummary && (
|
{detailSummary && (
|
||||||
<div className="flex flex-wrap gap-2 text-sm">
|
<div className="flex flex-wrap gap-2 text-sm">
|
||||||
<Badge variant="secondary">
|
<Badge variant="secondary">
|
||||||
Treffer gesamt: {detailSummary.total}
|
Treffer gesamt: {detailSummary.total_hits}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="destructive">
|
<Badge variant="destructive">
|
||||||
Abgelehnt: {detailSummary.rejected}
|
Abgelehnt: {detailSummary.rejected}
|
||||||
@@ -449,13 +455,14 @@ export function DSGVOTab({ canManage }: DSGVOTabProps) {
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{detailSummary.mails.map((m: DSGVOMailResult) => {
|
{detailSummary.mails.map((m: DSGVOMailResult) => {
|
||||||
|
const status = mailStatus(m);
|
||||||
const sm =
|
const sm =
|
||||||
MAIL_STATUS_META[m.status] ?? {
|
MAIL_STATUS_META[status] ?? {
|
||||||
label: m.status,
|
label: status,
|
||||||
variant: "secondary" as const,
|
variant: "secondary" as const,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<TableRow key={m.id}>
|
<TableRow key={m.mail_id}>
|
||||||
<TableCell className="max-w-xs break-words">
|
<TableCell className="max-w-xs break-words">
|
||||||
{m.subject || (
|
{m.subject || (
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
@@ -464,7 +471,7 @@ export function DSGVOTab({ canManage }: DSGVOTabProps) {
|
|||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="whitespace-nowrap">
|
<TableCell className="whitespace-nowrap">
|
||||||
{formatDay(m.received_at)}
|
{formatDay(m.date)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant={sm.variant} title={m.reason}>
|
<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";
|
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 {
|
export interface DSGVOMailResult {
|
||||||
id: string;
|
mail_id: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
received_at: string;
|
date: string | null;
|
||||||
status: DSGVOMailStatus;
|
|
||||||
reason: string;
|
|
||||||
retain_until: 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 {
|
export interface DSGVOResultSummary {
|
||||||
total: number;
|
total_hits: number;
|
||||||
rejected: number;
|
rejected: number;
|
||||||
deletable: number;
|
deletable: number;
|
||||||
deleted: number;
|
deleted: number;
|
||||||
@@ -43,8 +48,8 @@ export interface CreateDSGVORequestInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getDSGVORequests(): Promise<DSGVORequest[]> {
|
export async function getDSGVORequests(): Promise<DSGVORequest[]> {
|
||||||
const data = await request<DSGVORequest[] | null>("/api/admin/dsgvo");
|
const data = await request<{ requests: DSGVORequest[] | null }>("/api/admin/dsgvo");
|
||||||
return data ?? [];
|
return data.requests ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDSGVORequest(id: number): Promise<DSGVORequest> {
|
export async function getDSGVORequest(id: number): Promise<DSGVORequest> {
|
||||||
|
|||||||
Reference in New Issue
Block a user