diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-varroa.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-varroa.tsx new file mode 100644 index 0000000..853380a --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-varroa.tsx @@ -0,0 +1,87 @@ +"use client"; + +import Link from "next/link"; +import { useQuery } from "@tanstack/react-query"; +import { fetchColonyVarroaMeasurements } from "@/lib/varroa/api"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +const CHART_WIDTH = 280; +const CHART_HEIGHT = 80; +const CHART_PADDING = 8; + +function VarroaTrendChart({ values }: { values: number[] }) { + if (values.length < 2) { + return null; + } + const max = Math.max(...values, 1); + const min = Math.min(...values, 0); + const range = max - min || 1; + const usableWidth = CHART_WIDTH - CHART_PADDING * 2; + const usableHeight = CHART_HEIGHT - CHART_PADDING * 2; + const points = values + .map((value, index) => { + const x = CHART_PADDING + (index / (values.length - 1)) * usableWidth; + const y = CHART_PADDING + usableHeight - ((value - min) / range) * usableHeight; + return `${x},${y}`; + }) + .join(" "); + + return ( + + + + ); +} + +export function ColonyVarroaCard({ colonyId }: { colonyId: string }) { + const { data: measurements, isLoading } = useQuery({ + queryKey: ["colonies", colonyId, "varroa-measurements"], + queryFn: () => fetchColonyVarroaMeasurements(colonyId), + }); + + const values = (measurements ?? []) + .map((measurement) => measurement.miteCount ?? measurement.calculatedValue) + .filter((value): value is number => value !== null); + + return ( + + + Varroa + + + + {isLoading ?

Lädt…

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

Noch keine Varroamessung dokumentiert.

+ ) : null} + + {measurements?.map((measurement) => ( +
+

+ {new Date(measurement.date).toLocaleDateString("de-DE")} · {measurement.method} +

+

+ {measurement.miteCount !== null ? `${measurement.miteCount} Milben` : "—"} + {measurement.calculatedValue !== null + ? ` · ${measurement.calculatedValue.toFixed(1)} Milben/Tag` + : ""} +

+ {measurement.assessment ?

Einschätzung: {measurement.assessment}

: null} +
+ ))} +
+
+ ); +} diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx index 096d573..7818941 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx @@ -7,6 +7,7 @@ import { ColonyEditCard } from "./colony-edit-card"; import { ColonyActions } from "./colony-actions"; import { ColonyHistoryCard } from "./colony-history"; import { ColonyInspectionsCard } from "./colony-inspections"; +import { ColonyVarroaCard } from "./colony-varroa"; export default function ColonyDetailPage() { const params = useParams<{ id: string }>(); @@ -31,6 +32,7 @@ export default function ColonyDetailPage() { + ); diff --git a/ApiCore-Frontend/src/app/(app)/varroa/new/page.tsx b/ApiCore-Frontend/src/app/(app)/varroa/new/page.tsx new file mode 100644 index 0000000..603ebbf --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/varroa/new/page.tsx @@ -0,0 +1,188 @@ +"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 { createVarroaMeasurement } from "@/lib/varroa/api"; +import { VarroaMethod } from "@/lib/varroa/types"; +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"; + +const METHOD_OPTIONS: VarroaMethod[] = [ + "NATURAL_MITE_FALL", + "POWDERED_SUGAR", + "WASHING_METHOD", + "DRONE_BROOD_CONTROL", +]; + +function todayIsoDate(): string { + return new Date().toISOString().slice(0, 10); +} + +export default function NewVarroaMeasurementPage() { + 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 [method, setMethod] = useState("NATURAL_MITE_FALL"); + const [miteCount, setMiteCount] = useState(""); + const [durationMinutes, setDurationMinutes] = useState(""); + const [assessment, setAssessment] = useState(""); + const [notes, setNotes] = useState(""); + const [error, setError] = useState(null); + + const mutation = useMutation({ + mutationFn: createVarroaMeasurement, + onSuccess: (measurement) => { + void queryClient.invalidateQueries({ + queryKey: ["colonies", measurement.colonyId, "varroa-measurements"], + }); + router.push(`/colonies/${measurement.colonyId}`); + }, + onError: () => { + setError("Varroamessung 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(), + method, + miteCount: miteCount ? Number(miteCount) : undefined, + durationMinutes: durationMinutes ? Number(durationMinutes) : undefined, + assessment: assessment || undefined, + notes: notes || undefined, + }); + } + + return ( +
+ + + Neue Varroamessung + + +
+
+ + +
+
+ + setDate(event.target.value)} + /> +
+
+ + +
+
+ + setMiteCount(event.target.value)} + /> +
+
+ + setDurationMinutes(event.target.value)} + /> +
+
+ + setAssessment(event.target.value)} + /> +
+
+ +