feat(frontend): Dokumente-Upload-Widget (Modul 29 Frontend)

Backend war bereits fertig, das Frontend fehlte bislang. Neue
gemeinsame DocumentUploadCard-Komponente (Liste hochgeladener Dateien,
Upload per Dateiauswahl, Download, Löschen mit Bestätigungsdialog da
unumkehrbar) plus dazugehöriges src/lib/documents/ API-Modul.

apiFetch erzwingt bislang immer Content-Type: application/json, was
multipart-Uploads verhindert hätte – daher neue apiFetchFormData-
Hilfsfunktion in api-client.ts ergänzt (kein manuell gesetzter
Content-Type, damit der Browser die multipart-Boundary selbst setzt).

Eingebunden auf allen Detailseiten, die laut TASKS.md Modul 29 vorsehen
und tatsächlich eine eigene Route haben: Bienenstand, Volk, Königin,
Honigcharge. Durchsicht hat keine eigene Detailseite (nur Einträge in
der Volk-Unterliste) und wird daher vorerst ausgelassen.

Live verifiziert: Upload, Download und Löschen (inkl. Bestätigungsdialog)
funktionieren End-to-End gegen das Backend, zero Konsolenfehler auf
allen vier Detailseiten.
This commit is contained in:
Nick Szaunig committed 2026-08-24 16:43:18 +02:00
1 parent 9a32c6c11d
commit a21563cc76
9 files changed
+242 -1

No files matched your search

+32
View File
@@ -0,0 +1,32 @@
import { apiFetch, apiFetchBlob, apiFetchFormData } from "@/lib/auth/api-client";
import { downloadBlob } from "@/lib/export/api";
import { DocumentEntityType, DocumentSummary } from "./types";
export function fetchDocuments(
entityType: DocumentEntityType,
entityId: string,
): Promise<DocumentSummary[]> {
const params = new URLSearchParams({ entityType, entityId });
return apiFetch<DocumentSummary[]>(`/documents?${params.toString()}`);
}
export function uploadDocument(
entityType: DocumentEntityType,
entityId: string,
file: File,
): Promise<DocumentSummary> {
const formData = new FormData();
formData.append("file", file);
formData.append("entityType", entityType);
formData.append("entityId", entityId);
return apiFetchFormData<DocumentSummary>("/documents", formData);
}
export function deleteDocument(id: string): Promise<void> {
return apiFetch<void>(`/documents/${id}`, { method: "DELETE" });
}
export async function downloadDocument(id: string, fileName: string): Promise<void> {
const { blob, filename } = await apiFetchBlob(`/documents/${id}/download`, fileName);
downloadBlob(blob, filename);
}
@@ -0,0 +1,12 @@
export type DocumentEntityType = "APIARY" | "COLONY" | "QUEEN" | "INSPECTION" | "HONEY_BATCH";
export interface DocumentSummary {
id: string;
entityType: DocumentEntityType;
entityId: string;
fileName: string;
mimeType: string;
sizeBytes: number;
uploadedByUserId: string | null;
createdAt: string;
}