From d59153a6975d5668d3f509ad084993640e1d17e9 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Fri, 21 Aug 2026 08:59:18 +0200 Subject: [PATCH] feat(frontend): Bienenstaende-Views (Modul 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /apiaries: Liste mit Status-Badge; /apiaries/new: Anlegen-Formular; /apiaries/[id]: Bearbeiten (Name/Standort/Adresse/Notizen) + Deaktivieren. Editierbares Formular liest den Ausgangszustand direkt aus der geladenen Apiary (kein useEffect+setState-Sync, React-Key-Reset-Pattern statt Effect - vermeidet kaskadierende Renders). (app)/layout.tsx: einfache Nav-Leiste (Bienenstände fuer alle, Benutzer nur fuer Admins) - Platzhalter bis zur echten Bottom-Nav/Sidebar aus Modul 15. proxy.ts-Matcher um /apiaries/* erweitert. Live im Browser gefunden+behoben: Base-UI-Button meldete fehlende native Button-Semantik beim Rendern als Link (`nativeButton={false}` noetig, wenn Button auf ein zeigt). 8 Vitest-Tests, Lint, Typecheck und Build gruen. Kompletter Flow (Anlegen, Liste, Bearbeiten, Deaktivieren, Navigation) live verifiziert. --- ApiCore-Frontend/proxy.ts | 2 +- .../(app)/apiaries/[id]/apiary-edit-card.tsx | 111 ++++++++++++++++++ .../src/app/(app)/apiaries/[id]/page.tsx | 31 +++++ .../src/app/(app)/apiaries/new/page.tsx | 103 ++++++++++++++++ .../src/app/(app)/apiaries/page.spec.tsx | 45 +++++++ .../src/app/(app)/apiaries/page.tsx | 69 +++++++++++ ApiCore-Frontend/src/app/(app)/layout.tsx | 16 ++- ApiCore-Frontend/src/lib/apiaries/api.ts | 28 +++++ ApiCore-Frontend/src/lib/apiaries/types.ts | 24 ++++ TASKS.md | 2 +- 10 files changed, 428 insertions(+), 3 deletions(-) create mode 100644 ApiCore-Frontend/src/app/(app)/apiaries/[id]/apiary-edit-card.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/apiaries/[id]/page.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/apiaries/new/page.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/apiaries/page.spec.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/apiaries/page.tsx create mode 100644 ApiCore-Frontend/src/lib/apiaries/api.ts create mode 100644 ApiCore-Frontend/src/lib/apiaries/types.ts diff --git a/ApiCore-Frontend/proxy.ts b/ApiCore-Frontend/proxy.ts index 60d2189..970ac5e 100644 --- a/ApiCore-Frontend/proxy.ts +++ b/ApiCore-Frontend/proxy.ts @@ -22,5 +22,5 @@ export function proxy(request: NextRequest) { } export const config = { - matcher: ["/login", "/settings/:path*"], + matcher: ["/login", "/settings/:path*", "/apiaries/:path*"], }; 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 new file mode 100644 index 0000000..3fd30e3 --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/apiaries/[id]/apiary-edit-card.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { FormEvent, useState } from "react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { deactivateApiary, updateApiary } from "@/lib/apiaries/api"; +import { Apiary } from "@/lib/apiaries/types"; +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 { + Card, + CardContent, + CardHeader, + CardTitle, +} from "@/components/ui/card"; + +export function ApiaryEditCard({ apiary }: { apiary: Apiary }) { + const queryClient = useQueryClient(); + const [name, setName] = useState(apiary.name); + const [location, setLocation] = useState(apiary.location ?? ""); + const [address, setAddress] = useState(apiary.address ?? ""); + const [notes, setNotes] = useState(apiary.notes ?? ""); + + const updateMutation = useMutation({ + mutationFn: () => + updateApiary(apiary.id, { + name, + location: location || undefined, + address: address || undefined, + notes: notes || undefined, + }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["apiaries"] }); + }, + }); + + const deactivateMutation = useMutation({ + mutationFn: () => deactivateApiary(apiary.id), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["apiaries"] }); + }, + }); + + function handleSubmit(event: FormEvent) { + event.preventDefault(); + updateMutation.mutate(); + } + + return ( + + + {apiary.name} + + {apiary.status === "ACTIVE" ? "Aktiv" : "Inaktiv"} + + + +
+
+ + setName(event.target.value)} + /> +
+
+ + setLocation(event.target.value)} + /> +
+
+ + setAddress(event.target.value)} + /> +
+
+ +