From ec2e0b01e9bb0c936dc2abc6a2858ecfb0b5c084 Mon Sep 17 00:00:00 2001 From: "t.indorf" Date: Wed, 20 May 2026 23:48:40 +0200 Subject: [PATCH 1/2] Replace native confirms with dialogs --- src/app/dashboard/alert-button.tsx | 33 ++++--- .../erzieher/_components/erzieher-list.tsx | 32 ++++++- .../_components/my-children-manager.tsx | 29 +++++-- src/components/ui/confirm-dialog.tsx | 86 +++++++++++++++++++ 4 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 src/components/ui/confirm-dialog.tsx diff --git a/src/app/dashboard/alert-button.tsx b/src/app/dashboard/alert-button.tsx index 90d0782..fc81c07 100644 --- a/src/app/dashboard/alert-button.tsx +++ b/src/app/dashboard/alert-button.tsx @@ -1,10 +1,11 @@ "use client"; -import { useTransition } from "react"; +import { useState, useTransition } from "react"; import { Loader2, BellRing } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; +import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { triggerAlertAction } from "./actions"; export function AlertButton({ @@ -12,11 +13,10 @@ export function AlertButton({ }: { assignmentId: string; }) { + const [confirmOpen, setConfirmOpen] = useState(false); const [isPending, startTransition] = useTransition(); const handleAlert = () => { - if (!confirm("Bist du sicher? Dies löst den Notdienst-Alarm aus.")) return; - startTransition(async () => { const result = await triggerAlertAction(assignmentId); if ("error" in result && result.error) { @@ -28,13 +28,24 @@ export function AlertButton({ }; return ( - + + + ); } diff --git a/src/app/dashboard/erzieher/_components/erzieher-list.tsx b/src/app/dashboard/erzieher/_components/erzieher-list.tsx index 4f1786e..14d2b99 100644 --- a/src/app/dashboard/erzieher/_components/erzieher-list.tsx +++ b/src/app/dashboard/erzieher/_components/erzieher-list.tsx @@ -5,6 +5,7 @@ import { Plus, Pencil, Search, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; +import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { Dialog, DialogContent, @@ -40,6 +41,9 @@ type EducatorDto = { export function ErzieherList({ educators }: { educators: EducatorDto[] }) { const [openCreate, setOpenCreate] = useState(false); const [editingEducator, setEditingEducator] = useState(null); + const [educatorToDelete, setEducatorToDelete] = useState( + null, + ); const [isPending, startTransition] = useTransition(); const handleCreate = (formData: FormData) => { @@ -79,14 +83,14 @@ export function ErzieherList({ educators }: { educators: EducatorDto[] }) { }); }; - const handleDelete = (id: string) => { - if (!confirm("Wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.")) return; + const handleDelete = (educator: EducatorDto) => { startTransition(async () => { - const res = await deleteEducator(id); + const res = await deleteEducator(educator.id); if (res.error) { toast.error(res.error); } else { toast.success("ErzieherIn gelöscht."); + setEducatorToDelete(null); } }); }; @@ -193,8 +197,9 @@ export function ErzieherList({ educators }: { educators: EducatorDto[] }) { variant="ghost" size="icon" className="text-[var(--danger)] hover:bg-[var(--danger-soft)] hover:text-[var(--danger)]" - onClick={() => handleDelete(ed.id)} + onClick={() => setEducatorToDelete(ed)} disabled={isPending} + aria-label={`${ed.firstName} ${ed.lastName} löschen`} > @@ -237,6 +242,25 @@ export function ErzieherList({ educators }: { educators: EducatorDto[] }) { + + { + if (!open) setEducatorToDelete(null); + }} + title="ErzieherIn löschen?" + description={ + educatorToDelete + ? `${educatorToDelete.firstName} ${educatorToDelete.lastName} wird aus dem Personalstamm entfernt.` + : "Dieser Eintrag wird aus dem Personalstamm entfernt." + } + confirmLabel="Löschen" + pendingLabel="Wird gelöscht..." + isPending={isPending} + onConfirm={() => { + if (educatorToDelete) handleDelete(educatorToDelete); + }} + /> ); } diff --git a/src/app/dashboard/profil/_components/my-children-manager.tsx b/src/app/dashboard/profil/_components/my-children-manager.tsx index 1d6b6c2..1a010a9 100644 --- a/src/app/dashboard/profil/_components/my-children-manager.tsx +++ b/src/app/dashboard/profil/_components/my-children-manager.tsx @@ -5,6 +5,7 @@ import { Pencil, Plus, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; +import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { Dialog, DialogContent, @@ -51,6 +52,7 @@ export function MyChildrenManager({ const [editingChild, setEditingChild] = useState(null); const [editPayload, setEditPayload] = useState(emptyChildPayload); + const [childToDelete, setChildToDelete] = useState(null); const [isPending, startTransition] = useTransition(); function openEditor(child: MyChild) { @@ -91,11 +93,6 @@ export function MyChildrenManager({ } function handleDelete(child: MyChild) { - const confirmed = confirm( - `${child.firstName} ${child.lastName} wirklich entfernen?`, - ); - if (!confirmed) return; - startTransition(async () => { const result = await deleteMyChild(child.id); if (result.error) { @@ -104,6 +101,7 @@ export function MyChildrenManager({ } toast.success("Kind entfernt."); + setChildToDelete(null); }); } @@ -157,7 +155,7 @@ export function MyChildrenManager({ variant="ghost" size="icon" className="h-7 w-7 text-[var(--danger)] hover:bg-[var(--danger-soft)] hover:text-[var(--danger)]" - onClick={() => handleDelete(child)} + onClick={() => setChildToDelete(child)} disabled={isPending || !canManage} aria-label="Kind löschen" > @@ -250,6 +248,25 @@ export function MyChildrenManager({ + + { + if (!open) setChildToDelete(null); + }} + title="Kind entfernen?" + description={ + childToDelete + ? `${childToDelete.firstName} ${childToDelete.lastName} wird aus deinem Haushalt entfernt.` + : "Dieses Kind wird aus deinem Haushalt entfernt." + } + confirmLabel="Entfernen" + pendingLabel="Wird entfernt..." + isPending={isPending} + onConfirm={() => { + if (childToDelete) handleDelete(childToDelete); + }} + /> ); } diff --git a/src/components/ui/confirm-dialog.tsx b/src/components/ui/confirm-dialog.tsx new file mode 100644 index 0000000..8af9a37 --- /dev/null +++ b/src/components/ui/confirm-dialog.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { type ReactNode } from "react"; +import { AlertTriangle } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; + +type ConfirmDialogProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description: ReactNode; + confirmLabel: string; + cancelLabel?: string; + pendingLabel?: string; + isPending?: boolean; + children?: ReactNode; + onConfirm: () => void | Promise; +}; + +export function ConfirmDialog({ + open, + onOpenChange, + title, + description, + confirmLabel, + cancelLabel = "Abbrechen", + pendingLabel = "Wird ausgeführt...", + isPending = false, + children, + onConfirm, +}: ConfirmDialogProps) { + async function handleConfirm() { + await onConfirm(); + onOpenChange(false); + } + + return ( + + {children ? {children} : null} + + + + + {title} + + + {description} + + + +
+ Diese Aktion kann nicht rückgängig gemacht werden. +
+ + + + + +
+
+ ); +} From beffba65c5eee39c518fb51062a847bdc40e96e6 Mon Sep 17 00:00:00 2001 From: "t.indorf" Date: Wed, 20 May 2026 23:50:21 +0200 Subject: [PATCH 2/2] Document UI no-op follow-ups --- UI-TODO.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 UI-TODO.md diff --git a/UI-TODO.md b/UI-TODO.md new file mode 100644 index 0000000..509947c --- /dev/null +++ b/UI-TODO.md @@ -0,0 +1,79 @@ +# UI-ToDo: klickbare Elemente ohne Funktion + +Stand: 2026-05-20 + +Geprüft lokal als Admin mit Demo-Login `admin@waldameisen.local`. + +## Notdienst-Planung + +- [ ] `src/app/dashboard/notdienst/plan/page.tsx:186` + Monat zurück/vor haben keinen Handler oder Link. + +- [ ] `src/app/dashboard/notdienst/plan/page.tsx:196` + `Einstellungen` ist klickbar, öffnet aber nichts und navigiert nicht. + +- [ ] `src/app/dashboard/notdienst/plan/plan-view.tsx:135` + `Erinnerungen verschicken` im Empty-State ist ohne Funktion. + +- [ ] `src/app/dashboard/notdienst/plan/plan-view.tsx:202` + `Erinnerung an offene senden (...)` ist ohne Funktion. + +- [ ] `src/app/dashboard/notdienst/plan/plan-view.tsx:312` + `Als PDF exportieren` ist ohne Funktion. + +- [ ] `src/app/dashboard/notdienst/plan/plan-view.tsx:316` + `Plan zurücksetzen` ist ohne Funktion. + +## Terminkalender + +- [ ] `src/app/dashboard/kalender/page.tsx:296` + Umschalter `Liste / Monat / Agenda` ändern keine Ansicht. + +- [ ] `src/app/dashboard/kalender/page.tsx:317` + Suche `Termine suchen` filtert aktuell nicht. + +- [ ] `src/app/dashboard/kalender/page.tsx:415` + Kategorie-Filterchips sind nur visuell. + +- [ ] `src/app/dashboard/kalender/_components/termin-list.tsx:152` + `Mehr Optionen` bei Elterndiensten ist ohne Funktion. + +- [ ] `src/app/dashboard/kalender/_components/termin-list.tsx:191` + `Mehr Optionen` bei Terminen ist ohne Funktion. + +## Adressbuch + +- [ ] `src/app/dashboard/adressbuch/page.tsx:161` + `Sichtbarkeit` ist ohne Funktion. + +- [ ] `src/app/dashboard/adressbuch/page.tsx:193` + Suche `Familien, Namen oder E-Mail suchen` filtert aktuell nicht. + +- [ ] `src/app/dashboard/adressbuch/page.tsx:201` + Filterchips `Alle Gruppen`, `Mit Telefon`, `Mit Ämtern`, `Nur Opt-in` sind nur visuell. + +- [ ] `src/app/dashboard/adressbuch/page.tsx:334` + `Kontakt öffnen` ist ohne Funktion. + +## Familienverwaltung + +- [ ] `src/app/dashboard/families/page.tsx:117` + `Ansicht` ist ohne Funktion. + +- [ ] `src/app/dashboard/families/page.tsx:133` + Suche `Familien, Eltern oder Kinder suchen` filtert aktuell nicht. + +## Abwesenheiten Admin + +- [ ] `src/app/dashboard/admin/abwesenheiten/page.tsx:132` + Filterchips `Alle Gruppen` und `Alle Gründe` sind klickbar, aber ohne Funktion. + +## Bereits angebunden / nicht auffaellig + +- Dialoge und Mutationsaktionen sind bei der Pruefung nicht als No-Op aufgefallen: + - Familie anlegen/bearbeiten + - ErzieherIn anlegen/bearbeiten/loeschen + - Profil speichern + - Termin anlegen/anfragen + - Dienstplan-Zuweisungen +