From 49ce23147c8996b9f606eb5a26b3f1c16fd9ece6 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Fri, 21 Aug 2026 18:35:33 +0200 Subject: [PATCH] feat(frontend): Koeniginnen-Views (Modul 4) /queens: Liste; /queens/new: Anlegeformular; /queens/[id]: Bearbeiten (Zucht/Herkunft/Status/Notizen), "Volk zuweisen"-Dialog (nur ACTIVE Voelker zur Auswahl) und Volk-Historie. Bearbeitungsformular initialisiert Zustand direkt aus der geladenen Queen (gleiches Key-Reset-Pattern wie bei Apiaries/Colonies). Nav-Leiste und proxy.ts-Matcher um /queens erweitert. 10 Vitest-Tests, Lint, Typecheck und Build gruen. Kompletter Flow (Anlegen, Volk zuweisen, Historie) live im Browser gegen den echten Server verifiziert. --- ApiCore-Frontend/proxy.ts | 8 +- ApiCore-Frontend/src/app/(app)/layout.tsx | 3 + .../queens/[id]/assign-colony-dialog.tsx | 80 ++++++++++++ .../src/app/(app)/queens/[id]/page.tsx | 35 ++++++ .../app/(app)/queens/[id]/queen-edit-card.tsx | 119 ++++++++++++++++++ .../app/(app)/queens/[id]/queen-history.tsx | 42 +++++++ .../src/app/(app)/queens/new/page.tsx | 88 +++++++++++++ .../src/app/(app)/queens/page.spec.tsx | 51 ++++++++ .../src/app/(app)/queens/page.tsx | 69 ++++++++++ ApiCore-Frontend/src/lib/queens/api.ts | 41 ++++++ ApiCore-Frontend/src/lib/queens/types.ts | 57 +++++++++ TASKS.md | 2 +- 12 files changed, 593 insertions(+), 2 deletions(-) create mode 100644 ApiCore-Frontend/src/app/(app)/queens/[id]/assign-colony-dialog.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/queens/[id]/page.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/queens/[id]/queen-edit-card.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/queens/[id]/queen-history.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/queens/new/page.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/queens/page.spec.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/queens/page.tsx create mode 100644 ApiCore-Frontend/src/lib/queens/api.ts create mode 100644 ApiCore-Frontend/src/lib/queens/types.ts diff --git a/ApiCore-Frontend/proxy.ts b/ApiCore-Frontend/proxy.ts index f9ffd47..8f2b2cb 100644 --- a/ApiCore-Frontend/proxy.ts +++ b/ApiCore-Frontend/proxy.ts @@ -22,5 +22,11 @@ export function proxy(request: NextRequest) { } export const config = { - matcher: ["/login", "/settings/:path*", "/apiaries/:path*", "/colonies/:path*"], + matcher: [ + "/login", + "/settings/:path*", + "/apiaries/:path*", + "/colonies/:path*", + "/queens/:path*", + ], }; diff --git a/ApiCore-Frontend/src/app/(app)/layout.tsx b/ApiCore-Frontend/src/app/(app)/layout.tsx index 070792e..9fbbdbd 100644 --- a/ApiCore-Frontend/src/app/(app)/layout.tsx +++ b/ApiCore-Frontend/src/app/(app)/layout.tsx @@ -31,6 +31,9 @@ export default function AppLayout({ children }: { children: ReactNode }) { Völker + + Königinnen + {user.role === "ADMIN" ? ( assignQueenColony(queenId, { colonyId }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["queens", queenId] }); + void queryClient.invalidateQueries({ queryKey: ["queens", queenId, "history"] }); + setOpen(false); + }, + }); + + return ( + + Volk zuweisen} /> + + + Königin einem Volk zuweisen + +
{ + event.preventDefault(); + mutation.mutate(); + }} + > +
+ + +
+ + + +
+
+
+ ); +} diff --git a/ApiCore-Frontend/src/app/(app)/queens/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/queens/[id]/page.tsx new file mode 100644 index 0000000..24334cd --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/queens/[id]/page.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { useParams } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import { fetchQueen } from "@/lib/queens/api"; +import { QueenEditCard } from "./queen-edit-card"; +import { AssignColonyDialog } from "./assign-colony-dialog"; +import { QueenHistoryCard } from "./queen-history"; + +export default function QueenDetailPage() { + const params = useParams<{ id: string }>(); + const { data: queen, isLoading, isError } = useQuery({ + queryKey: ["queens", params.id], + queryFn: () => fetchQueen(params.id), + }); + + if (isLoading) { + return

Lädt…

; + } + if (isError || !queen) { + return ( +

+ Königin konnte nicht geladen werden. +

+ ); + } + + return ( +
+ + + +
+ ); +} diff --git a/ApiCore-Frontend/src/app/(app)/queens/[id]/queen-edit-card.tsx b/ApiCore-Frontend/src/app/(app)/queens/[id]/queen-edit-card.tsx new file mode 100644 index 0000000..25627ba --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/queens/[id]/queen-edit-card.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { FormEvent, useState } from "react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { updateQueen } from "@/lib/queens/api"; +import { Queen, QueenStatus } from "@/lib/queens/types"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +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 QUEEN_STATUSES: QueenStatus[] = [ + "ACTIVE", + "RESERVE", + "UNMATED", + "MATED", + "LOST", + "REPLACED", + "SOLD", + "DECEASED", +]; + +export function QueenEditCard({ queen }: { queen: Queen }) { + const queryClient = useQueryClient(); + const [breed, setBreed] = useState(queen.breed ?? ""); + const [origin, setOrigin] = useState(queen.origin ?? ""); + const [notes, setNotes] = useState(queen.notes ?? ""); + const [status, setStatus] = useState(queen.status); + + const mutation = useMutation({ + mutationFn: () => + updateQueen(queen.id, { + breed: breed || undefined, + origin: origin || undefined, + notes: notes || undefined, + status, + }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["queens"] }); + void queryClient.invalidateQueries({ queryKey: ["queens", queen.id] }); + }, + }); + + function handleSubmit(event: FormEvent) { + event.preventDefault(); + mutation.mutate(); + } + + return ( + + + {queen.code} + + {queen.status} + + + +
+
+ + setBreed(event.target.value)} + /> +
+
+ + setOrigin(event.target.value)} + /> +
+
+ + +
+
+ +