From 18954df6565108a469fb1608ea3715dd9bb1b02d Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 1 Sep 2025 09:12:09 +0000 Subject: (대표님) 구매 기본계약, gtc 개발 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vendor-table/basic-contract-sign-dialog.tsx | 237 +++++++++++---------- .../update-vendor-document-status-button.tsx | 145 +++++++++++++ 2 files changed, 272 insertions(+), 110 deletions(-) create mode 100644 lib/basic-contract/vendor-table/update-vendor-document-status-button.tsx (limited to 'lib/basic-contract/vendor-table') 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 f70bed94..fa68c9c8 100644 --- a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx +++ b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx @@ -51,9 +51,9 @@ interface BasicContractSignDialogProps { onOpenChange?: (open: boolean) => void; } -export function BasicContractSignDialog({ - contracts, - onSuccess, +export function BasicContractSignDialog({ + contracts, + onSuccess, hasSelectedRows = false, mode = 'vendor', onBuyerSignComplete, @@ -68,19 +68,26 @@ export function BasicContractSignDialog({ 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 [contractStatuses, setContractStatuses] = React.useState([]); - + // 서명/설문/GTC 코멘트 완료 상태 관리 const [surveyCompletionStatus, setSurveyCompletionStatus] = React.useState>({}); const [signatureStatus, setSignatureStatus] = React.useState>({}); - const [gtcCommentStatus, setGtcCommentStatus] = React.useState>({}); - + const [gtcCommentStatus, setGtcCommentStatus] = React.useState>({}); + + console.log(gtcCommentStatus, "gtcCommentStatus") + const router = useRouter() // 실제 사용할 open 상태 (외부 제어가 있으면 외부 상태 사용, 없으면 내부 상태 사용) @@ -91,7 +98,7 @@ export function BasicContractSignDialog({ const isBuyerMode = mode === 'buyer'; const dialogTitle = isBuyerMode ? "구매자 최종승인 서명" : t("basicContracts.dialog.title"); const signButtonText = isBuyerMode ? "최종승인 완료" : "서명 완료 및 저장"; - + // 버튼 비활성화 조건 const isButtonDisabled = !hasSelectedRows || contracts.length === 0; @@ -107,27 +114,31 @@ export function BasicContractSignDialog({ }; // 현재 선택된 계약서의 서명 완료 가능 여부 확인 - const canCompleteCurrentContract = React.useMemo(() => { - if (!selectedContract) return false; - - const contractId = selectedContract.id; - - // 구매자 모드에서는 설문조사나 GTC 체크 불필요 - if (isBuyerMode) { - const signatureCompleted = signatureStatus[contractId] === true; - return signatureCompleted; - } - - // 협력업체 모드의 기존 로직 - const isComplianceTemplate = selectedContract.templateName?.includes('준법'); - const isGTCTemplate = selectedContract.templateName?.includes('GTC'); - - const surveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contractId] === true : true; - const gtcCompleted = isGTCTemplate ? (gtcCommentStatus[contractId]?.hasComments !== true) : true; +const canCompleteCurrentContract = React.useMemo(() => { + if (!selectedContract) return false; + + const contractId = selectedContract.id; + + if (isBuyerMode) { const signatureCompleted = signatureStatus[contractId] === true; - - return surveyCompleted && gtcCompleted && signatureCompleted; - }, [selectedContract, surveyCompletionStatus, signatureStatus, gtcCommentStatus, isBuyerMode]); + return signatureCompleted; + } + + const isComplianceTemplate = selectedContract.templateName?.includes('준법'); + const isGTCTemplate = selectedContract.templateName?.includes('GTC'); + + const surveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contractId] === true : true; + + // GTC 체크 수정 + const gtcStatus = gtcCommentStatus[contractId]; + const gtcCompleted = isGTCTemplate ? + (!gtcStatus?.hasComments || gtcStatus?.isComplete === true) : true; + + const signatureCompleted = signatureStatus[contractId] === true; + + return surveyCompleted && gtcCompleted && signatureCompleted; +}, [selectedContract, surveyCompletionStatus, signatureStatus, gtcCommentStatus, isBuyerMode]); + // 계약서별 상태 초기화 React.useEffect(() => { @@ -147,7 +158,7 @@ export function BasicContractSignDialog({ const allCompleted = completedCount === totalCount && totalCount > 0; // 현재 선택된 계약서의 상태 - const currentContractStatus = selectedContract + const currentContractStatus = selectedContract ? contractStatuses.find(status => status.id === selectedContract.id) : null; @@ -155,7 +166,7 @@ export function BasicContractSignDialog({ const getNextPendingContract = () => { const pendingStatuses = contractStatuses.filter(status => status.status === 'pending'); if (pendingStatuses.length === 0) return null; - + const nextPendingId = pendingStatuses[0].id; return contracts.find(contract => contract.id === nextPendingId) || null; }; @@ -169,14 +180,14 @@ export function BasicContractSignDialog({ ); if (!confirmClose) return; } - + // 외부 제어가 있으면 외부 콜백 호출, 없으면 내부 상태 업데이트 if (isControlledExternally && externalOnOpenChange) { externalOnOpenChange(isOpen); } else { setInternalOpen(isOpen); } - + if (!isOpen) { // 다이얼로그 닫을 때 상태 초기화 setSelectedContract(null); @@ -226,7 +237,7 @@ export function BasicContractSignDialog({ } } }, [open, contracts, selectedContract, contractStatuses]); - + // 추가 파일 가져오기 useEffect (구매자 모드에서는 스킵) React.useEffect(() => { if (isBuyerMode) { @@ -285,11 +296,17 @@ export function BasicContractSignDialog({ }, []); // GTC 코멘트 상태 변경 콜백 함수 - const handleGtcCommentStatusChange = React.useCallback((contractId: number, hasComments: boolean, commentCount: number) => { - console.log(`📋 GTC 코멘트 상태 변경: 계약서 ${contractId}, 코멘트 ${commentCount}개`); + const handleGtcCommentStatusChange = React.useCallback(( + contractId: number, + hasComments: boolean, + commentCount: number, + reviewStatus?: string, + isComplete?: boolean + ) => { + console.log(`📋 GTC 상태 변경: 계약서 ${contractId}, 코멘트 ${commentCount}개, 상태: ${reviewStatus}, 완료: ${isComplete}`); setGtcCommentStatus(prev => ({ ...prev, - [contractId]: { hasComments, commentCount } + [contractId]: { hasComments, commentCount, reviewStatus, isComplete } })); }, []); @@ -300,10 +317,10 @@ export function BasicContractSignDialog({ // 서명 완료 가능 여부 재확인 if (!canCompleteCurrentContract) { const contractId = selectedContract.id; - + if (isBuyerMode) { const signatureCompleted = signatureStatus[contractId] === true; - + if (!signatureCompleted) { toast.error("계약서에 서명을 먼저 완료해주세요.", { description: "문서의 서명 필드에 서명해주세요.", @@ -316,9 +333,9 @@ export function BasicContractSignDialog({ const isComplianceTemplate = selectedContract.templateName?.includes('준법'); const isGTCTemplate = selectedContract.templateName?.includes('GTC'); const surveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contractId] === true : true; - const gtcCompleted = isGTCTemplate ? (gtcCommentStatus[contractId]?.hasComments !== true) : true; + const gtcCompleted = isGTCTemplate ? (gtcCommentStatus[contractId]?.isComplete !== true) : true; const signatureCompleted = signatureStatus[contractId] === true; - + if (!surveyCompleted) { toast.error("준법 설문조사를 먼저 완료해주세요.", { description: "설문조사 탭에서 모든 필수 항목을 완료해주세요.", @@ -326,7 +343,7 @@ export function BasicContractSignDialog({ }); return; } - + if (!gtcCompleted) { toast.error("GTC 조항에 코멘트가 있어 서명할 수 없습니다.", { description: "조항 검토 탭에서 모든 코멘트를 삭제하거나 협의를 완료해주세요.", @@ -334,7 +351,7 @@ export function BasicContractSignDialog({ }); return; } - + if (!signatureCompleted) { toast.error("계약서에 서명을 먼저 완료해주세요.", { description: "문서의 서명 필드에 서명해주세요.", @@ -343,7 +360,7 @@ export function BasicContractSignDialog({ return; } } - + return; } @@ -376,9 +393,9 @@ export function BasicContractSignDialog({ if (result.success) { // 성공시 해당 계약서 상태를 완료로 업데이트 - setContractStatuses(prev => - prev.map(status => - status.id === selectedContract.id + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id ? { ...status, status: 'completed' as const } : status ) @@ -413,9 +430,9 @@ export function BasicContractSignDialog({ router.refresh(); } else { // 실패시 에러 상태 업데이트 - setContractStatuses(prev => - prev.map(status => - status.id === selectedContract.id + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id ? { ...status, status: 'error' as const, errorMessage: result.message } : status ) @@ -432,26 +449,26 @@ export function BasicContractSignDialog({ 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)); } - + // API 호출 const response = await fetch('/api/upload/signed-contract', { method: 'POST', body: submitFormData, next: { tags: ["basicContractView-vendor"] }, }); - + const result = await response.json(); if (result.result) { // 성공시 해당 계약서 상태를 완료로 업데이트 - setContractStatuses(prev => - prev.map(status => - status.id === selectedContract.id + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id ? { ...status, status: 'completed' as const } : status ) @@ -481,9 +498,9 @@ export function BasicContractSignDialog({ router.refresh(); } else { // 실패시 에러 상태 업데이트 - setContractStatuses(prev => - prev.map(status => - status.id === selectedContract.id + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id ? { ...status, status: 'error' as const, errorMessage: result.error } : status ) @@ -497,11 +514,11 @@ export function BasicContractSignDialog({ } } catch (error) { console.error("서명 완료 중 오류:", error); - + // 에러 상태 업데이트 - setContractStatuses(prev => - prev.map(status => - status.id === selectedContract.id + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id ? { ...status, status: 'error' as const, errorMessage: '서명 처리 중 오류가 발생했습니다' } : status ) @@ -519,10 +536,10 @@ export function BasicContractSignDialog({ if (onSuccess) { onSuccess(); } - const successMessage = isBuyerMode - ? "모든 계약서 최종승인이 완료되었습니다!" + const successMessage = isBuyerMode + ? "모든 계약서 최종승인이 완료되었습니다!" : "모든 계약서 서명이 완료되었습니다!"; - + toast.success(successMessage, { description: "계약서 관리 페이지가 새고침됩니다.", icon: @@ -540,33 +557,33 @@ export function BasicContractSignDialog({ disabled={isButtonDisabled} className={cn( "gap-2 transition-all disabled:opacity-50 disabled:cursor-not-allowed", - isBuyerMode - ? "hover:bg-green-50 hover:text-green-600 hover:border-green-200" + isBuyerMode + ? "hover:bg-green-50 hover:text-green-600 hover:border-green-200" : "hover:bg-blue-50 hover:text-blue-600 hover:border-blue-200" )} > {isBuyerMode ? ( -