- 공통 템플릿 타입 수정 (IntegratedDetailTemplate, UniversalListPage) - 페이지(app/[locale]) 타입 호환성 수정 (80개) - 재고/자재 모듈 타입 수정 (StockStatus, ReceivingManagement) - 생산 모듈 타입 수정 (WorkOrders, WorkerScreen, WorkResults) - 주문/출고 모듈 타입 수정 (ShipmentManagement, Orders) - 견적/단가 모듈 타입 수정 (Quotes, Pricing) - 건설 모듈 타입 수정 (49개, 17개 하위 모듈) - HR 모듈 타입 수정 (CardManagement, VacationManagement 등) - 설정 모듈 타입 수정 (PermissionManagement, AccountManagement 등) - 게시판 모듈 타입 수정 (BoardManagement, BoardList 등) - 회계 모듈 타입 수정 (VendorManagement, BadDebtCollection 등) - 기타 모듈 타입 수정 (CEODashboard, clients, vehicle 등) - 유틸/훅/API 타입 수정 (hooks, contexts, lib) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
226 lines
6.1 KiB
TypeScript
226 lines
6.1 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* ConfirmDialog - 확인/취소 다이얼로그 공통 컴포넌트
|
|
*
|
|
* 사용 예시:
|
|
* ```tsx
|
|
* <ConfirmDialog
|
|
* open={showDeleteDialog}
|
|
* onOpenChange={setShowDeleteDialog}
|
|
* title="삭제 확인"
|
|
* description="정말 삭제하시겠습니까?"
|
|
* confirmText="삭제"
|
|
* variant="destructive"
|
|
* loading={isLoading}
|
|
* onConfirm={handleDelete}
|
|
* />
|
|
* ```
|
|
*/
|
|
|
|
import { ReactNode, useCallback, useState } from 'react';
|
|
import { Loader2 } from 'lucide-react';
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from '@/components/ui/alert-dialog';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
export type ConfirmDialogVariant = 'default' | 'destructive' | 'warning' | 'success';
|
|
|
|
export interface ConfirmDialogProps {
|
|
/** 다이얼로그 열림 상태 */
|
|
open: boolean;
|
|
/** 열림 상태 변경 핸들러 */
|
|
onOpenChange: (open: boolean) => void;
|
|
/** 다이얼로그 제목 */
|
|
title: ReactNode;
|
|
/** 다이얼로그 설명 (문자열 또는 ReactNode) */
|
|
description: ReactNode;
|
|
/** 확인 버튼 텍스트 (기본값: '확인') */
|
|
confirmText?: string;
|
|
/** 취소 버튼 텍스트 (기본값: '취소') */
|
|
cancelText?: string;
|
|
/** 버튼 스타일 변형 */
|
|
variant?: ConfirmDialogVariant;
|
|
/** 외부 로딩 상태 (외부에서 관리할 때) */
|
|
loading?: boolean;
|
|
/** 확인 버튼 클릭 핸들러 (Promise 반환 시 내부 로딩 상태 자동 관리) */
|
|
onConfirm: () => void | Promise<void>;
|
|
/** 취소 버튼 클릭 핸들러 (선택사항) */
|
|
onCancel?: () => void;
|
|
/** 확인 버튼 비활성화 여부 */
|
|
confirmDisabled?: boolean;
|
|
/** 아이콘 (제목 옆에 표시) */
|
|
icon?: ReactNode;
|
|
}
|
|
|
|
const variantStyles: Record<ConfirmDialogVariant, string> = {
|
|
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
warning: 'bg-orange-600 text-white hover:bg-orange-700',
|
|
success: 'bg-green-600 text-white hover:bg-green-700',
|
|
};
|
|
|
|
export function ConfirmDialog({
|
|
open,
|
|
onOpenChange,
|
|
title,
|
|
description,
|
|
confirmText = '확인',
|
|
cancelText = '취소',
|
|
variant = 'default',
|
|
loading: externalLoading,
|
|
onConfirm,
|
|
onCancel,
|
|
confirmDisabled,
|
|
icon,
|
|
}: ConfirmDialogProps) {
|
|
const [internalLoading, setInternalLoading] = useState(false);
|
|
const isLoading = externalLoading ?? internalLoading;
|
|
|
|
const handleConfirm = useCallback(async () => {
|
|
const result = onConfirm();
|
|
|
|
// Promise인 경우 내부 로딩 상태 관리
|
|
if (result instanceof Promise && externalLoading === undefined) {
|
|
setInternalLoading(true);
|
|
try {
|
|
await result;
|
|
} finally {
|
|
setInternalLoading(false);
|
|
}
|
|
}
|
|
}, [onConfirm, externalLoading]);
|
|
|
|
const handleCancel = useCallback(() => {
|
|
onCancel?.();
|
|
onOpenChange(false);
|
|
}, [onCancel, onOpenChange]);
|
|
|
|
return (
|
|
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle className="flex items-center gap-2">
|
|
{icon}
|
|
{title}
|
|
</AlertDialogTitle>
|
|
<AlertDialogDescription asChild={typeof description !== 'string'}>
|
|
{typeof description === 'string' ? (
|
|
description
|
|
) : (
|
|
<div>{description}</div>
|
|
)}
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel onClick={handleCancel} disabled={isLoading}>
|
|
{cancelText}
|
|
</AlertDialogCancel>
|
|
<AlertDialogAction
|
|
onClick={handleConfirm}
|
|
disabled={isLoading || confirmDisabled}
|
|
className={cn(variantStyles[variant])}
|
|
>
|
|
{isLoading && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
|
{confirmText}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 삭제 확인 다이얼로그 프리셋
|
|
*/
|
|
export interface DeleteConfirmDialogProps
|
|
extends Omit<ConfirmDialogProps, 'title' | 'confirmText' | 'variant'> {
|
|
/** 삭제 대상 이름 (선택사항) */
|
|
itemName?: string;
|
|
/** 커스텀 제목 (기본: '삭제 확인') */
|
|
title?: string;
|
|
}
|
|
|
|
export function DeleteConfirmDialog({
|
|
itemName,
|
|
description,
|
|
title,
|
|
...props
|
|
}: DeleteConfirmDialogProps) {
|
|
return (
|
|
<ConfirmDialog
|
|
title={title || '삭제 확인'}
|
|
description={
|
|
description ?? (
|
|
<>
|
|
{itemName ? `"${itemName}"을(를) ` : ''}정말 삭제하시겠습니까?
|
|
<br />
|
|
삭제된 데이터는 복구할 수 없습니다.
|
|
</>
|
|
)
|
|
}
|
|
confirmText="삭제"
|
|
variant="destructive"
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 저장 확인 다이얼로그 프리셋
|
|
*/
|
|
export interface SaveConfirmDialogProps
|
|
extends Omit<ConfirmDialogProps, 'title' | 'confirmText' | 'variant' | 'description'> {
|
|
/** 커스텀 제목 (기본: '저장 확인') */
|
|
title?: string;
|
|
/** 커스텀 설명 (기본: '변경사항을 저장하시겠습니까?') */
|
|
description?: ReactNode;
|
|
}
|
|
|
|
export function SaveConfirmDialog({
|
|
description = '변경사항을 저장하시겠습니까?',
|
|
title,
|
|
...props
|
|
}: SaveConfirmDialogProps) {
|
|
return (
|
|
<ConfirmDialog
|
|
title={title || '저장 확인'}
|
|
description={description}
|
|
confirmText="저장"
|
|
variant="default"
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 취소 확인 다이얼로그 프리셋
|
|
*/
|
|
export interface CancelConfirmDialogProps
|
|
extends Omit<ConfirmDialogProps, 'title' | 'confirmText' | 'variant'> {}
|
|
|
|
export function CancelConfirmDialog({
|
|
description = '작업을 취소하시겠습니까? 변경사항이 저장되지 않습니다.',
|
|
...props
|
|
}: CancelConfirmDialogProps) {
|
|
return (
|
|
<ConfirmDialog
|
|
title="취소 확인"
|
|
description={description}
|
|
confirmText="취소"
|
|
variant="warning"
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default ConfirmDialog;
|