From 1f71ac9e3c98e1babe1e4d1cf1ce62d61839a82c Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Sat, 22 Aug 2026 00:10:46 +0200 Subject: [PATCH] feat(frontend): Behandlungen-Modul (Modul 8) Mobile-optimiertes Erfassungsformular unter /treatments/new sowie "Behandlungen"-Ansicht auf der Volk-Detailseite. --- .../(app)/colonies/[id]/colony-treatments.tsx | 47 +++++ .../src/app/(app)/colonies/[id]/page.tsx | 2 + .../src/app/(app)/treatments/new/page.tsx | 168 ++++++++++++++++++ ApiCore-Frontend/src/lib/treatments/api.ts | 13 ++ ApiCore-Frontend/src/lib/treatments/types.ts | 30 ++++ TASKS.md | 2 +- 6 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-treatments.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/treatments/new/page.tsx create mode 100644 ApiCore-Frontend/src/lib/treatments/api.ts create mode 100644 ApiCore-Frontend/src/lib/treatments/types.ts diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-treatments.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-treatments.tsx new file mode 100644 index 0000000..1346c25 --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-treatments.tsx @@ -0,0 +1,47 @@ +"use client"; + +import Link from "next/link"; +import { useQuery } from "@tanstack/react-query"; +import { fetchColonyTreatments } from "@/lib/treatments/api"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +export function ColonyTreatmentsCard({ colonyId }: { colonyId: string }) { + const { data: treatments, isLoading } = useQuery({ + queryKey: ["colonies", colonyId, "treatments"], + queryFn: () => fetchColonyTreatments(colonyId), + }); + + return ( + + + Behandlungen + + + + {isLoading ?

Lädt…

: null} + {!isLoading && (!treatments || treatments.length === 0) ? ( +

Noch keine Behandlung dokumentiert.

+ ) : null} + {treatments?.map((treatment) => ( +
+

+ {new Date(treatment.date).toLocaleDateString("de-DE")} · {treatment.product} +

+

+ {treatment.amount ?? "—"} + {treatment.method ? ` · ${treatment.method}` : ""} +

+ {treatment.reason ?

Grund: {treatment.reason}

: null} +
+ ))} +
+
+ ); +} diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx index 7818941..d3a22d7 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx @@ -8,6 +8,7 @@ import { ColonyActions } from "./colony-actions"; import { ColonyHistoryCard } from "./colony-history"; import { ColonyInspectionsCard } from "./colony-inspections"; import { ColonyVarroaCard } from "./colony-varroa"; +import { ColonyTreatmentsCard } from "./colony-treatments"; export default function ColonyDetailPage() { const params = useParams<{ id: string }>(); @@ -33,6 +34,7 @@ export default function ColonyDetailPage() { + ); diff --git a/ApiCore-Frontend/src/app/(app)/treatments/new/page.tsx b/ApiCore-Frontend/src/app/(app)/treatments/new/page.tsx new file mode 100644 index 0000000..6d4d113 --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/treatments/new/page.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { FormEvent, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { fetchColonies } from "@/lib/colonies/api"; +import { createTreatment } from "@/lib/treatments/api"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +function todayIsoDate(): string { + return new Date().toISOString().slice(0, 10); +} + +export default function NewTreatmentPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const queryClient = useQueryClient(); + + const { data: colonies } = useQuery({ + queryKey: ["colonies"], + queryFn: fetchColonies, + }); + const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? []; + + const [colonyId, setColonyId] = useState(searchParams.get("colonyId") ?? ""); + const [date, setDate] = useState(todayIsoDate()); + const [product, setProduct] = useState(""); + const [amount, setAmount] = useState(""); + const [method, setMethod] = useState(""); + const [reason, setReason] = useState(""); + const [notes, setNotes] = useState(""); + const [error, setError] = useState(null); + + const mutation = useMutation({ + mutationFn: createTreatment, + onSuccess: (treatment) => { + void queryClient.invalidateQueries({ + queryKey: ["colonies", treatment.colonyId, "treatments"], + }); + router.push(`/colonies/${treatment.colonyId}`); + }, + onError: () => { + setError("Behandlung konnte nicht gespeichert werden."); + }, + }); + + function handleSubmit(event: FormEvent) { + event.preventDefault(); + setError(null); + if (!colonyId) { + setError("Bitte ein Volk auswählen."); + return; + } + mutation.mutate({ + colonyId, + date: new Date(date).toISOString(), + product, + amount: amount || undefined, + method: method || undefined, + reason: reason || undefined, + notes: notes || undefined, + }); + } + + return ( +
+ + + Neue Behandlung + + +
+
+ + +
+
+ + setDate(event.target.value)} + /> +
+
+ + setProduct(event.target.value)} + /> +
+
+ + setAmount(event.target.value)} + /> +
+
+ + setMethod(event.target.value)} + /> +
+
+ + setReason(event.target.value)} + /> +
+
+ +