diff --git a/ApiCore-Frontend/src/app/(app)/colonies/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/page.tsx index 3cf8157..f1d2e59 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/page.tsx @@ -2,9 +2,10 @@ import { useState } from "react"; import Link from "next/link"; -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { fetchColonies } from "@/lib/colonies/api"; import { ColonyStatus } from "@/lib/colonies/types"; +import { exportColoniesCsv } from "@/lib/export/api"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { @@ -42,13 +43,24 @@ export default function ColoniesPage() { queryFn: () => fetchColonies({ status: status || undefined }), }); + const exportMutation = useMutation({ mutationFn: exportColoniesCsv }); + return (

Völker

- +
+ + +
{ }); } +export interface BlobDownload { + blob: Blob; + filename: string; +} + +function filenameFromContentDisposition(header: string | null, fallback: string): string { + const match = header ? /filename="([^"]+)"/.exec(header) : null; + return match?.[1] ?? fallback; +} + +export async function apiFetchBlob(path: string, fallbackFilename: string): Promise { + let response = await rawFetch(path, {}); + + if (response.status === 401) { + const refreshed = await refreshRequest(); + if (refreshed) { + setAccessToken(refreshed.accessToken); + response = await rawFetch(path, {}); + } + } + + if (!response.ok) { + throw new ApiError(response.status, 'Export fehlgeschlagen'); + } + + const blob = await response.blob(); + const filename = filenameFromContentDisposition( + response.headers.get('Content-Disposition'), + fallbackFilename, + ); + return { blob, filename }; +} + export async function apiFetch(path: string, init: RequestInit = {}): Promise { let response = await rawFetch(path, init); diff --git a/ApiCore-Frontend/src/lib/export/api.ts b/ApiCore-Frontend/src/lib/export/api.ts new file mode 100644 index 0000000..17850c1 --- /dev/null +++ b/ApiCore-Frontend/src/lib/export/api.ts @@ -0,0 +1,33 @@ +import { apiFetchBlob } from "@/lib/auth/api-client"; + +export function downloadBlob(blob: Blob, filename: string): void { + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +} + +export async function exportColoniesCsv(): Promise { + const { blob, filename } = await apiFetchBlob("/export/colonies.csv", "colonies.csv"); + downloadBlob(blob, filename); +} + +export async function exportHoneyBatchesCsv(): Promise { + const { blob, filename } = await apiFetchBlob( + "/export/honey-batches.csv", + "honey-batches.csv", + ); + downloadBlob(blob, filename); +} + +export async function exportHoneyBatchTraceabilityPdf(honeyBatchId: string): Promise { + const { blob, filename } = await apiFetchBlob( + `/export/honey-batch-traceability.pdf?honeyBatchId=${honeyBatchId}`, + "honey-batch-traceability.pdf", + ); + downloadBlob(blob, filename); +} diff --git a/TASKS.md b/TASKS.md index 6c10fde..0b09ab0 100644 --- a/TASKS.md +++ b/TASKS.md @@ -154,7 +154,7 @@ Abgeleitet aus [`docs/lastenheft.md`](docs/lastenheft.md) Abschnitt 26. Reihenfo - Endpunkte: `GET /api/v1/export/colonies.csv`, `GET /api/v1/export/honey-batches.csv`, `GET /api/v1/export/:report.pdf`, `GET /api/v1/export/:entity.json` - Frontend: Export-Buttons in den jeweiligen Listen-Views - [x] Backend-Modul fertig, getestet, grün, committet -- [ ] Frontend-Views fertig, getestet, grün, committet +- [x] Frontend-Views fertig, getestet, grün, committet ### MVP-Abschluss