From 78c471eec35182959e0029ded18f144974ccaca2 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Thu, 23 Oct 2025 18:13:41 +0900 Subject: (김준회) 결재 템플릿 에디터 및 결재 워크플로 공통함수 작성, 실사의뢰 결재 연결 예시 작성 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/approval/ApprovalPreviewDialog.tsx | 211 ++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 components/approval/ApprovalPreviewDialog.tsx (limited to 'components/approval/ApprovalPreviewDialog.tsx') diff --git a/components/approval/ApprovalPreviewDialog.tsx b/components/approval/ApprovalPreviewDialog.tsx new file mode 100644 index 00000000..7739bf54 --- /dev/null +++ b/components/approval/ApprovalPreviewDialog.tsx @@ -0,0 +1,211 @@ +"use client"; + +import * as React from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Loader2, FileText, AlertCircle } from "lucide-react"; +import { toast } from "sonner"; +import { Separator } from "@/components/ui/separator"; + +import ApprovalLineSelector, { + type ApprovalLineItem, +} from "@/components/knox/approval/ApprovalLineSelector"; +import { getApprovalTemplateByName, replaceTemplateVariables } from "@/lib/approval/template-utils"; +import { debugLog, debugError, debugSuccess } from "@/lib/debug-utils"; + +interface ApprovalPreviewDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + templateName: string; + variables: Record; + title: string; + description?: string; + currentUser: { id: number; epId: string | null; name?: string | null; email?: string }; + onSubmit: (approvers: ApprovalLineItem[]) => Promise; +} + +export function ApprovalPreviewDialog({ + open, + onOpenChange, + templateName, + variables, + title, + description, + currentUser, + onSubmit, +}: ApprovalPreviewDialogProps) { + const [approvalLines, setApprovalLines] = React.useState([]); + const [isSubmitting, setIsSubmitting] = React.useState(false); + const [templateContent, setTemplateContent] = React.useState(""); + const [isLoadingTemplate, setIsLoadingTemplate] = React.useState(false); + const [templateError, setTemplateError] = React.useState(null); + + // 상신자 초기화 + React.useEffect(() => { + if (open && currentUser.epId) { + const drafterLine: ApprovalLineItem = { + id: `drafter-${Date.now()}`, + epId: currentUser.epId, + userId: currentUser.id.toString(), + emailAddress: currentUser.email, + name: currentUser.name || undefined, + role: "0", // 기안 + seq: "0", + opinion: "", + }; + setApprovalLines([drafterLine]); + } + }, [open, currentUser]); + + // 템플릿 로드 및 변수 치환 + React.useEffect(() => { + if (!open) return; + + const loadTemplate = async () => { + setIsLoadingTemplate(true); + setTemplateError(null); + + try { + const template = await getApprovalTemplateByName(templateName); + + if (!template) { + setTemplateError(`템플릿 "${templateName}"을 찾을 수 없습니다.`); + setTemplateContent(`

${description || "결재 요청"}

`); + } else { + // 변수 치환 + const replaced = await replaceTemplateVariables(template.content, variables); + setTemplateContent(replaced); + } + } catch (error) { + console.error("Template load error:", error); + setTemplateError("템플릿을 불러오는 중 오류가 발생했습니다."); + setTemplateContent(`

${description || "결재 요청"}

`); + } finally { + setIsLoadingTemplate(false); + } + }; + + loadTemplate(); + }, [open, templateName, variables, description]); + + const handleSubmit = async () => { + debugLog('[ApprovalPreviewDialog] 결재 제출 시작', { + templateName, + approvalLineCount: approvalLines.length, + }); + + // 결재자가 있는지 확인 (상신자 제외) + const approvers = approvalLines.filter((line) => line.seq !== "0"); + if (approvers.length === 0) { + debugError('[ApprovalPreviewDialog] 결재자가 없음'); + toast.error("결재자를 최소 1명 이상 추가해주세요."); + return; + } + + setIsSubmitting(true); + try { + debugLog('[ApprovalPreviewDialog] onSubmit 호출', { + approversCount: approvers.length, + }); + + await onSubmit(approvalLines); + + debugSuccess('[ApprovalPreviewDialog] 결재 요청 성공'); + toast.success("결재가 성공적으로 요청되었습니다."); + onOpenChange(false); + } catch (error) { + debugError('[ApprovalPreviewDialog] 결재 요청 실패', error); + const errorMessage = error instanceof Error ? error.message : "결재 요청에 실패했습니다."; + toast.error(errorMessage); + // 에러 발생 시 dialog를 닫지 않음 + } finally { + setIsSubmitting(false); + } + }; + + return ( + + + + + + {title} + + {description && {description}} + + +
+ {/* 템플릿 미리보기 */} + + + 결재 내용 미리보기 + + 템플릿: {templateName} + + + + {isLoadingTemplate ? ( +
+ + 템플릿을 불러오는 중... +
+ ) : templateError ? ( +
+
+ + 경고 +
+

{templateError}

+

+ 기본 내용으로 대체되었습니다. 결재는 정상적으로 진행됩니다. +

+
+ ) : null} + +
+ + + + + + {/* 결재선 선택 */} +
+ +
+
+ + + + + + +
+ ); +} + -- cgit v1.2.3