From 3678a01319b6fb033fa592c7e9201a26e89457f3 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Tue, 8 Sep 2026 22:39:47 +0200 Subject: [PATCH] fix: prevent new backend guards from ever surfacing as raw errors The colony-apiary and queen-availability validation just added backend-side had no matching frontend guard, so a user could still pick an inactive apiary or open the assign-colony dialog on an unavailable queen and only find out it fails after submitting: - Colony create/move apiary pickers now fetch only ACTIVE apiaries. - The queen "Volk zuweisen" button is disabled once her status is LOST/REPLACED/SOLD/DECEASED (QUEEN_UNAVAILABLE_STATUSES, mirroring the backend's QUEEN_NO_LONGER_AVAILABLE_STATUSES), instead of only failing after the form is filled out and submitted. Co-Authored-By: Claude Sonnet 5 --- .../(app)/colonies/[id]/colony-actions.tsx | 5 ++++- .../src/app/(app)/colonies/new/page.tsx | 5 ++++- .../queens/[id]/assign-colony-dialog.tsx | 19 +++++++++++++++++-- .../src/app/(app)/queens/[id]/page.tsx | 2 +- ApiCore-Frontend/src/lib/queens/labels.ts | 11 +++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-actions.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-actions.tsx index 1119765..dcfda3a 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-actions.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-actions.tsx @@ -48,7 +48,10 @@ function MoveDialog({ colony }: { colony: Colony }) { const queryClient = useQueryClient(); const [open, setOpen] = useState(false); const [apiaryId, setApiaryId] = useState(""); - const { data: apiaries } = useQuery({ queryKey: ["apiaries"], queryFn: () => fetchApiaries() }); + const { data: apiaries } = useQuery({ + queryKey: ["apiaries", { status: "ACTIVE" }], + queryFn: () => fetchApiaries({ status: "ACTIVE" }), + }); const mutation = useMutation({ mutationFn: () => moveColony(colony.id, { apiaryId }), diff --git a/ApiCore-Frontend/src/app/(app)/colonies/new/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/new/page.tsx index c746ab1..4311cd4 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/new/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/new/page.tsx @@ -24,7 +24,10 @@ import { Card, CardContent } from "@/components/ui/card"; export default function NewColonyPage() { const router = useRouter(); const queryClient = useQueryClient(); - const { data: apiaries } = useQuery({ queryKey: ["apiaries"], queryFn: () => fetchApiaries() }); + const { data: apiaries } = useQuery({ + queryKey: ["apiaries", { status: "ACTIVE" }], + queryFn: () => fetchApiaries({ status: "ACTIVE" }), + }); const [code, setCode] = useState(""); const [apiaryId, setApiaryId] = useState(""); diff --git a/ApiCore-Frontend/src/app/(app)/queens/[id]/assign-colony-dialog.tsx b/ApiCore-Frontend/src/app/(app)/queens/[id]/assign-colony-dialog.tsx index 8acc5e9..b93a07a 100644 --- a/ApiCore-Frontend/src/app/(app)/queens/[id]/assign-colony-dialog.tsx +++ b/ApiCore-Frontend/src/app/(app)/queens/[id]/assign-colony-dialog.tsx @@ -3,6 +3,8 @@ import { FormEvent, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { assignQueenColony } from "@/lib/queens/api"; +import { QUEEN_UNAVAILABLE_STATUSES } from "@/lib/queens/labels"; +import { QueenStatus } from "@/lib/queens/types"; import { fetchColonies } from "@/lib/colonies/api"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; @@ -22,11 +24,18 @@ import { SelectValue, } from "@/components/ui/select"; -export function AssignColonyDialog({ queenId }: { queenId: string }) { +export function AssignColonyDialog({ + queenId, + queenStatus, +}: { + queenId: string; + queenStatus: QueenStatus; +}) { const queryClient = useQueryClient(); const [open, setOpen] = useState(false); const [colonyId, setColonyId] = useState(""); const { data: colonies } = useQuery({ queryKey: ["colonies"], queryFn: () => fetchColonies() }); + const unavailable = QUEEN_UNAVAILABLE_STATUSES.includes(queenStatus); const mutation = useMutation({ mutationFn: () => assignQueenColony(queenId, { colonyId }), @@ -39,7 +48,13 @@ export function AssignColonyDialog({ queenId }: { queenId: string }) { return ( - Volk zuweisen} /> + + Volk zuweisen + + } + /> Königin einem Volk zuweisen diff --git a/ApiCore-Frontend/src/app/(app)/queens/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/queens/[id]/page.tsx index 1c62b5b..8624d0b 100644 --- a/ApiCore-Frontend/src/app/(app)/queens/[id]/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/queens/[id]/page.tsx @@ -37,7 +37,7 @@ export default function QueenDetailPage() {
- +
diff --git a/ApiCore-Frontend/src/lib/queens/labels.ts b/ApiCore-Frontend/src/lib/queens/labels.ts index 7562ce9..0dbf825 100644 --- a/ApiCore-Frontend/src/lib/queens/labels.ts +++ b/ApiCore-Frontend/src/lib/queens/labels.ts @@ -12,6 +12,17 @@ export const QUEEN_STATUSES: QueenStatus[] = [ "DECEASED", ]; +// Mirrors the backend's QUEEN_NO_LONGER_AVAILABLE_STATUSES - a queen in one +// of these statuses can no longer be assigned to a colony (the backend +// rejects it), so the assign-colony UI disables itself instead of letting +// the user hit that error after filling out the form. +export const QUEEN_UNAVAILABLE_STATUSES: QueenStatus[] = [ + "LOST", + "REPLACED", + "SOLD", + "DECEASED", +]; + export const QUEEN_STATUS_LABELS: Record = { ACTIVE: "Aktiv", RESERVE: "Reserve",