111 lines
2.9 KiB
TypeScript
111 lines
2.9 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* UnsavedChangesGuard
|
||
|
|
*
|
||
|
|
* 모달에서 저장하지 않은 변경사항이 있을 때 닫기를 시도하면
|
||
|
|
* 확인 다이얼로그를 띄우는 공통 모듈.
|
||
|
|
*
|
||
|
|
* 사용법:
|
||
|
|
* 1. useUnsavedChangesGuard 훅으로 상태 관리
|
||
|
|
* 2. UnsavedChangesDialog 컴포넌트로 확인 다이얼로그 렌더링
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* const guard = useUnsavedChangesGuard({
|
||
|
|
* hasChanges: () => JSON.stringify(data) !== initialSnapshot,
|
||
|
|
* onClose: () => onOpenChange(false),
|
||
|
|
* });
|
||
|
|
*
|
||
|
|
* <Dialog open={open} onOpenChange={guard.handleOpenChange}>
|
||
|
|
* ...
|
||
|
|
* <Button onClick={guard.tryClose}>취소</Button>
|
||
|
|
* </Dialog>
|
||
|
|
* <UnsavedChangesDialog guard={guard} />
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { useState, useCallback } from "react";
|
||
|
|
import {
|
||
|
|
AlertDialog,
|
||
|
|
AlertDialogAction,
|
||
|
|
AlertDialogCancel,
|
||
|
|
AlertDialogContent,
|
||
|
|
AlertDialogDescription,
|
||
|
|
AlertDialogFooter,
|
||
|
|
AlertDialogHeader,
|
||
|
|
AlertDialogTitle,
|
||
|
|
} from "@/components/ui/alert-dialog";
|
||
|
|
|
||
|
|
interface UseUnsavedChangesGuardOptions {
|
||
|
|
hasChanges: () => boolean;
|
||
|
|
onClose: () => void;
|
||
|
|
title?: string;
|
||
|
|
description?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface UnsavedChangesGuardState {
|
||
|
|
showConfirm: boolean;
|
||
|
|
setShowConfirm: (v: boolean) => void;
|
||
|
|
tryClose: () => void;
|
||
|
|
doClose: () => void;
|
||
|
|
handleOpenChange: (open: boolean) => void;
|
||
|
|
title: string;
|
||
|
|
description: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useUnsavedChangesGuard({
|
||
|
|
hasChanges,
|
||
|
|
onClose,
|
||
|
|
title = "저장하지 않은 변경사항",
|
||
|
|
description = "저장하지 않은 변경사항이 있습니다. 저장하지 않고 닫으시겠습니까?",
|
||
|
|
}: UseUnsavedChangesGuardOptions): UnsavedChangesGuardState {
|
||
|
|
const [showConfirm, setShowConfirm] = useState(false);
|
||
|
|
|
||
|
|
const doClose = useCallback(() => {
|
||
|
|
setShowConfirm(false);
|
||
|
|
onClose();
|
||
|
|
}, [onClose]);
|
||
|
|
|
||
|
|
const tryClose = useCallback(() => {
|
||
|
|
if (hasChanges()) {
|
||
|
|
setShowConfirm(true);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
doClose();
|
||
|
|
}, [hasChanges, doClose]);
|
||
|
|
|
||
|
|
const handleOpenChange = useCallback(
|
||
|
|
(open: boolean) => {
|
||
|
|
if (!open) tryClose();
|
||
|
|
},
|
||
|
|
[tryClose],
|
||
|
|
);
|
||
|
|
|
||
|
|
return { showConfirm, setShowConfirm, tryClose, doClose, handleOpenChange, title, description };
|
||
|
|
}
|
||
|
|
|
||
|
|
interface UnsavedChangesDialogProps {
|
||
|
|
guard: UnsavedChangesGuardState;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function UnsavedChangesDialog({ guard }: UnsavedChangesDialogProps) {
|
||
|
|
return (
|
||
|
|
<AlertDialog open={guard.showConfirm} onOpenChange={guard.setShowConfirm}>
|
||
|
|
<AlertDialogContent>
|
||
|
|
<AlertDialogHeader>
|
||
|
|
<AlertDialogTitle>{guard.title}</AlertDialogTitle>
|
||
|
|
<AlertDialogDescription>{guard.description}</AlertDialogDescription>
|
||
|
|
</AlertDialogHeader>
|
||
|
|
<AlertDialogFooter>
|
||
|
|
<AlertDialogAction
|
||
|
|
onClick={guard.doClose}
|
||
|
|
className="bg-red-600 text-white hover:bg-red-700"
|
||
|
|
>
|
||
|
|
닫기
|
||
|
|
</AlertDialogAction>
|
||
|
|
<AlertDialogCancel>취소</AlertDialogCancel>
|
||
|
|
</AlertDialogFooter>
|
||
|
|
</AlertDialogContent>
|
||
|
|
</AlertDialog>
|
||
|
|
);
|
||
|
|
}
|