-
-
- Produkt
- setProduct(event.target.value)}
- />
-
-
- Chargennummer
- setBatchNumber(event.target.value)}
- />
-
-
- Notizen
-
-
- {mutation.isPending ? "Speichert…" : "Speichern"}
-
+
+ {({ id }) => (
+ setProduct(event.target.value)}
+ />
+ )}
+
+
+
+
+
+ {({ id }) => (
+ setBatchNumber(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+
+
+
+
diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/inspection-edit-dialog.spec.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/inspection-edit-dialog.spec.tsx
new file mode 100644
index 0000000..40addc0
--- /dev/null
+++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/inspection-edit-dialog.spec.tsx
@@ -0,0 +1,106 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { InspectionEditDialog } from "./inspection-edit-dialog";
+import { updateInspection } from "@/lib/inspections/api";
+import { fetchColonies } from "@/lib/colonies/api";
+import { ApiError } from "@/lib/auth/api-client";
+import type { Inspection } from "@/lib/inspections/types";
+
+vi.mock("@/lib/colonies/api", () => ({ fetchColonies: vi.fn() }));
+vi.mock("@/lib/inspections/api", () => ({ updateInspection: vi.fn() }));
+
+const inspection: Inspection = {
+ id: "i-1",
+ colonyId: "colony-1",
+ userId: "u-1",
+ date: "2026-06-01T00:00:00.000Z",
+ weather: null,
+ temperature: null,
+ colonyStrength: 8,
+ broodAmount: null,
+ broodPattern: 3,
+ openBroodPresent: true,
+ cappedBroodPresent: true,
+ eggsPresent: true,
+ queenSeen: false,
+ queenCellsPresent: false,
+ swarmMood: null,
+ gentleness: 2,
+ combBehavior: null,
+ feedStatus: "ausreichend",
+ honeySuperStatus: null,
+ healthIssues: null,
+ varroaIssues: null,
+ measures: null,
+ notes: null,
+ createdAt: "2026-06-01T00:00:00.000Z",
+};
+
+async function openDialog() {
+ const client = new QueryClient({
+ defaultOptions: { queries: { retry: false } },
+ });
+ render(
+
+
+ ,
+ );
+ await userEvent.click(screen.getByRole("button", { name: "Bearbeiten" }));
+ return screen.findByRole("dialog");
+}
+
+describe("InspectionEditDialog", () => {
+ beforeEach(() => {
+ vi.mocked(fetchColonies).mockResolvedValue([
+ { id: "colony-1", code: "V-01", status: "ACTIVE" },
+ ] as never);
+ vi.mocked(updateInspection).mockReset();
+ });
+
+ it("sends the tapped rating as a number", async () => {
+ vi.mocked(updateInspection).mockResolvedValue(inspection);
+ await openDialog();
+
+ // Order: Brutbild, Sanftmut, Wabensitz, Schwarmstimmung, Futter.
+ const gentleness = screen.getAllByRole("radiogroup")[1];
+ await userEvent.click(
+ gentleness.querySelector('[aria-label="5 von 5"]') as HTMLElement,
+ );
+ await userEvent.click(screen.getByRole("button", { name: "Speichern" }));
+
+ const [id, input] = vi.mocked(updateInspection).mock.calls[0];
+ expect(id).toBe("i-1");
+ expect(input).toMatchObject({
+ colonyId: "colony-1",
+ gentleness: 5,
+ broodPattern: 3,
+ colonyStrength: 8,
+ });
+ });
+
+ it("shows a missing date next to the field and does not save", async () => {
+ await openDialog();
+
+ await userEvent.clear(screen.getByLabelText(/Datum/));
+ await userEvent.click(screen.getByRole("button", { name: "Speichern" }));
+
+ expect(screen.getByText("Bitte ein Datum angeben.")).toBeInTheDocument();
+ expect(screen.getByLabelText(/Datum/)).toHaveAttribute("aria-invalid", "true");
+ expect(updateInspection).not.toHaveBeenCalled();
+ });
+
+ it("shows the server's reason inline when saving fails", async () => {
+ vi.mocked(updateInspection).mockRejectedValue(
+ new ApiError(400, "Datum liegt in der Zukunft"),
+ );
+ await openDialog();
+
+ await userEvent.click(screen.getByRole("button", { name: "Speichern" }));
+
+ expect(
+ await screen.findByText("Datum liegt in der Zukunft"),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/inspection-edit-dialog.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/inspection-edit-dialog.tsx
index 60ce4f2..b6ecd8c 100644
--- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/inspection-edit-dialog.tsx
+++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/inspection-edit-dialog.tsx
@@ -1,29 +1,17 @@
"use client";
import { FormEvent, useState } from "react";
-import { Pencil } from "lucide-react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
-import { toast } from "sonner";
import { updateInspection } from "@/lib/inspections/api";
import { Inspection } from "@/lib/inspections/types";
-
-const FEED_STATUS_LABELS: Record
= {
- ausreichend: "Ausreichend",
- niedrig: "Niedrig",
- kritisch: "Kritisch",
-};
-import { Button } from "@/components/ui/button";
+import { errorMessage } from "@/lib/forms/error-message";
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 { ColonySelectField } from "@/components/colony-select-field";
+import { Field } from "@/components/form/field";
+import { FormSection } from "@/components/form/form-section";
+import { RatingScale } from "@/components/form/rating-scale";
+import { ToggleChip } from "@/components/form/toggle-chip";
import {
Dialog,
DialogContent,
@@ -31,304 +19,335 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
+import { cn } from "@/lib/utils";
+import {
+ FEED_STATUS_OPTIONS,
+ FeedStatus,
+ InspectionFormValues,
+ toInspectionInput,
+} from "../../inspections/new/inspection-form-values";
+import {
+ DATE_REQUIRED_MESSAGE,
+ DialogSubmitBar,
+ EditTriggerButton,
+ hasAnyValue,
+} from "./edit-dialog-parts";
+
+function valuesFromInspection(inspection: Inspection): InspectionFormValues {
+ return {
+ colonyId: inspection.colonyId,
+ date: inspection.date.slice(0, 10),
+ weather: inspection.weather ?? "",
+ temperature: inspection.temperature?.toString() ?? "",
+ colonyStrength: inspection.colonyStrength?.toString() ?? "",
+ broodAmount: inspection.broodAmount ?? "",
+ broodPattern: inspection.broodPattern,
+ openBroodPresent: inspection.openBroodPresent ?? false,
+ cappedBroodPresent: inspection.cappedBroodPresent ?? false,
+ queenSeen: inspection.queenSeen ?? false,
+ eggsPresent: inspection.eggsPresent ?? true,
+ queenCellsPresent: inspection.queenCellsPresent ?? false,
+ swarmMood: inspection.swarmMood,
+ gentleness: inspection.gentleness,
+ combBehavior: inspection.combBehavior,
+ // Kept verbatim even if it is not one of the three known values, so
+ // saving without touching it never changes it.
+ feedStatus: (inspection.feedStatus ?? "ausreichend") as FeedStatus,
+ honeySuperStatus: inspection.honeySuperStatus ?? "",
+ healthIssues: inspection.healthIssues ?? "",
+ varroaIssues: inspection.varroaIssues ?? "",
+ measures: inspection.measures ?? "",
+ notes: inspection.notes ?? "",
+ };
+}
+
+const RATINGS: {
+ key: "gentleness" | "combBehavior" | "swarmMood";
+ label: string;
+ low: string;
+ high: string;
+}[] = [
+ { key: "gentleness", label: "Sanftmut", low: "stechlustig", high: "sehr sanft" },
+ { key: "combBehavior", label: "Wabensitz", low: "läuft weg", high: "sitzt ruhig" },
+ { key: "swarmMood", label: "Schwarmstimmung", low: "keine", high: "stark" },
+];
export function InspectionEditDialog({ inspection }: { inspection: Inspection }) {
const queryClient = useQueryClient();
const [open, setOpen] = useState(false);
- const [colonyId, setColonyId] = useState(inspection.colonyId);
- const [date, setDate] = useState(inspection.date.slice(0, 10));
- const [weather, setWeather] = useState(inspection.weather ?? "");
- const [temperature, setTemperature] = useState(inspection.temperature?.toString() ?? "");
- const [colonyStrength, setColonyStrength] = useState(
- inspection.colonyStrength?.toString() ?? "",
+ const [values, setValues] = useState(() =>
+ valuesFromInspection(inspection),
);
- const [broodAmount, setBroodAmount] = useState(inspection.broodAmount ?? "");
- const [broodPattern, setBroodPattern] = useState(inspection.broodPattern?.toString() ?? "");
- const [openBroodPresent, setOpenBroodPresent] = useState(inspection.openBroodPresent ?? false);
- const [cappedBroodPresent, setCappedBroodPresent] = useState(
- inspection.cappedBroodPresent ?? false,
- );
- const [queenSeen, setQueenSeen] = useState(inspection.queenSeen ?? false);
- const [eggsPresent, setEggsPresent] = useState(inspection.eggsPresent ?? true);
- const [queenCellsPresent, setQueenCellsPresent] = useState(
- inspection.queenCellsPresent ?? false,
- );
- const [swarmMood, setSwarmMood] = useState(inspection.swarmMood?.toString() ?? "");
- const [gentleness, setGentleness] = useState(inspection.gentleness?.toString() ?? "");
- const [combBehavior, setCombBehavior] = useState(inspection.combBehavior?.toString() ?? "");
- const [feedStatus, setFeedStatus] = useState(inspection.feedStatus ?? "ausreichend");
- const [honeySuperStatus, setHoneySuperStatus] = useState(inspection.honeySuperStatus ?? "");
- const [healthIssues, setHealthIssues] = useState(inspection.healthIssues ?? "");
- const [varroaIssues, setVarroaIssues] = useState(inspection.varroaIssues ?? "");
- const [measures, setMeasures] = useState(inspection.measures ?? "");
- const [notes, setNotes] = useState(inspection.notes ?? "");
+ const [dateError, setDateError] = useState(null);
+ const [submitError, setSubmitError] = useState(null);
+
+ function set(
+ key: K,
+ value: InspectionFormValues[K],
+ ) {
+ setValues((current) => ({ ...current, [key]: value }));
+ if (key === "date") setDateError(null);
+ }
const mutation = useMutation({
- mutationFn: () =>
- updateInspection(inspection.id, {
- colonyId,
- date: new Date(date).toISOString(),
- weather: weather || undefined,
- temperature: temperature ? Number(temperature) : undefined,
- colonyStrength: colonyStrength ? Number(colonyStrength) : undefined,
- broodAmount: broodAmount || undefined,
- broodPattern: broodPattern ? Number(broodPattern) : undefined,
- openBroodPresent,
- cappedBroodPresent,
- queenSeen,
- eggsPresent,
- queenCellsPresent,
- swarmMood: swarmMood ? Number(swarmMood) : undefined,
- gentleness: gentleness ? Number(gentleness) : undefined,
- combBehavior: combBehavior ? Number(combBehavior) : undefined,
- feedStatus,
- honeySuperStatus: honeySuperStatus || undefined,
- healthIssues: healthIssues || undefined,
- varroaIssues: varroaIssues || undefined,
- measures: measures || undefined,
- notes: notes || undefined,
- }),
- onSuccess: () => {
+ mutationFn: (input: InspectionFormValues) =>
+ updateInspection(inspection.id, toInspectionInput(input)),
+ onSuccess: (_result, input) => {
// After re-filing, both the colony it left and the one it joined
// show a stale list until each is invalidated.
- for (const affectedColonyId of new Set([inspection.colonyId, colonyId])) {
+ for (const affectedColonyId of new Set([inspection.colonyId, input.colonyId])) {
void queryClient.invalidateQueries({
queryKey: ["colonies", affectedColonyId, "inspections"],
});
}
setOpen(false);
},
- onError: () => toast.error("Durchsicht konnte nicht gespeichert werden"),
+ onError: (error) =>
+ setSubmitError(
+ errorMessage(error, "Durchsicht konnte nicht gespeichert werden."),
+ ),
});
function handleSubmit(event: FormEvent) {
event.preventDefault();
- mutation.mutate();
+ setSubmitError(null);
+ if (!values.date) {
+ setDateError(DATE_REQUIRED_MESSAGE);
+ return;
+ }
+ mutation.mutate(values);
}
+ const hasExtras = hasAnyValue(
+ values.weather,
+ values.temperature,
+ values.honeySuperStatus,
+ values.varroaIssues,
+ values.healthIssues,
+ values.measures,
+ values.notes,
+ );
+
return (
-
-
- Bearbeiten
-
- }
- />
+ } />
Durchsicht bearbeiten
-
diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/swarm-control-edit-dialog.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/swarm-control-edit-dialog.tsx
index bc78f6f..97654d3 100644
--- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/swarm-control-edit-dialog.tsx
+++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/swarm-control-edit-dialog.tsx
@@ -1,15 +1,12 @@
"use client";
import { FormEvent, useState } from "react";
-import { Pencil } from "lucide-react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
-import { toast } from "sonner";
import { updateSwarmControlCheck } from "@/lib/swarm-control/api";
import { SWARM_ACTION_LABELS, SWARM_MOOD_LABELS } from "@/lib/swarm-control/labels";
import { SwarmControlAction, SwarmControlCheck, SwarmMood } from "@/lib/swarm-control/types";
-import { Button } from "@/components/ui/button";
+import { errorMessage } from "@/lib/forms/error-message";
import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
@@ -19,6 +16,9 @@ import {
SelectValue,
} from "@/components/ui/select";
import { ColonySelectField } from "@/components/colony-select-field";
+import { Field } from "@/components/form/field";
+import { FormSection } from "@/components/form/form-section";
+import { ToggleChip } from "@/components/form/toggle-chip";
import {
Dialog,
DialogContent,
@@ -26,6 +26,12 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
+import {
+ DATE_REQUIRED_MESSAGE,
+ DialogSubmitBar,
+ EditTriggerButton,
+ hasAnyValue,
+} from "./edit-dialog-parts";
export function SwarmControlEditDialog({ check }: { check: SwarmControlCheck }) {
const queryClient = useQueryClient();
@@ -38,6 +44,8 @@ export function SwarmControlEditDialog({ check }: { check: SwarmControlCheck })
const [actionTaken, setActionTaken] = useState(check.actionTaken);
const [nextCheckDate, setNextCheckDate] = useState(check.nextCheckDate?.slice(0, 10) ?? "");
const [notes, setNotes] = useState(check.notes ?? "");
+ const [dateError, setDateError] = useState(null);
+ const [submitError, setSubmitError] = useState(null);
const mutation = useMutation({
mutationFn: () =>
@@ -61,124 +69,144 @@ export function SwarmControlEditDialog({ check }: { check: SwarmControlCheck })
}
setOpen(false);
},
- onError: () => toast.error("Schwarmkontrolle konnte nicht gespeichert werden"),
+ onError: (error) =>
+ setSubmitError(errorMessage(error, "Schwarmkontrolle konnte nicht gespeichert werden.")),
});
function handleSubmit(event: FormEvent) {
event.preventDefault();
+ setSubmitError(null);
+ if (!date) {
+ setDateError(DATE_REQUIRED_MESSAGE);
+ return;
+ }
mutation.mutate();
}
return (
-
-
- Bearbeiten
-
- }
- />
+ } />
Schwarmkontrolle bearbeiten
-
-
-
- Datum
- setDate(event.target.value)}
+
+
+
-
-
- Weiselzellen gefunden
- setQueenCellsFound(event.target.value)}
- />
-
-
-
+ {({ id, describedBy, invalid }) => (
+ {
+ setDate(event.target.value);
+ setDateError(null);
+ }}
+ />
+ )}
+
+
+
+
+
+ {({ id, describedBy }) => (
+ setQueenCellsFound(event.target.value)}
+ />
+ )}
+
+ setQueenCellsCapped(event.target.checked)}
+ onChange={setQueenCellsCapped}
/>
- Weiselzellen bereits verdeckelt (akut)
-
-
- Schwarmstimmung
- setSwarmMood((value ?? swarmMood) as SwarmMood)}
- >
-
- {SWARM_MOOD_LABELS[swarmMood]}
-
-
- {(Object.keys(SWARM_MOOD_LABELS) as SwarmMood[]).map((option) => (
-
- {SWARM_MOOD_LABELS[option]}
-
- ))}
-
-
-
-
- Maßnahme
-
- setActionTaken((value ?? actionTaken) as SwarmControlAction)
- }
- >
-
- {SWARM_ACTION_LABELS[actionTaken]}
-
-
- {(Object.keys(SWARM_ACTION_LABELS) as SwarmControlAction[]).map((option) => (
-
- {SWARM_ACTION_LABELS[option]}
-
- ))}
-
-
-
-
- Nächste Kontrolle
- setNextCheckDate(event.target.value)}
- />
-
-
- Notizen
- setNotes(event.target.value)}
- />
-
-
- {mutation.isPending ? "Speichert…" : "Speichern"}
-
+
+ {({ id }) => (
+ setSwarmMood((value ?? swarmMood) as SwarmMood)}
+ >
+
+ {SWARM_MOOD_LABELS[swarmMood]}
+
+
+ {(Object.keys(SWARM_MOOD_LABELS) as SwarmMood[]).map((option) => (
+
+ {SWARM_MOOD_LABELS[option]}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+ {({ id }) => (
+
+ setActionTaken((value ?? actionTaken) as SwarmControlAction)
+ }
+ >
+
+ {SWARM_ACTION_LABELS[actionTaken]}
+
+
+ {(Object.keys(SWARM_ACTION_LABELS) as SwarmControlAction[]).map((option) => (
+
+ {SWARM_ACTION_LABELS[option]}
+
+ ))}
+
+
+ )}
+
+
+ {({ id }) => (
+ setNextCheckDate(event.target.value)}
+ />
+ )}
+
+
+
+
+
+ {({ id }) => (
+ setNotes(event.target.value)} />
+ )}
+
+
+
+
diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/treatment-edit-dialog.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/treatment-edit-dialog.tsx
index d09ed5f..ab82bdf 100644
--- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/treatment-edit-dialog.tsx
+++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/treatment-edit-dialog.tsx
@@ -1,16 +1,15 @@
"use client";
import { FormEvent, useState } from "react";
-import { Pencil } from "lucide-react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
-import { toast } from "sonner";
import { updateTreatment } from "@/lib/treatments/api";
import { Treatment } from "@/lib/treatments/types";
-import { Button } from "@/components/ui/button";
+import { errorMessage } from "@/lib/forms/error-message";
import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { ColonySelectField } from "@/components/colony-select-field";
+import { Field } from "@/components/form/field";
+import { FormSection } from "@/components/form/form-section";
import {
Dialog,
DialogContent,
@@ -18,6 +17,17 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
+import {
+ DATE_REQUIRED_MESSAGE,
+ DialogSubmitBar,
+ EditTriggerButton,
+ hasAnyValue,
+} from "./edit-dialog-parts";
+
+interface FieldErrors {
+ date?: string;
+ product?: string;
+}
export function TreatmentEditDialog({ treatment }: { treatment: Treatment }) {
const queryClient = useQueryClient();
@@ -39,6 +49,8 @@ export function TreatmentEditDialog({ treatment }: { treatment: Treatment }) {
const [withdrawalPeriodDays, setWithdrawalPeriodDays] = useState(
treatment.withdrawalPeriodDays?.toString() ?? "",
);
+ const [fieldErrors, setFieldErrors] = useState({});
+ const [submitError, setSubmitError] = useState(null);
const mutation = useMutation({
mutationFn: () =>
@@ -69,168 +81,221 @@ export function TreatmentEditDialog({ treatment }: { treatment: Treatment }) {
}
setOpen(false);
},
- onError: () => toast.error("Behandlung konnte nicht gespeichert werden"),
+ onError: (error) =>
+ setSubmitError(errorMessage(error, "Behandlung konnte nicht gespeichert werden.")),
});
function handleSubmit(event: FormEvent) {
event.preventDefault();
+ setSubmitError(null);
+ const errors: FieldErrors = {
+ date: date ? undefined : DATE_REQUIRED_MESSAGE,
+ product: product.trim() ? undefined : "Bitte das verwendete Mittel angeben.",
+ };
+ setFieldErrors(errors);
+ if (errors.date || errors.product) {
+ return;
+ }
mutation.mutate();
}
return (
-
-
- Bearbeiten
-
- }
- />
+ } />
Behandlung bearbeiten
-
-
-
- Datum
- setDate(event.target.value)}
+
+
+
-
-
- Beginn
- setStartDate(event.target.value)}
- />
-
-
- Ende
- setEndDate(event.target.value)}
- />
-
-
- Mittel
- setProduct(event.target.value)}
- />
-
-
- Wirkstoff
- setActiveIngredient(event.target.value)}
- />
-
-
- Menge
- setAmount(event.target.value)}
- />
-
-
- Methode
- setMethod(event.target.value)}
- />
-
-
- Grund
- setReason(event.target.value)}
- />
-
-
- Chargennummer
- setBatchNumber(event.target.value)}
- />
-
-
- Wartezeit (Tage)
- setWithdrawalPeriodDays(event.target.value)}
- />
-
-
- Für das Bestandsbuch
-
- Pflicht bei jedem Tierarzneimittel, auch bei freiverkäuflichen Säuren (Art. 108
- VO (EU) 2019/6). Belege 5 Jahre aufbewahren.
-
-
-
Lieferant (Name, Anschrift)
-
setSupplier(event.target.value)}
- />
+
+ {({ id, describedBy, invalid }) => (
+ {
+ setDate(event.target.value);
+ setFieldErrors((current) => ({ ...current, date: undefined }));
+ }}
+ />
+ )}
+
+
+
+
+
+ {({ id, describedBy, invalid }) => (
+ {
+ setProduct(event.target.value);
+ setFieldErrors((current) => ({ ...current, product: undefined }));
+ }}
+ />
+ )}
+
+
+ {({ id }) => (
+ setActiveIngredient(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+ setAmount(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+ setMethod(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+ setReason(event.target.value)}
+ />
+ )}
+
+
+
+
+
+
+ {({ id }) => (
+ setStartDate(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+ setEndDate(event.target.value)}
+ />
+ )}
+
-
- Beleg für den Erwerb
- setPurchaseProof(event.target.value)}
- />
-
-
- Verschreibender Tierarzt (falls zutreffend)
- setPrescribingVet(event.target.value)}
- />
-
-
-
- Notizen
- setNotes(event.target.value)}
- />
-
-
- {mutation.isPending ? "Speichert…" : "Speichern"}
-
+
+ {({ id }) => (
+ setWithdrawalPeriodDays(event.target.value)}
+ />
+ )}
+
+
+
+
+
+ {({ id }) => (
+ setBatchNumber(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+ setSupplier(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+ setPurchaseProof(event.target.value)}
+ />
+ )}
+
+
+ {({ id, describedBy }) => (
+ setPrescribingVet(event.target.value)}
+ />
+ )}
+
+
+
+
+
+ {({ id }) => (
+ setNotes(event.target.value)} />
+ )}
+
+
+
+
diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/varroa-edit-dialog.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/varroa-edit-dialog.tsx
index 74cd8c3..0a81b76 100644
--- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/varroa-edit-dialog.tsx
+++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/varroa-edit-dialog.tsx
@@ -1,15 +1,12 @@
"use client";
import { FormEvent, useState } from "react";
-import { Pencil } from "lucide-react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
-import { toast } from "sonner";
import { updateVarroaMeasurement } from "@/lib/varroa/api";
import { VarroaMeasurement, VarroaMethod } from "@/lib/varroa/types";
import { VARROA_METHODS, VARROA_METHOD_LABELS } from "@/lib/varroa/labels";
-import { Button } from "@/components/ui/button";
+import { errorMessage } from "@/lib/forms/error-message";
import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
@@ -19,6 +16,8 @@ import {
SelectValue,
} from "@/components/ui/select";
import { ColonySelectField } from "@/components/colony-select-field";
+import { Field } from "@/components/form/field";
+import { FormSection } from "@/components/form/form-section";
import {
Dialog,
DialogContent,
@@ -26,6 +25,12 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
+import {
+ DATE_REQUIRED_MESSAGE,
+ DialogSubmitBar,
+ EditTriggerButton,
+ hasAnyValue,
+} from "./edit-dialog-parts";
export function VarroaEditDialog({ measurement }: { measurement: VarroaMeasurement }) {
const queryClient = useQueryClient();
@@ -42,6 +47,8 @@ export function VarroaEditDialog({ measurement }: { measurement: VarroaMeasureme
);
const [assessment, setAssessment] = useState(measurement.assessment ?? "");
const [notes, setNotes] = useState(measurement.notes ?? "");
+ const [dateError, setDateError] = useState
(null);
+ const [submitError, setSubmitError] = useState(null);
const mutation = useMutation({
mutationFn: () =>
@@ -65,111 +72,142 @@ export function VarroaEditDialog({ measurement }: { measurement: VarroaMeasureme
}
setOpen(false);
},
- onError: () => toast.error("Varroamessung konnte nicht gespeichert werden"),
+ onError: (error) =>
+ setSubmitError(errorMessage(error, "Varroamessung konnte nicht gespeichert werden.")),
});
function handleSubmit(event: FormEvent) {
event.preventDefault();
+ setSubmitError(null);
+ if (!date) {
+ setDateError(DATE_REQUIRED_MESSAGE);
+ return;
+ }
mutation.mutate();
}
return (
-
-
- Bearbeiten
-
- }
- />
+ } />
Varroamessung bearbeiten
-
-
-
- Datum
- setDate(event.target.value)}
+
+
+
-
-
- Methode
- setMethod((value ?? method) as VarroaMethod)}
- >
-
- {VARROA_METHOD_LABELS[method]}
-
-
- {VARROA_METHODS.map((option) => (
-
- {VARROA_METHOD_LABELS[option]}
-
- ))}
-
-
-
-
- Milbenzahl
- setMiteCount(event.target.value)}
- />
-
-
- Zeitraum (Minuten)
- setDurationMinutes(event.target.value)}
- />
-
-
- Milben pro Tag
- setCalculatedValue(event.target.value)}
- />
-
-
- Einschätzung
- setAssessment(event.target.value)}
- />
-
-
- Notizen
- setNotes(event.target.value)}
- />
-
-
- {mutation.isPending ? "Speichert…" : "Speichern"}
-
+
+ {({ id, describedBy, invalid }) => (
+ {
+ setDate(event.target.value);
+ setDateError(null);
+ }}
+ />
+ )}
+
+
+
+
+
+ {({ id }) => (
+ setMethod((value ?? method) as VarroaMethod)}
+ >
+
+ {VARROA_METHOD_LABELS[method]}
+
+
+ {VARROA_METHODS.map((option) => (
+
+ {VARROA_METHOD_LABELS[option]}
+
+ ))}
+
+
+ )}
+
+
+
+ {({ id }) => (
+ setMiteCount(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+ setDurationMinutes(event.target.value)}
+ />
+ )}
+
+
+
+ {({ id }) => (
+ setCalculatedValue(event.target.value)}
+ />
+ )}
+
+
+
+
+
+ {({ id }) => (
+ setAssessment(event.target.value)}
+ />
+ )}
+
+
+ {({ id }) => (
+ setNotes(event.target.value)} />
+ )}
+
+
+
+