Files
sam-react-prod/src/components/ui/confirm-dialog.tsx
유병철 a1f4c82cec fix: 프로젝트 전체 TypeScript 타입에러 408개 수정 (tsc --noEmit 0 errors)
- 공통 템플릿 타입 수정 (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>
2026-01-30 10:07:58 +09:00

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;