From 02b1cf005cf3e1df64183d20ba42930eb2767a9f Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 21 Aug 2025 06:57:36 +0000 Subject: (대표님, 최겸) 설계메뉴추가, 작업사항 업데이트 설계메뉴 - 문서관리 설계메뉴 - 벤더 데이터 gtc 메뉴 업데이트 정보시스템 - 메뉴리스트 및 정보 업데이트 파일 라우트 업데이트 엑셀임포트 개선 기본계약 개선 벤더 가입과정 변경 및 개선 벤더 기본정보 - pq 돌체 오류 수정 및 개선 벤더 로그인 과정 이메일 오류 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vendor-table/basic-contract-sign-dialog.tsx | 313 +++++++++++++++------ 1 file changed, 229 insertions(+), 84 deletions(-) (limited to 'lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx') diff --git a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx index 7bffdac9..7d828a7e 100644 --- a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx +++ b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx @@ -7,7 +7,6 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { formatDate } from "@/lib/utils"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; -import { BasicContractSignViewer } from "@/lib/basic-contract/viewer/basic-contract-sign-viewer"; import type { WebViewerInstance } from "@pdftron/webviewer"; import type { BasicContractView } from "@/db/schema"; import { @@ -19,45 +18,82 @@ import { FileText, User, AlertCircle, - Calendar + Calendar, + Loader2 } from "lucide-react"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { useRouter } from "next/navigation" +import { BasicContractSignViewer } from "../viewer/basic-contract-sign-viewer"; +import { getVendorAttachments } from "../service"; -// 수정된 props 인터페이스 interface BasicContractSignDialogProps { contracts: BasicContractView[]; onSuccess?: () => void; + hasSelectedRows?: boolean; + t: (key: string) => string; } -export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractSignDialogProps) { +export function BasicContractSignDialog({ + contracts, + onSuccess, + hasSelectedRows = false, + t +}: BasicContractSignDialogProps) { const [open, setOpen] = React.useState(false); const [selectedContract, setSelectedContract] = React.useState(null); const [instance, setInstance] = React.useState(null); const [searchTerm, setSearchTerm] = React.useState(""); const [isSubmitting, setIsSubmitting] = React.useState(false); + + // 추가된 state들 + const [additionalFiles, setAdditionalFiles] = React.useState([]); + const [isLoadingAttachments, setIsLoadingAttachments] = React.useState(false); + const router = useRouter() + console.log(selectedContract,"selectedContract") + console.log(additionalFiles,"additionalFiles") + + // 버튼 비활성화 조건 + const isButtonDisabled = !hasSelectedRows || contracts.length === 0; + + // 비활성화 이유 텍스트 + const getDisabledReason = () => { + if (!hasSelectedRows) { + return t("basicContracts.toolbar.selectRows"); + } + if (contracts.length === 0) { + return t("basicContracts.toolbar.noPendingContracts"); + } + return ""; + }; + // 다이얼로그 열기/닫기 핸들러 const handleOpenChange = (isOpen: boolean) => { setOpen(isOpen); - // 다이얼로그가 열릴 때 첫 번째 계약서 자동 선택 - if (isOpen && contracts.length > 0 && !selectedContract) { - setSelectedContract(contracts[0]); - } - if (!isOpen) { + // 다이얼로그 닫을 때 상태 초기화 setSelectedContract(null); setSearchTerm(""); + setAdditionalFiles([]); // 추가 파일 상태 초기화 + // WebViewer 인스턴스 정리 + if (instance) { + try { + instance.UI.dispose(); + } catch (error) { + console.log("WebViewer dispose error:", error); + } + setInstance(null); + } } }; // 계약서 선택 핸들러 const handleSelectContract = (contract: BasicContractView) => { + console.log("계약서 선택:", contract.id, contract.templateName); setSelectedContract(contract); }; @@ -79,6 +115,40 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS } }, [open, contracts, selectedContract]); + // 추가 파일 가져오기 useEffect + React.useEffect(() => { + const fetchAdditionalFiles = async () => { + if (!selectedContract) { + setAdditionalFiles([]); + return; + } + + // "비밀유지 계약서"인 경우에만 추가 파일 가져오기 + if (selectedContract.templateName === "비밀유지 계약서") { + setIsLoadingAttachments(true); + try { + const result = await getVendorAttachments(selectedContract.vendorId); + if (result.success) { + setAdditionalFiles(result.data); + console.log("추가 파일 로드됨:", result.data); + } else { + console.error("Failed to fetch attachments:", result.error); + setAdditionalFiles([]); + } + } catch (error) { + console.error("Error fetching attachments:", error); + setAdditionalFiles([]); + } finally { + setIsLoadingAttachments(false); + } + } else { + setAdditionalFiles([]); + } + }; + + fetchAdditionalFiles(); + }, [selectedContract]); + // 서명 완료 핸들러 const completeSign = async () => { if (!instance || !selectedContract) return; @@ -89,29 +159,57 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS const doc = documentViewer.getDocument(); const xfdfString = await annotationManager.exportAnnotations(); + // 폼 필드 데이터 수집 + const fieldManager = annotationManager.getFieldManager(); + const fields = fieldManager.getFields(); + const formData: any = {}; + fields.forEach((field: any) => { + formData[field.name] = field.value; + }); + const data = await doc.getFileData({ xfdfString, downloadType: "pdf", }); // FormData 생성 및 파일 추가 - const formData = new FormData(); - formData.append('file', new Blob([data], { type: 'application/pdf' })); - formData.append('tableRowId', selectedContract.id.toString()); - formData.append('templateName', selectedContract.signedFileName || ''); + const submitFormData = new FormData(); + submitFormData.append('file', new Blob([data], { type: 'application/pdf' })); + submitFormData.append('tableRowId', selectedContract.id.toString()); + submitFormData.append('templateName', selectedContract.signedFileName || ''); + + // 폼 필드 데이터 추가 + if (Object.keys(formData).length > 0) { + submitFormData.append('formData', JSON.stringify(formData)); + } + + // 준법 템플릿인 경우 필수 필드 검증 + if (selectedContract.templateName?.includes('준법')) { + const requiredFields = ['compliance_agreement', 'legal_review', 'risk_assessment']; + const missingFields = requiredFields.filter(field => !formData[field]); + + if (missingFields.length > 0) { + toast.error("필수 준법 항목이 누락되었습니다.", { + description: `다음 항목을 완료해주세요: ${missingFields.join(', ')}`, + icon: + }); + setIsSubmitting(false); + return; + } + } // API 호출 const response = await fetch('/api/upload/signed-contract', { method: 'POST', - body: formData, + body: submitFormData, next: { tags: ["basicContractView-vendor"] }, }); const result = await response.json(); if (result.result) { - toast.success("서명이 성공적으로 완료되었습니다.", { - description: "문서가 성공적으로 처리되었습니다.", + toast.success(t("basicContracts.messages.signSuccess"), { + description: t("basicContracts.messages.documentProcessed"), icon: }); router.refresh(); @@ -120,22 +218,19 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS onSuccess(); } } else { - toast.error("서명 처리 중 오류가 발생했습니다.", { + toast.error(t("basicContracts.messages.signError"), { description: result.error, icon: }); } } catch (error) { console.error("서명 완료 중 오류:", error); - toast.error("서명 처리 중 오류가 발생했습니다."); + toast.error(t("basicContracts.messages.signError")); } finally { setIsSubmitting(false); } }; - // 서명 대기중(PENDING) 계약서가 있는지 확인 - const hasPendingContracts = contracts.length > 0; - return ( <> {/* 서명 버튼 */} @@ -143,62 +238,67 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS variant="outline" size="sm" onClick={() => setOpen(true)} - disabled={!hasPendingContracts} - className="gap-2 transition-all hover:bg-blue-50 hover:text-blue-600 hover:border-blue-200" + disabled={isButtonDisabled} + className="gap-2 transition-all hover:bg-blue-50 hover:text-blue-600 hover:border-blue-200 disabled:opacity-50 disabled:cursor-not-allowed" > -