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 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-08 22:39:47 +02:00
1 parent 3e179391b0
commit 3678a01319
5 files changed
+37 -5

No files matched your search

@@ -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 }),
@@ -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("");
@@ -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 (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger render={<Button variant="outline">Volk zuweisen</Button>} />
<DialogTrigger
render={
<Button variant="outline" disabled={unavailable}>
Volk zuweisen
</Button>
}
/>
<DialogContent>
<DialogHeader>
<DialogTitle>Königin einem Volk zuweisen</DialogTitle>
@@ -37,7 +37,7 @@ export default function QueenDetailPage() {
<div className="grid grid-cols-1 items-start gap-4 lg:grid-cols-3">
<div className="flex flex-col gap-4 lg:col-span-1">
<QueenEditCard key={queen.id} queen={queen} />
<AssignColonyDialog queenId={queen.id} />
<AssignColonyDialog queenId={queen.id} queenStatus={queen.status} />
</div>
<div className="flex flex-col gap-4 lg:col-span-2">
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
+11
View File
@@ -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<QueenStatus, string> = {
ACTIVE: "Aktiv",
RESERVE: "Reserve",