fix: allow entering a queen's hatch year and marking color/number

The backend has always accepted hatchYear/markingColor/markingNumber
(Queen DTOs, Prisma schema), and the queens list even already renders
a "Schlupfjahr" field - but neither the create nor the edit form ever
exposed inputs for them, so no user could actually set hatchYear
through the UI. That silently broke the OLD_QUEEN dashboard alert
just added: it can only ever fire for colonies whose queen has a
hatchYear, and there was no way to record one.

Both forms now include Schlupfjahr, Zeichenfarbe and Zeichennummer.
Marking color is auto-suggested from the hatch year using the
internationally standardized queen marking color convention (last
digit of the year -> white/yellow/red/green/blue), editable like any
other suggestion.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-06 21:44:29 +02:00
1 parent edf13d3ae4
commit 985ea09556
5 files changed
+116 -1

No files matched your search

@@ -4,6 +4,7 @@ import { FormEvent, useState } from "react";
import { Crown } from "lucide-react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { fetchQueens, updateQueen } from "@/lib/queens/api";
import { suggestMarkingColorForYear } from "@/lib/queens/marking-color";
import { Queen, QueenStatus } from "@/lib/queens/types";
import { QUEEN_STATUSES, QUEEN_STATUS_LABELS } from "@/lib/queens/labels";
import { statusTone } from "@/lib/status-tone";
@@ -28,12 +29,23 @@ import {
export function QueenEditCard({ queen }: { queen: Queen }) {
const queryClient = useQueryClient();
const [hatchYear, setHatchYear] = useState(queen.hatchYear?.toString() ?? "");
const [breed, setBreed] = useState(queen.breed ?? "");
const [origin, setOrigin] = useState(queen.origin ?? "");
const [markingColor, setMarkingColor] = useState(queen.markingColor ?? "");
const [markingNumber, setMarkingNumber] = useState(queen.markingNumber ?? "");
const [motherQueenId, setMotherQueenId] = useState(queen.motherQueenId ?? "");
const [notes, setNotes] = useState(queen.notes ?? "");
const [status, setStatus] = useState<QueenStatus>(queen.status);
function handleHatchYearChange(value: string) {
setHatchYear(value);
const year = Number(value);
if (!markingColor && value && Number.isInteger(year)) {
setMarkingColor(suggestMarkingColorForYear(year));
}
}
const { data: queens } = useQuery({
queryKey: ["queens"],
queryFn: () => fetchQueens(),
@@ -43,8 +55,11 @@ export function QueenEditCard({ queen }: { queen: Queen }) {
const mutation = useMutation({
mutationFn: () =>
updateQueen(queen.id, {
hatchYear: hatchYear ? Number(hatchYear) : undefined,
breed: breed || undefined,
origin: origin || undefined,
markingColor: markingColor || undefined,
markingNumber: markingNumber || undefined,
motherQueenId: motherQueenId || undefined,
notes: notes || undefined,
status,
@@ -76,6 +91,16 @@ export function QueenEditCard({ queen }: { queen: Queen }) {
</CardHeader>
<CardContent>
<form className="flex flex-col gap-4" onSubmit={handleSubmit}>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-hatch-year">Schlupfjahr</Label>
<Input
id="queen-hatch-year"
type="number"
inputMode="numeric"
value={hatchYear}
onChange={(event) => handleHatchYearChange(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-breed">Zucht/Rasse</Label>
<Input
@@ -92,6 +117,22 @@ export function QueenEditCard({ queen }: { queen: Queen }) {
onChange={(event) => setOrigin(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-marking-color">Zeichenfarbe</Label>
<Input
id="queen-marking-color"
value={markingColor}
onChange={(event) => setMarkingColor(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-marking-number">Zeichennummer</Label>
<Input
id="queen-marking-number"
value={markingNumber}
onChange={(event) => setMarkingNumber(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-mother">Mutter</Label>
<Select
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Crown } from "lucide-react";
import { createQueen, fetchQueens } from "@/lib/queens/api";
import { suggestMarkingColorForYear } from "@/lib/queens/marking-color";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -22,11 +23,22 @@ export default function NewQueenPage() {
const router = useRouter();
const queryClient = useQueryClient();
const [code, setCode] = useState("");
const [hatchYear, setHatchYear] = useState("");
const [breed, setBreed] = useState("");
const [origin, setOrigin] = useState("");
const [markingColor, setMarkingColor] = useState("");
const [markingNumber, setMarkingNumber] = useState("");
const [motherQueenId, setMotherQueenId] = useState("");
const [error, setError] = useState<string | null>(null);
function handleHatchYearChange(value: string) {
setHatchYear(value);
const year = Number(value);
if (!markingColor && value && Number.isInteger(year)) {
setMarkingColor(suggestMarkingColorForYear(year));
}
}
const { data: queens } = useQuery({
queryKey: ["queens"],
queryFn: () => fetchQueens(),
@@ -48,8 +60,11 @@ export default function NewQueenPage() {
setError(null);
mutation.mutate({
code,
hatchYear: hatchYear ? Number(hatchYear) : undefined,
breed: breed || undefined,
origin: origin || undefined,
markingColor: markingColor || undefined,
markingNumber: markingNumber || undefined,
motherQueenId: motherQueenId || undefined,
});
}
@@ -74,6 +89,16 @@ export default function NewQueenPage() {
onChange={(event) => setCode(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-hatch-year">Schlupfjahr</Label>
<Input
id="queen-hatch-year"
type="number"
inputMode="numeric"
value={hatchYear}
onChange={(event) => handleHatchYearChange(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-breed">Zucht/Rasse</Label>
<Input
@@ -90,6 +115,26 @@ export default function NewQueenPage() {
onChange={(event) => setOrigin(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-marking-color">Zeichenfarbe</Label>
<Input
id="queen-marking-color"
value={markingColor}
onChange={(event) => setMarkingColor(event.target.value)}
/>
<p className="text-xs text-muted-foreground">
Wird nach internationaler Konvention aus dem Schlupfjahr vorgeschlagen, kann
aber angepasst werden.
</p>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-marking-number">Zeichennummer</Label>
<Input
id="queen-marking-number"
value={markingNumber}
onChange={(event) => setMarkingNumber(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="queen-mother">Mutter</Label>
<Select
@@ -113,7 +113,10 @@ export default function QueensPage() {
{QUEEN_STATUS_LABELS[queen.status]}
</Badge>
}
fields={[{ label: "Schlupfjahr", value: queen.hatchYear ?? "—" }]}
fields={[
{ label: "Schlupfjahr", value: queen.hatchYear ?? "—" },
{ label: "Zeichenfarbe", value: queen.markingColor ?? "—" },
]}
/>
))}
</div>
@@ -0,0 +1,20 @@
// Internationally standardized queen marking colors by the last digit of
// the hatch year (used across European beekeeping regardless of country):
// 1/6 white, 2/7 yellow, 3/8 red, 4/9 green, 5/0 blue.
const MARKING_COLORS_BY_LAST_DIGIT: Record<number, string> = {
1: "Weiß",
6: "Weiß",
2: "Gelb",
7: "Gelb",
3: "Rot",
8: "Rot",
4: "Grün",
9: "Grün",
5: "Blau",
0: "Blau",
};
export function suggestMarkingColorForYear(hatchYear: number): string {
const lastDigit = ((hatchYear % 10) + 10) % 10;
return MARKING_COLORS_BY_LAST_DIGIT[lastDigit];
}
+6
View File
@@ -32,15 +32,21 @@ export interface Queen {
export interface CreateQueenInput {
code: string;
hatchYear?: number;
breed?: string;
origin?: string;
markingColor?: string;
markingNumber?: string;
motherQueenId?: string;
notes?: string;
}
export interface UpdateQueenInput {
hatchYear?: number;
breed?: string;
origin?: string;
markingColor?: string;
markingNumber?: string;
motherQueenId?: string;
notes?: string;
status?: QueenStatus;