From 031e47eaadffc3a9929427768e28af16e4fe4299 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Thu, 1 Oct 2026 18:32:33 +0200 Subject: [PATCH] feat(ui): detail pages for colonies, apiaries and queens with one clear main action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New DetailHeader: heading, back link, status as icon + word, ONE primary action and a "Mehr" menu for exports, QR and the rest. - Colony: "Durchsicht erfassen" is the main action; sub-cards show a load error with retry instead of "Noch keine …". - Apiary: error states, weather with units. - Queen: edit card in sections, evaluations as 1-5 tap scales. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../(app)/apiaries/[id]/apiary-edit-card.tsx | 199 ++++---- .../src/app/(app)/apiaries/[id]/page.tsx | 85 ++-- .../app/(app)/apiaries/[id]/weather-card.tsx | 36 +- .../(app)/colonies/[id]/colony-edit-card.tsx | 23 +- .../(app)/colonies/[id]/colony-feedings.tsx | 60 ++- .../(app)/colonies/[id]/colony-history.tsx | 81 +-- .../colonies/[id]/colony-inspections.tsx | 72 +-- .../colonies/[id]/colony-swarm-control.tsx | 84 ++-- .../(app)/colonies/[id]/colony-treatments.tsx | 66 +-- .../app/(app)/colonies/[id]/colony-varroa.tsx | 78 +-- .../src/app/(app)/colonies/[id]/page.spec.tsx | 50 ++ .../src/app/(app)/colonies/[id]/page.tsx | 74 +-- .../queens/[id]/assign-colony-dialog.tsx | 16 +- .../src/app/(app)/queens/[id]/page.tsx | 34 +- .../app/(app)/queens/[id]/queen-edit-card.tsx | 364 ++++++++------ .../(app)/queens/[id]/queen-evaluations.tsx | 461 ++++++++++-------- .../app/(app)/queens/[id]/queen-history.tsx | 44 +- .../app/(app)/queens/[id]/queen-lineage.tsx | 15 +- .../src/components/detail-header.spec.tsx | 56 +++ .../src/components/detail-header.tsx | 196 ++++++++ 20 files changed, 1300 insertions(+), 794 deletions(-) create mode 100644 ApiCore-Frontend/src/app/(app)/colonies/[id]/page.spec.tsx create mode 100644 ApiCore-Frontend/src/components/detail-header.spec.tsx create mode 100644 ApiCore-Frontend/src/components/detail-header.tsx diff --git a/ApiCore-Frontend/src/app/(app)/apiaries/[id]/apiary-edit-card.tsx b/ApiCore-Frontend/src/app/(app)/apiaries/[id]/apiary-edit-card.tsx index c8aa620..24a8819 100644 --- a/ApiCore-Frontend/src/app/(app)/apiaries/[id]/apiary-edit-card.tsx +++ b/ApiCore-Frontend/src/app/(app)/apiaries/[id]/apiary-edit-card.tsx @@ -1,24 +1,17 @@ "use client"; import { FormEvent, useState } from "react"; -import { MapPin } from "lucide-react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { deactivateApiary, reactivateApiary, updateApiary } from "@/lib/apiaries/api"; -import { ApiError } from "@/lib/auth/api-client"; import { Apiary } from "@/lib/apiaries/types"; +import { errorMessage } from "@/lib/forms/error-message"; import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { ConfirmDialog } from "@/components/confirm-dialog"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@/components/ui/card"; +import { Field } from "@/components/form/field"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; export function ApiaryEditCard({ apiary }: { apiary: Apiary }) { const queryClient = useQueryClient(); @@ -29,6 +22,8 @@ export function ApiaryEditCard({ apiary }: { apiary: Apiary }) { const [longitude, setLongitude] = useState(apiary.longitude?.toString() ?? ""); const [address, setAddress] = useState(apiary.address ?? ""); const [notes, setNotes] = useState(apiary.notes ?? ""); + const [nameError, setNameError] = useState(null); + const [submitError, setSubmitError] = useState(null); const updateMutation = useMutation({ mutationFn: () => @@ -43,8 +38,10 @@ export function ApiaryEditCard({ apiary }: { apiary: Apiary }) { }), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ["apiaries"] }); + toast.success("Bienenstand gespeichert"); }, - onError: () => toast.error("Bienenstand konnte nicht gespeichert werden"), + onError: (error) => + setSubmitError(errorMessage(error, "Bienenstand konnte nicht gespeichert werden.")), }); const deactivateMutation = useMutation({ @@ -56,9 +53,7 @@ export function ApiaryEditCard({ apiary }: { apiary: Apiary }) { }); }, onError: (error) => - toast.error( - error instanceof ApiError ? error.message : "Bienenstand konnte nicht deaktiviert werden", - ), + toast.error(errorMessage(error, "Bienenstand konnte nicht deaktiviert werden.")), }); const reactivateMutation = useMutation({ @@ -67,106 +62,120 @@ export function ApiaryEditCard({ apiary }: { apiary: Apiary }) { void queryClient.invalidateQueries({ queryKey: ["apiaries"] }); toast.success("Bienenstand reaktiviert"); }, - onError: () => toast.error("Bienenstand konnte nicht reaktiviert werden"), + onError: (error) => + toast.error(errorMessage(error, "Bienenstand konnte nicht reaktiviert werden.")), }); function handleSubmit(event: FormEvent) { event.preventDefault(); + setSubmitError(null); + if (!name.trim()) { + setNameError("Bitte einen Namen angeben."); + return; + } updateMutation.mutate(); } return ( - -
- - - - {apiary.name} -
- - {apiary.status === "ACTIVE" ? "Aktiv" : "Inaktiv"} - + + Stammdaten -
-
- - setName(event.target.value)} - /> -
-
- -