ERP-node/frontend/components/common/ConfirmDialog.tsx

119 lines
3.2 KiB
TypeScript

"use client";
/**
* ConfirmDialog — 공통 확인/취소 다이얼로그
*
* 사용법:
* const { confirm, ConfirmDialogComponent } = useConfirmDialog();
*
* // JSX에 넣기
* <ConfirmDialogComponent />
*
* // 호출
* const ok = await confirm("삭제하시겠습니까?", { description: "이 작업은 되돌릴 수 없습니다." });
* if (ok) { ... }
*/
import React, { useState, useCallback, useRef } from "react";
import {
AlertDialog,
AlertDialogContent,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogCancel,
AlertDialogAction,
} from "@/components/ui/alert-dialog";
import { AlertTriangle, Info, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
type ConfirmVariant = "default" | "destructive" | "info";
interface ConfirmOptions {
description?: string;
confirmText?: string;
cancelText?: string;
variant?: ConfirmVariant;
}
const VARIANT_CONFIG = {
default: {
icon: Info,
iconClass: "text-primary",
buttonClass: "",
},
destructive: {
icon: Trash2,
iconClass: "text-destructive",
buttonClass: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
},
info: {
icon: Info,
iconClass: "text-blue-500",
buttonClass: "",
},
};
export function useConfirmDialog() {
const [open, setOpen] = useState(false);
const [title, setTitle] = useState("");
const [options, setOptions] = useState<ConfirmOptions>({});
const resolveRef = useRef<((value: boolean) => void) | null>(null);
const confirm = useCallback((msg: string, opts?: ConfirmOptions): Promise<boolean> => {
setTitle(msg);
setOptions(opts || {});
setOpen(true);
return new Promise<boolean>((resolve) => {
resolveRef.current = resolve;
});
}, []);
const handleConfirm = () => {
setOpen(false);
resolveRef.current?.(true);
};
const handleCancel = () => {
setOpen(false);
resolveRef.current?.(false);
};
const variant = options.variant || "default";
const config = VARIANT_CONFIG[variant];
const Icon = config.icon;
const ConfirmDialogComponent = (
<AlertDialog open={open} onOpenChange={(v) => { if (!v) handleCancel(); }}>
<AlertDialogContent className="max-w-[420px]">
<AlertDialogHeader>
<div className="flex items-start gap-3">
<div className={cn("mt-0.5 shrink-0", config.iconClass)}>
<Icon className="h-5 w-5" />
</div>
<div>
<AlertDialogTitle className="text-base">{title}</AlertDialogTitle>
{options.description && (
<AlertDialogDescription className="mt-1.5 text-sm">
{options.description}
</AlertDialogDescription>
)}
</div>
</div>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancel}>
{options.cancelText || "취소"}
</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirm} className={cn(config.buttonClass)}>
{options.confirmText || "확인"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
return { confirm, ConfirmDialogComponent };
}