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

@@ -58,6 +58,41 @@ export async function apiFetchBlob(path: string, fallbackFilename: string): Prom
return { blob, filename };
}
async function rawFetchFormData(path: string, formData: FormData): Promise<Response> {
const token = getAccessToken();
return fetch(`${API_URL}${path}`, {
method: 'POST',
body: formData,
credentials: 'include',
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
});
}
export async function apiFetchFormData<T>(path: string, formData: FormData): Promise<T> {
let response = await rawFetchFormData(path, formData);
if (response.status === 401) {
const refreshed = await refreshRequest();
if (refreshed) {
setAccessToken(refreshed.accessToken);
response = await rawFetchFormData(path, formData);
}
}
if (!response.ok) {
const body: unknown = await response.json().catch(() => ({}));
const message =
typeof body === 'object' && body !== null && 'message' in body
? String((body as { message: unknown }).message)
: 'Request failed';
throw new ApiError(response.status, message);
}
return response.json() as Promise<T>;
}
export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise<T> {
let response = await rawFetch(path, init);