"use client"; import * as React from "react"; import { Loader2, Send, X } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Drawer, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useMediaQuery } from "@/hooks/use-media-query"; import { ApprovalLineSelector, type ApprovalLineItem } from "@/components/knox/approval/ApprovalLineSelector"; import { getApprovalTemplateByName, replaceTemplateVariables } from "./template-utils"; /** * 결재 미리보기 다이얼로그 Props */ export interface ApprovalPreviewDialogProps { /** 다이얼로그 열림 상태 */ open: boolean; /** 다이얼로그 열림 상태 변경 핸들러 */ onOpenChange: (open: boolean) => void; /** 템플릿 이름 (DB에서 조회) */ templateName: string; /** 템플릿 변수 ({{변수명}} 형태로 치환) */ variables: Record; /** 결재 제목 */ title: string; /** 현재 사용자 정보 */ currentUser: { id: number; epId: string; name?: string; email?: string; deptName?: string; }; /** 초기 결재선 (선택사항) */ defaultApprovers?: string[]; /** 확인 버튼 클릭 시 콜백 */ onConfirm: (data: { approvers: string[]; title: string; }) => Promise; /** 제목 수정 가능 여부 (기본: true) */ allowTitleEdit?: boolean; } /** * 결재 미리보기 다이얼로그 컴포넌트 * * **주요 기능:** * 1. 템플릿 실시간 미리보기 (변수 치환) * 2. 결재선 선택 (ApprovalLineSelector 활용) * 3. 제목/설명 수정 * 4. 반응형 UI (Desktop: Dialog, Mobile: Drawer) * * **사용 예시:** * ```tsx * { * await submitApproval(approvers); * }} * /> * ``` */ export function ApprovalPreviewDialog({ open, onOpenChange, templateName, variables, title: initialTitle, currentUser, defaultApprovers = [], onConfirm, allowTitleEdit = true, }: ApprovalPreviewDialogProps) { const isDesktop = useMediaQuery("(min-width: 768px)"); // 로딩 상태 const [isLoadingTemplate, setIsLoadingTemplate] = React.useState(false); const [isSubmitting, setIsSubmitting] = React.useState(false); // 폼 상태 const [title, setTitle] = React.useState(initialTitle); const [approvalLines, setApprovalLines] = React.useState([]); const [previewHtml, setPreviewHtml] = React.useState(""); // 템플릿 로딩 및 미리보기 생성 React.useEffect(() => { if (!open) return; async function loadTemplatePreview() { try { setIsLoadingTemplate(true); // 1. 템플릿 조회 const template = await getApprovalTemplateByName(templateName); if (!template) { toast.error(`템플릿을 찾을 수 없습니다: ${templateName}`); return; } // 2. 변수 치환 const renderedHtml = await replaceTemplateVariables( template.content || "", variables ); setPreviewHtml(renderedHtml); } catch (error) { console.error("[ApprovalPreviewDialog] 템플릿 로딩 실패:", error); toast.error("템플릿을 불러오는 중 오류가 발생했습니다."); } finally { setIsLoadingTemplate(false); } } loadTemplatePreview(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, templateName]); // variables 제거 - 다이얼로그가 열릴 때만 로드 // 다이얼로그 상태 초기화/리셋 React.useEffect(() => { if (!open) { // 다이얼로그가 닫힐 때 상태 초기화 setTitle(initialTitle); setApprovalLines([]); setPreviewHtml(""); return; } // 다이얼로그가 열릴 때 초기화 setTitle(initialTitle); // 상신자 추가 const submitter: ApprovalLineItem = { id: `submitter-${currentUser.id}`, epId: currentUser.epId, userId: currentUser.id.toString(), emailAddress: currentUser.email || "", name: currentUser.name || "상신자", deptName: currentUser.deptName, role: "0", // 상신자 seq: "0", opinion: "", }; // 기본 결재자들 추가 (있는 경우) const defaultLines: ApprovalLineItem[] = defaultApprovers.map((epId, index) => ({ id: `approver-${index}`, epId: epId, userId: "", // EP ID로만 식별 emailAddress: "", name: `결재자 ${index + 1}`, role: "1", // 결재 seq: (index + 1).toString(), opinion: "", })); setApprovalLines([submitter, ...defaultLines]); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); // open 상태만 감지 - 다이얼로그 열림/닫힘 시에만 초기화 // 결재선 변경 핸들러 const handleApprovalLinesChange = (lines: ApprovalLineItem[]) => { setApprovalLines(lines); }; // 제출 핸들러 const handleSubmit = async () => { try { // 검증: 결재선 확인 const approvers = approvalLines .filter((line) => line.role === "1" && line.seq !== "0") .sort((a, b) => parseInt(a.seq) - parseInt(b.seq)); if (approvers.length === 0) { toast.error("최소 1명의 결재자를 선택해주세요."); return; } // 검증: 제목 확인 if (!title.trim()) { toast.error("결재 제목을 입력해주세요."); return; } setIsSubmitting(true); // EP ID 목록 추출 const approverEpIds = approvers .map((line) => line.epId) .filter((epId): epId is string => !!epId); // 상위 컴포넌트로 데이터 전달 await onConfirm({ approvers: approverEpIds, title: title.trim(), }); // 성공 시 다이얼로그 닫기 onOpenChange(false); } catch (error) { console.error("[ApprovalPreviewDialog] 제출 실패:", error); // 에러는 상위 컴포넌트에서 처리 (toast 등) } finally { setIsSubmitting(false); } }; // 취소 핸들러 const handleCancel = () => { onOpenChange(false); }; // 폼 내용 const FormContent = () => (
{/* 결재선 설정 */}

결재자를 검색하여 추가하고, 결재 순서를 설정하세요.

{/* 제목 입력 */}
setTitle(e.target.value)} placeholder="결재 제목을 입력하세요" disabled={!allowTitleEdit || isSubmitting} />
{/* 템플릿 미리보기 */}
{isLoadingTemplate ? (
템플릿을 불러오는 중...
) : (
)}
); // Desktop: Dialog if (isDesktop) { return ( 결재 문서 미리보기 결재 문서를 확인하고 결재선을 설정한 후 상신하세요.
); } // Mobile: Drawer return ( 결재 문서 미리보기 결재 문서를 확인하고 결재선을 설정한 후 상신하세요.
); }