Replace native confirms with dialogs
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTransition } from "react";
|
import { useState, useTransition } from "react";
|
||||||
import { Loader2, BellRing } from "lucide-react";
|
import { Loader2, BellRing } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import { triggerAlertAction } from "./actions";
|
import { triggerAlertAction } from "./actions";
|
||||||
|
|
||||||
export function AlertButton({
|
export function AlertButton({
|
||||||
@@ -12,11 +13,10 @@ export function AlertButton({
|
|||||||
}: {
|
}: {
|
||||||
assignmentId: string;
|
assignmentId: string;
|
||||||
}) {
|
}) {
|
||||||
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const handleAlert = () => {
|
const handleAlert = () => {
|
||||||
if (!confirm("Bist du sicher? Dies löst den Notdienst-Alarm aus.")) return;
|
|
||||||
|
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
const result = await triggerAlertAction(assignmentId);
|
const result = await triggerAlertAction(assignmentId);
|
||||||
if ("error" in result && result.error) {
|
if ("error" in result && result.error) {
|
||||||
@@ -28,13 +28,24 @@ export function AlertButton({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant="destructive" onClick={handleAlert} disabled={isPending}>
|
<ConfirmDialog
|
||||||
{isPending ? (
|
open={confirmOpen}
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
onOpenChange={setConfirmOpen}
|
||||||
) : (
|
title="Notdienst-Alarm auslösen?"
|
||||||
<BellRing className="mr-2 h-4 w-4" />
|
description="Der Alarm informiert die hinterlegten Kontakte per E-Mail über den Notdienst."
|
||||||
)}
|
confirmLabel="Alarm auslösen"
|
||||||
Alarm auslösen
|
pendingLabel="Alarm wird ausgelöst..."
|
||||||
</Button>
|
isPending={isPending}
|
||||||
|
onConfirm={handleAlert}
|
||||||
|
>
|
||||||
|
<Button variant="destructive" disabled={isPending}>
|
||||||
|
{isPending ? (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<BellRing className="mr-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
Alarm auslösen
|
||||||
|
</Button>
|
||||||
|
</ConfirmDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Plus, Pencil, Search, Trash2 } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -40,6 +41,9 @@ type EducatorDto = {
|
|||||||
export function ErzieherList({ educators }: { educators: EducatorDto[] }) {
|
export function ErzieherList({ educators }: { educators: EducatorDto[] }) {
|
||||||
const [openCreate, setOpenCreate] = useState(false);
|
const [openCreate, setOpenCreate] = useState(false);
|
||||||
const [editingEducator, setEditingEducator] = useState<EducatorDto | null>(null);
|
const [editingEducator, setEditingEducator] = useState<EducatorDto | null>(null);
|
||||||
|
const [educatorToDelete, setEducatorToDelete] = useState<EducatorDto | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const handleCreate = (formData: FormData) => {
|
const handleCreate = (formData: FormData) => {
|
||||||
@@ -79,14 +83,14 @@ export function ErzieherList({ educators }: { educators: EducatorDto[] }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (educator: EducatorDto) => {
|
||||||
if (!confirm("Wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.")) return;
|
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
const res = await deleteEducator(id);
|
const res = await deleteEducator(educator.id);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
toast.error(res.error);
|
toast.error(res.error);
|
||||||
} else {
|
} else {
|
||||||
toast.success("ErzieherIn gelöscht.");
|
toast.success("ErzieherIn gelöscht.");
|
||||||
|
setEducatorToDelete(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -193,8 +197,9 @@ export function ErzieherList({ educators }: { educators: EducatorDto[] }) {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="text-[var(--danger)] hover:bg-[var(--danger-soft)] hover:text-[var(--danger)]"
|
className="text-[var(--danger)] hover:bg-[var(--danger-soft)] hover:text-[var(--danger)]"
|
||||||
onClick={() => handleDelete(ed.id)}
|
onClick={() => setEducatorToDelete(ed)}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
|
aria-label={`${ed.firstName} ${ed.lastName} löschen`}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4 [stroke-width:1.7]" />
|
<Trash2 className="h-4 w-4 [stroke-width:1.7]" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -237,6 +242,25 @@ export function ErzieherList({ educators }: { educators: EducatorDto[] }) {
|
|||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
open={!!educatorToDelete}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Pencil, Plus, Trash2 } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -51,6 +52,7 @@ export function MyChildrenManager({
|
|||||||
const [editingChild, setEditingChild] = useState<MyChild | null>(null);
|
const [editingChild, setEditingChild] = useState<MyChild | null>(null);
|
||||||
const [editPayload, setEditPayload] =
|
const [editPayload, setEditPayload] =
|
||||||
useState<ChildPayload>(emptyChildPayload);
|
useState<ChildPayload>(emptyChildPayload);
|
||||||
|
const [childToDelete, setChildToDelete] = useState<MyChild | null>(null);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
function openEditor(child: MyChild) {
|
function openEditor(child: MyChild) {
|
||||||
@@ -91,11 +93,6 @@ export function MyChildrenManager({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDelete(child: MyChild) {
|
function handleDelete(child: MyChild) {
|
||||||
const confirmed = confirm(
|
|
||||||
`${child.firstName} ${child.lastName} wirklich entfernen?`,
|
|
||||||
);
|
|
||||||
if (!confirmed) return;
|
|
||||||
|
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
const result = await deleteMyChild(child.id);
|
const result = await deleteMyChild(child.id);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
@@ -104,6 +101,7 @@ export function MyChildrenManager({
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast.success("Kind entfernt.");
|
toast.success("Kind entfernt.");
|
||||||
|
setChildToDelete(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +155,7 @@ export function MyChildrenManager({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-7 w-7 text-[var(--danger)] hover:bg-[var(--danger-soft)] hover:text-[var(--danger)]"
|
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}
|
disabled={isPending || !canManage}
|
||||||
aria-label="Kind löschen"
|
aria-label="Kind löschen"
|
||||||
>
|
>
|
||||||
@@ -250,6 +248,25 @@ export function MyChildrenManager({
|
|||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
open={!!childToDelete}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
{children ? <DialogTrigger asChild>{children}</DialogTrigger> : null}
|
||||||
|
<DialogContent className="border-[var(--danger)]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2 text-[var(--danger)]">
|
||||||
|
<AlertTriangle className="h-5 w-5" />
|
||||||
|
{title}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="pt-1 text-sm leading-6">
|
||||||
|
{description}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-[var(--danger)] bg-[var(--danger-soft)] p-3 text-sm text-[var(--danger)]">
|
||||||
|
Diese Aktion kann nicht rückgängig gemacht werden.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => onOpenChange(false)}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{cancelLabel}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleConfirm}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending ? pendingLabel : confirmLabel}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user