"use client"; import * as React from "react"; import { Loader2, Eye, 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 { Textarea } from "@/components/ui/textarea"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useMediaQuery } from "@/hooks/use-media-query"; import { ApprovalLineSelector } from "@/components/knox/approval/ApprovalLineSelector"; import { getApprovalTemplateByName, replaceTemplateVariables } from "./template-utils"; import type { ApprovalLine } from "@/components/knox/approval/types"; /** * 결재 미리보기 다이얼로그 Props */ export interface ApprovalPreviewDialogProps { /** 다이얼로그 열림 상태 */ open: boolean; /** 다이얼로그 열림 상태 변경 핸들러 */ onOpenChange: (open: boolean) => void; /** 템플릿 이름 (DB에서 조회) */ templateName: string; /** 템플릿 변수 ({{변수명}} 형태로 치환) */ variables: Record; /** 결재 제목 */ title: string; /** 결재 설명 (선택사항) */ description?: string; /** 현재 사용자 정보 */ currentUser: { id: number; epId: string; name?: string; email?: string; deptName?: string; }; /** 초기 결재선 (선택사항) */ defaultApprovers?: string[]; /** 확인 버튼 클릭 시 콜백 */ onConfirm: (data: { approvers: string[]; title: string; description?: string; }) => Promise; /** 제목 수정 가능 여부 (기본: true) */ allowTitleEdit?: boolean; /** 설명 수정 가능 여부 (기본: true) */ allowDescriptionEdit?: boolean; } /** * 결재 미리보기 다이얼로그 컴포넌트 * * **주요 기능:** * 1. 템플릿 실시간 미리보기 (변수 치환) * 2. 결재선 선택 (ApprovalLineSelector 활용) * 3. 제목/설명 수정 * 4. 반응형 UI (Desktop: Dialog, Mobile: Drawer) * * **사용 예시:** * ```tsx * { * await submitApproval(approvers); * }} * /> * ``` */ export function ApprovalPreviewDialog({ open, onOpenChange, templateName, variables, title: initialTitle, description: initialDescription, currentUser, defaultApprovers = [], onConfirm, allowTitleEdit = true, allowDescriptionEdit = 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 [description, setDescription] = React.useState(initialDescription || ""); 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(); }, [open, templateName, variables]); // 초기 결재선 설정 React.useEffect(() => { if (!open) return; // 상신자 추가 const submitter: ApprovalLine = { 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: ApprovalLine[] = 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]); }, [open, currentUser, defaultApprovers]); // 결재선 변경 핸들러 const handleApprovalLinesChange = (lines: ApprovalLine[]) => { 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(), description: description.trim() || undefined, }); // 성공 시 다이얼로그 닫기 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} />
{/* 설명 입력 */}