feat: Jahresbericht-PDF pro Volk/Stand

Neue PDF-Reports im Export-Modul: colony-annual-report (Durchsichten,
Behandlungen, Varroakontrollen, Schwarmkontrolle, Ernten eines Volkes fuer
ein Jahr) und apiary-annual-report (Voelker-Uebersicht + Ernten eines
Stands fuer ein Jahr). Jahr ist optional, faellt auf das aktuelle Jahr
zurueck.

Frontend: "Jahresbericht"-Button auf Volk- und Stand-Detailseite, laedt
das PDF ueber den bestehenden Blob-Download-Mechanismus herunter.
This commit is contained in:
Nick Szaunig committed 2026-08-25 11:51:41 +02:00
1 parent f1298eff08
commit 3f7c4ed404
8 files changed
+630 -26

No files matched your search

@@ -2,13 +2,16 @@
import Link from "next/link";
import { useParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { fetchApiary } from "@/lib/apiaries/api";
import { exportApiaryAnnualReportPdf } from "@/lib/export/api";
import { QrCodeDialog } from "@/components/qr-code-dialog";
import { DocumentUploadCard } from "@/components/document-upload-card";
import { Button } from "@/components/ui/button";
import { ApiaryEditCard } from "./apiary-edit-card";
const CURRENT_YEAR = new Date().getFullYear();
export default function ApiaryDetailPage() {
const params = useParams<{ id: string }>();
const { data: apiary, isLoading, isError } = useQuery({
@@ -16,6 +19,10 @@ export default function ApiaryDetailPage() {
queryFn: () => fetchApiary(params.id),
});
const exportReportMutation = useMutation({
mutationFn: () => exportApiaryAnnualReportPdf(params.id, CURRENT_YEAR),
});
if (isLoading) {
return <p className="p-4 text-sm text-muted-foreground sm:p-6">Lädt…</p>;
}
@@ -34,13 +41,23 @@ export default function ApiaryDetailPage() {
</div>
<ApiaryEditCard key={apiary.id} apiary={apiary} />
<DocumentUploadCard entityType="APIARY" entityId={apiary.id} />
<Button
className="w-fit"
nativeButton={false}
render={<Link href={`/harvests/new?apiaryId=${apiary.id}`} />}
>
Neue Ernte
</Button>
<div className="flex flex-wrap gap-2">
<Button
nativeButton={false}
render={<Link href={`/harvests/new?apiaryId=${apiary.id}`} />}
>
Neue Ernte
</Button>
<Button
variant="outline"
disabled={exportReportMutation.isPending}
onClick={() => exportReportMutation.mutate()}
>
{exportReportMutation.isPending
? "Wird erstellt…"
: `Jahresbericht ${CURRENT_YEAR}`}
</Button>
</div>
</main>
);
}
@@ -1,10 +1,12 @@
"use client";
import { useParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { fetchColony } from "@/lib/colonies/api";
import { exportColonyAnnualReportPdf } from "@/lib/export/api";
import { QrCodeDialog } from "@/components/qr-code-dialog";
import { DocumentUploadCard } from "@/components/document-upload-card";
import { Button } from "@/components/ui/button";
import { ColonyEditCard } from "./colony-edit-card";
import { ColonyActions } from "./colony-actions";
import { ColonyHistoryCard } from "./colony-history";
@@ -14,6 +16,8 @@ import { ColonySwarmControlCard } from "./colony-swarm-control";
import { ColonyTreatmentsCard } from "./colony-treatments";
import { ColonyFeedingsCard } from "./colony-feedings";
const CURRENT_YEAR = new Date().getFullYear();
export default function ColonyDetailPage() {
const params = useParams<{ id: string }>();
const { data: colony, isLoading, isError } = useQuery({
@@ -21,6 +25,10 @@ export default function ColonyDetailPage() {
queryFn: () => fetchColony(params.id),
});
const exportReportMutation = useMutation({
mutationFn: () => exportColonyAnnualReportPdf(params.id, CURRENT_YEAR),
});
if (isLoading) {
return <p className="p-4 text-sm text-muted-foreground sm:p-6">Lädt…</p>;
}
@@ -34,7 +42,16 @@ export default function ColonyDetailPage() {
return (
<main className="flex flex-col gap-4 p-4 sm:p-6">
<div className="flex justify-end">
<div className="flex flex-wrap items-center justify-end gap-2">
<Button
variant="outline"
disabled={exportReportMutation.isPending}
onClick={() => exportReportMutation.mutate()}
>
{exportReportMutation.isPending
? "Wird erstellt…"
: `Jahresbericht ${CURRENT_YEAR}`}
</Button>
<QrCodeDialog entityType="colonies" id={colony.id} label="QR-Code für Durchsicht" />
</div>
<ColonyEditCard key={colony.id} colony={colony} />