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:
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 { Crown } from "lucide-react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { fetchQueens, updateQueen } from "@/lib/queens/api";
|
import { fetchQueens, updateQueen } from "@/lib/queens/api";
|
||||||
|
import { suggestMarkingColorForYear } from "@/lib/queens/marking-color";
|
||||||
import { Queen, QueenStatus } from "@/lib/queens/types";
|
import { Queen, QueenStatus } from "@/lib/queens/types";
|
||||||
import { QUEEN_STATUSES, QUEEN_STATUS_LABELS } from "@/lib/queens/labels";
|
import { QUEEN_STATUSES, QUEEN_STATUS_LABELS } from "@/lib/queens/labels";
|
||||||
import { statusTone } from "@/lib/status-tone";
|
import { statusTone } from "@/lib/status-tone";
|
||||||
@@ -28,12 +29,23 @@ import {
|
|||||||
|
|
||||||
export function QueenEditCard({ queen }: { queen: Queen }) {
|
export function QueenEditCard({ queen }: { queen: Queen }) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const [hatchYear, setHatchYear] = useState(queen.hatchYear?.toString() ?? "");
|
||||||
const [breed, setBreed] = useState(queen.breed ?? "");
|
const [breed, setBreed] = useState(queen.breed ?? "");
|
||||||
const [origin, setOrigin] = useState(queen.origin ?? "");
|
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 [motherQueenId, setMotherQueenId] = useState(queen.motherQueenId ?? "");
|
||||||
const [notes, setNotes] = useState(queen.notes ?? "");
|
const [notes, setNotes] = useState(queen.notes ?? "");
|
||||||
const [status, setStatus] = useState<QueenStatus>(queen.status);
|
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({
|
const { data: queens } = useQuery({
|
||||||
queryKey: ["queens"],
|
queryKey: ["queens"],
|
||||||
queryFn: () => fetchQueens(),
|
queryFn: () => fetchQueens(),
|
||||||
@@ -43,8 +55,11 @@ export function QueenEditCard({ queen }: { queen: Queen }) {
|
|||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: () =>
|
mutationFn: () =>
|
||||||
updateQueen(queen.id, {
|
updateQueen(queen.id, {
|
||||||
|
hatchYear: hatchYear ? Number(hatchYear) : undefined,
|
||||||
breed: breed || undefined,
|
breed: breed || undefined,
|
||||||
origin: origin || undefined,
|
origin: origin || undefined,
|
||||||
|
markingColor: markingColor || undefined,
|
||||||
|
markingNumber: markingNumber || undefined,
|
||||||
motherQueenId: motherQueenId || undefined,
|
motherQueenId: motherQueenId || undefined,
|
||||||
notes: notes || undefined,
|
notes: notes || undefined,
|
||||||
status,
|
status,
|
||||||
@@ -76,6 +91,16 @@ export function QueenEditCard({ queen }: { queen: Queen }) {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit}>
|
<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">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="queen-breed">Zucht/Rasse</Label>
|
<Label htmlFor="queen-breed">Zucht/Rasse</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -92,6 +117,22 @@ export function QueenEditCard({ queen }: { queen: Queen }) {
|
|||||||
onChange={(event) => setOrigin(event.target.value)}
|
onChange={(event) => setOrigin(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="queen-mother">Mutter</Label>
|
<Label htmlFor="queen-mother">Mutter</Label>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Crown } from "lucide-react";
|
import { Crown } from "lucide-react";
|
||||||
import { createQueen, fetchQueens } from "@/lib/queens/api";
|
import { createQueen, fetchQueens } from "@/lib/queens/api";
|
||||||
|
import { suggestMarkingColorForYear } from "@/lib/queens/marking-color";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@@ -22,11 +23,22 @@ export default function NewQueenPage() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [code, setCode] = useState("");
|
const [code, setCode] = useState("");
|
||||||
|
const [hatchYear, setHatchYear] = useState("");
|
||||||
const [breed, setBreed] = useState("");
|
const [breed, setBreed] = useState("");
|
||||||
const [origin, setOrigin] = useState("");
|
const [origin, setOrigin] = useState("");
|
||||||
|
const [markingColor, setMarkingColor] = useState("");
|
||||||
|
const [markingNumber, setMarkingNumber] = useState("");
|
||||||
const [motherQueenId, setMotherQueenId] = useState("");
|
const [motherQueenId, setMotherQueenId] = useState("");
|
||||||
const [error, setError] = useState<string | null>(null);
|
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({
|
const { data: queens } = useQuery({
|
||||||
queryKey: ["queens"],
|
queryKey: ["queens"],
|
||||||
queryFn: () => fetchQueens(),
|
queryFn: () => fetchQueens(),
|
||||||
@@ -48,8 +60,11 @@ export default function NewQueenPage() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
code,
|
code,
|
||||||
|
hatchYear: hatchYear ? Number(hatchYear) : undefined,
|
||||||
breed: breed || undefined,
|
breed: breed || undefined,
|
||||||
origin: origin || undefined,
|
origin: origin || undefined,
|
||||||
|
markingColor: markingColor || undefined,
|
||||||
|
markingNumber: markingNumber || undefined,
|
||||||
motherQueenId: motherQueenId || undefined,
|
motherQueenId: motherQueenId || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -74,6 +89,16 @@ export default function NewQueenPage() {
|
|||||||
onChange={(event) => setCode(event.target.value)}
|
onChange={(event) => setCode(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="queen-breed">Zucht/Rasse</Label>
|
<Label htmlFor="queen-breed">Zucht/Rasse</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -90,6 +115,26 @@ export default function NewQueenPage() {
|
|||||||
onChange={(event) => setOrigin(event.target.value)}
|
onChange={(event) => setOrigin(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="queen-mother">Mutter</Label>
|
<Label htmlFor="queen-mother">Mutter</Label>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -113,7 +113,10 @@ export default function QueensPage() {
|
|||||||
{QUEEN_STATUS_LABELS[queen.status]}
|
{QUEEN_STATUS_LABELS[queen.status]}
|
||||||
</Badge>
|
</Badge>
|
||||||
}
|
}
|
||||||
fields={[{ label: "Schlupfjahr", value: queen.hatchYear ?? "—" }]}
|
fields={[
|
||||||
|
{ label: "Schlupfjahr", value: queen.hatchYear ?? "—" },
|
||||||
|
{ label: "Zeichenfarbe", value: queen.markingColor ?? "—" },
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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];
|
||||||
|
}
|
||||||
@@ -32,15 +32,21 @@ export interface Queen {
|
|||||||
|
|
||||||
export interface CreateQueenInput {
|
export interface CreateQueenInput {
|
||||||
code: string;
|
code: string;
|
||||||
|
hatchYear?: number;
|
||||||
breed?: string;
|
breed?: string;
|
||||||
origin?: string;
|
origin?: string;
|
||||||
|
markingColor?: string;
|
||||||
|
markingNumber?: string;
|
||||||
motherQueenId?: string;
|
motherQueenId?: string;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateQueenInput {
|
export interface UpdateQueenInput {
|
||||||
|
hatchYear?: number;
|
||||||
breed?: string;
|
breed?: string;
|
||||||
origin?: string;
|
origin?: string;
|
||||||
|
markingColor?: string;
|
||||||
|
markingNumber?: string;
|
||||||
motherQueenId?: string;
|
motherQueenId?: string;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
status?: QueenStatus;
|
status?: QueenStatus;
|
||||||
|
|||||||
Reference in new issue
Block a user