diff options
Diffstat (limited to 'lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx')
| -rw-r--r-- | lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx | 561 |
1 files changed, 450 insertions, 111 deletions
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 7d828a7e..319ae4b9 100644 --- a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx +++ b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx @@ -19,7 +19,10 @@ import { User, AlertCircle, Calendar, - Loader2 + Loader2, + ArrowRight, + Trophy, + Target } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; @@ -28,6 +31,13 @@ import { useRouter } from "next/navigation" import { BasicContractSignViewer } from "../viewer/basic-contract-sign-viewer"; import { getVendorAttachments } from "../service"; +// 계약서 상태 타입 정의 +interface ContractStatus { + id: number; + status: 'pending' | 'completed' | 'error'; + errorMessage?: string; +} + interface BasicContractSignDialogProps { contracts: BasicContractView[]; onSuccess?: () => void; @@ -51,6 +61,13 @@ export function BasicContractSignDialog({ const [additionalFiles, setAdditionalFiles] = React.useState<any[]>([]); const [isLoadingAttachments, setIsLoadingAttachments] = React.useState(false); + // 계약서 상태 관리 + const [contractStatuses, setContractStatuses] = React.useState<ContractStatus[]>([]); + + // 🔥 새로 추가: 서명/설문 완료 상태 관리 + const [surveyCompletionStatus, setSurveyCompletionStatus] = React.useState<Record<number, boolean>>({}); + const [signatureStatus, setSignatureStatus] = React.useState<Record<number, boolean>>({}); + const router = useRouter() console.log(selectedContract,"selectedContract") @@ -70,15 +87,81 @@ export function BasicContractSignDialog({ return ""; }; + // 🔥 현재 선택된 계약서의 서명 완료 가능 여부 확인 + const canCompleteCurrentContract = React.useMemo(() => { + if (!selectedContract) return false; + + const contractId = selectedContract.id; + const isComplianceTemplate = selectedContract.templateName?.includes('준법'); + + // 1. 준법 템플릿인 경우 설문조사 완료 여부 확인 + const surveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contractId] === true : true; + + // 2. 서명 완료 여부 확인 + const signatureCompleted = signatureStatus[contractId] === true; + + console.log('🔍 서명 완료 가능 여부 체크:', { + contractId, + isComplianceTemplate, + surveyCompleted, + signatureCompleted, + canComplete: surveyCompleted && signatureCompleted + }); + + return surveyCompleted && signatureCompleted; + }, [selectedContract, surveyCompletionStatus, signatureStatus]); + + // 계약서별 상태 초기화 + React.useEffect(() => { + if (contracts.length > 0 && contractStatuses.length === 0) { + setContractStatuses( + contracts.map(contract => ({ + id: contract.id, + status: 'pending' as const + })) + ); + } + }, [contracts, contractStatuses.length]); + + // 완료된 계약서 수 계산 + const completedCount = contractStatuses.filter(status => status.status === 'completed').length; + const totalCount = contracts.length; + const allCompleted = completedCount === totalCount && totalCount > 0; + + // 현재 선택된 계약서의 상태 + const currentContractStatus = selectedContract + ? contractStatuses.find(status => status.id === selectedContract.id) + : null; + + // 다음 미완료 계약서 찾기 + 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; + }; + // 다이얼로그 열기/닫기 핸들러 const handleOpenChange = (isOpen: boolean) => { + if (!isOpen && !allCompleted && completedCount > 0) { + // 완료되지 않은 계약서가 있으면 확인 대화상자 + const confirmClose = window.confirm( + `${completedCount}/${totalCount}개 계약서가 완료되었습니다. 정말 나가시겠습니까?` + ); + if (!confirmClose) return; + } + setOpen(isOpen); if (!isOpen) { // 다이얼로그 닫을 때 상태 초기화 setSelectedContract(null); setSearchTerm(""); - setAdditionalFiles([]); // 추가 파일 상태 초기화 + setAdditionalFiles([]); + setContractStatuses([]); + setSurveyCompletionStatus({}); // 🔥 추가 + setSignatureStatus({}); // 🔥 추가 // WebViewer 인스턴스 정리 if (instance) { try { @@ -108,12 +191,17 @@ export function BasicContractSignDialog({ ); }, [contracts, searchTerm]); - // 다이얼로그가 열릴 때 첫 번째 계약서 자동 선택 + // 다이얼로그가 열릴 때 첫 번째 미완료 계약서 자동 선택 React.useEffect(() => { if (open && contracts.length > 0 && !selectedContract) { - setSelectedContract(contracts[0]); + const firstPending = getNextPendingContract(); + if (firstPending) { + setSelectedContract(firstPending); + } else { + setSelectedContract(contracts[0]); + } } - }, [open, contracts, selectedContract]); + }, [open, contracts, selectedContract, contractStatuses]); // 추가 파일 가져오기 useEffect React.useEffect(() => { @@ -149,10 +237,54 @@ export function BasicContractSignDialog({ fetchAdditionalFiles(); }, [selectedContract]); - // 서명 완료 핸들러 + // 🔥 설문조사 완료 콜백 함수 + const handleSurveyComplete = React.useCallback((contractId: number) => { + console.log(`📋 설문조사 완료: 계약서 ${contractId}`); + setSurveyCompletionStatus(prev => ({ + ...prev, + [contractId]: true + })); + }, []); + + // 🔥 서명 완료 콜백 함수 + const handleSignatureComplete = React.useCallback((contractId: number) => { + console.log(`✍️ 서명 완료: 계약서 ${contractId}`); + setSignatureStatus(prev => ({ + ...prev, + [contractId]: true + })); + }, []); + + // 서명 완료 핸들러 (수정됨) const completeSign = async () => { if (!instance || !selectedContract) return; + // 🔥 서명 완료 가능 여부 재확인 + if (!canCompleteCurrentContract) { + const contractId = selectedContract.id; + const isComplianceTemplate = selectedContract.templateName?.includes('준법'); + const surveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contractId] === true : true; + const signatureCompleted = signatureStatus[contractId] === true; + + if (!surveyCompleted) { + toast.error("준법 설문조사를 먼저 완료해주세요.", { + description: "설문조사 탭에서 모든 필수 항목을 완료해주세요.", + icon: <AlertCircle className="h-5 w-5 text-red-500" /> + }); + return; + } + + if (!signatureCompleted) { + toast.error("계약서에 서명을 먼저 완료해주세요.", { + description: "문서의 서명 필드에 서명해주세요.", + icon: <Target className="h-5 w-5 text-blue-500" /> + }); + return; + } + + return; + } + setIsSubmitting(true); try { const { documentViewer, annotationManager } = instance.Core; @@ -183,21 +315,6 @@ export function BasicContractSignDialog({ 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: <AlertCircle className="h-5 w-5 text-red-500" /> - }); - setIsSubmitting(false); - return; - } - } - // API 호출 const response = await fetch('/api/upload/signed-contract', { method: 'POST', @@ -208,29 +325,82 @@ export function BasicContractSignDialog({ const result = await response.json(); if (result.result) { - toast.success(t("basicContracts.messages.signSuccess"), { - description: t("basicContracts.messages.documentProcessed"), + // 성공시 해당 계약서 상태를 완료로 업데이트 + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id + ? { ...status, status: 'completed' as const } + : status + ) + ); + + toast.success("계약서 서명이 완료되었습니다!", { + description: `${selectedContract.templateName} - ${completedCount + 1}/${totalCount}개 완료`, icon: <CheckCircle2 className="h-5 w-5 text-green-500" /> }); - router.refresh(); - setOpen(false); - if (onSuccess) { - onSuccess(); + + // 다음 미완료 계약서로 자동 이동 + const nextContract = getNextPendingContract(); + if (nextContract) { + setSelectedContract(nextContract); + toast.info(`다음 계약서로 이동합니다`, { + description: nextContract.templateName, + icon: <ArrowRight className="h-4 w-4 text-blue-500" /> + }); + } else { + // 모든 계약서 완료시 + toast.success("🎉 모든 계약서 서명이 완료되었습니다!", { + description: `총 ${totalCount}개 계약서 서명 완료`, + icon: <Trophy className="h-5 w-5 text-yellow-500" /> + }); } + + router.refresh(); } else { - toast.error(t("basicContracts.messages.signError"), { + // 실패시 에러 상태 업데이트 + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id + ? { ...status, status: 'error' as const, errorMessage: result.error } + : status + ) + ); + + toast.error("서명 처리 중 오류가 발생했습니다", { description: result.error, icon: <AlertCircle className="h-5 w-5 text-red-500" /> }); } } catch (error) { console.error("서명 완료 중 오류:", error); - toast.error(t("basicContracts.messages.signError")); + + // 에러 상태 업데이트 + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id + ? { ...status, status: 'error' as const, errorMessage: '서명 처리 중 오류가 발생했습니다' } + : status + ) + ); + + toast.error("서명 처리 중 오류가 발생했습니다"); } finally { setIsSubmitting(false); } }; + // 모든 서명 완료 핸들러 + const completeAllSigns = () => { + setOpen(false); + if (onSuccess) { + onSuccess(); + } + toast.success("모든 계약서 서명이 완료되었습니다!", { + description: "계약서 관리 페이지가 새로고침됩니다.", + icon: <Trophy className="h-5 w-5 text-yellow-500" /> + }); + }; + return ( <> {/* 서명 버튼 */} @@ -260,19 +430,48 @@ export function BasicContractSignDialog({ </span> </Button> - {/* 서명 다이얼로그 - 레이아웃 개선 */} + {/* 서명 다이얼로그 */} <Dialog open={open} onOpenChange={handleOpenChange}> <DialogContent className="max-w-7xl w-[95vw] h-[90vh] p-0 flex flex-col overflow-hidden" style={{width:'95vw', maxWidth:'95vw'}}> - {/* 고정 헤더 */} + {/* 고정 헤더 - 진행 상황 표시 */} <DialogHeader className="px-6 py-4 bg-gradient-to-r from-blue-50 to-purple-50 border-b flex-shrink-0"> - <DialogTitle className="text-xl font-bold flex items-center text-gray-800"> - <FileSignature className="mr-2 h-5 w-5 text-blue-500" /> - {t("basicContracts.dialog.title")} - {/* 추가 파일 로딩 표시 */} - {isLoadingAttachments && ( - <Loader2 className="ml-2 h-4 w-4 animate-spin text-blue-500" /> + <DialogTitle className="text-xl font-bold flex items-center justify-between text-gray-800"> + <div className="flex items-center"> + <FileSignature className="mr-2 h-5 w-5 text-blue-500" /> + {t("basicContracts.dialog.title")} + {/* 진행 상황 표시 */} + <Badge variant="outline" className="ml-3 bg-blue-50 text-blue-700 border-blue-200"> + {completedCount}/{totalCount} 완료 + </Badge> + {/* 추가 파일 로딩 표시 */} + {isLoadingAttachments && ( + <Loader2 className="ml-2 h-4 w-4 animate-spin text-blue-500" /> + )} + </div> + + {allCompleted && ( + <Badge variant="default" className="bg-green-100 text-green-700 border-green-200"> + <Trophy className="h-4 w-4 mr-1" /> + 전체 완료! + </Badge> )} </DialogTitle> + + {/* 진행률 바 */} + {totalCount > 1 && ( + <div className="mt-3"> + <div className="flex justify-between text-xs text-gray-600 mb-1"> + <span>전체 진행률</span> + <span>{Math.round((completedCount / totalCount) * 100)}%</span> + </div> + <div className="w-full bg-gray-200 rounded-full h-2"> + <div + className="bg-gradient-to-r from-blue-500 to-green-500 h-2 rounded-full transition-all duration-500" + style={{ width: `${(completedCount / totalCount) * 100}%` }} + /> + </div> + </div> + )} </DialogHeader> {/* 메인 컨텐츠 영역 - Flexbox 사용 */} @@ -302,49 +501,101 @@ export function BasicContractSignDialog({ </div> ) : ( <div className="space-y-2"> - {filteredContracts.map((contract) => ( - <Button - key={contract.id} - variant="outline" - className={cn( - "w-full justify-start text-left h-auto p-2 bg-white hover:bg-blue-50 transition-colors", - "border border-gray-200 hover:border-blue-200 rounded-md", - selectedContract?.id === contract.id && "border-blue-500 bg-blue-50 shadow-sm" - )} - onClick={() => handleSelectContract(contract)} - > - <div className="flex flex-col w-full space-y-1"> - {/* 첫 번째 줄: 제목 + 상태 */} - <div className="flex items-center justify-between w-full"> - <span className="font-medium text-xs truncate text-gray-800 flex items-center min-w-0"> - <FileText className="h-3 w-3 mr-1 text-blue-500 flex-shrink-0" /> - <span className="truncate">{contract.templateName || t("basicContracts.dialog.document")}</span> - {/* 비밀유지 계약서인 경우 표시 */} - {contract.templateName === "비밀유지 계약서" && ( - <Badge variant="outline" className="ml-1 bg-green-50 text-green-700 border-green-200 text-xs"> - NDA + {filteredContracts.map((contract) => { + const contractStatus = contractStatuses.find(status => status.id === contract.id); + const isCompleted = contractStatus?.status === 'completed'; + const hasError = contractStatus?.status === 'error'; + + // 🔥 계약서별 완료 상태 확인 + const isComplianceTemplate = contract.templateName?.includes('준법'); + const hasSurveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contract.id] === true : true; + const hasSignatureCompleted = signatureStatus[contract.id] === true; + + return ( + <Button + key={contract.id} + variant="outline" + className={cn( + "w-full justify-start text-left h-auto p-2 bg-white transition-colors", + "border border-gray-200 rounded-md", + selectedContract?.id === contract.id && !isCompleted && "border-blue-500 bg-blue-50 shadow-sm", + isCompleted && "border-green-200 bg-green-50", + hasError && "border-red-200 bg-red-50", + !isCompleted && !hasError && "hover:bg-blue-50 hover:border-blue-200" + )} + onClick={() => handleSelectContract(contract)} + disabled={isCompleted} + > + <div className="flex flex-col w-full space-y-1"> + {/* 첫 번째 줄: 제목 + 상태 */} + <div className="flex items-center justify-between w-full"> + <span className="font-medium text-xs truncate text-gray-800 flex items-center min-w-0"> + <FileText className="h-3 w-3 mr-1 text-blue-500 flex-shrink-0" /> + <span className="truncate">{contract.templateName || t("basicContracts.dialog.document")}</span> + {/* 비밀유지 계약서인 경우 표시 */} + {contract.templateName === "비밀유지 계약서" && ( + <Badge variant="outline" className="ml-1 bg-green-50 text-green-700 border-green-200 text-xs"> + NDA + </Badge> + )} + </span> + + {/* 상태 표시 */} + {isCompleted ? ( + <Badge variant="outline" className="bg-green-50 text-green-700 border-green-200 text-xs ml-2 flex-shrink-0"> + <CheckCircle2 className="h-3 w-3 mr-1" /> + 완료 + </Badge> + ) : hasError ? ( + <Badge variant="outline" className="bg-red-50 text-red-700 border-red-200 text-xs ml-2 flex-shrink-0"> + <AlertCircle className="h-3 w-3 mr-1" /> + 오류 + </Badge> + ) : ( + <Badge variant="outline" className="bg-yellow-50 text-yellow-700 border-yellow-200 text-xs ml-2 flex-shrink-0"> + 대기 </Badge> )} - </span> - <Badge variant="outline" className="bg-yellow-50 text-yellow-700 border-yellow-200 text-xs ml-2 flex-shrink-0"> - {t("basicContracts.statusValues.PENDING")} - </Badge> - </div> - - {/* 두 번째 줄: 사용자 + 날짜 */} - <div className="flex items-center justify-between text-xs text-gray-500"> - <div className="flex items-center min-w-0"> - <User className="h-3 w-3 mr-1 flex-shrink-0" /> - <span className="truncate">{contract.requestedByName || t("basicContracts.dialog.unknown")}</span> </div> - <div className="flex items-center ml-2 flex-shrink-0"> - <Calendar className="h-3 w-3 mr-1 flex-shrink-0" /> - <span>{formatDate(contract.createdAt)}</span> + + {/* 🔥 완료 상태 표시 */} + {!isCompleted && !hasError && ( + <div className="flex items-center space-x-2 text-xs"> + {isComplianceTemplate && ( + <span className={`flex items-center ${hasSurveyCompleted ? 'text-green-600' : 'text-gray-400'}`}> + <CheckCircle2 className={`h-3 w-3 mr-1 ${hasSurveyCompleted ? 'text-green-500' : 'text-gray-300'}`} /> + 설문 + </span> + )} + <span className={`flex items-center ${hasSignatureCompleted ? 'text-green-600' : 'text-gray-400'}`}> + <Target className={`h-3 w-3 mr-1 ${hasSignatureCompleted ? 'text-green-500' : 'text-gray-300'}`} /> + 서명 + </span> + </div> + )} + + {/* 두 번째 줄: 사용자 + 날짜 */} + <div className="flex items-center justify-between text-xs text-gray-500"> + <div className="flex items-center min-w-0"> + <User className="h-3 w-3 mr-1 flex-shrink-0" /> + <span className="truncate">{contract.requestedByName || t("basicContracts.dialog.unknown")}</span> + </div> + <div className="flex items-center ml-2 flex-shrink-0"> + <Calendar className="h-3 w-3 mr-1 flex-shrink-0" /> + <span>{formatDate(contract.createdAt)}</span> + </div> </div> + + {/* 에러 메시지 표시 */} + {hasError && contractStatus?.errorMessage && ( + <div className="text-xs text-red-600 mt-1"> + {contractStatus.errorMessage} + </div> + )} </div> - </div> - </Button> - ))} + </Button> + ); + })} </div> )} </div> @@ -360,12 +611,31 @@ export function BasicContractSignDialog({ <h3 className="font-semibold text-gray-800 flex items-center"> <FileText className="h-4 w-4 mr-2 text-blue-500" /> {selectedContract.templateName || t("basicContracts.dialog.document")} + + {/* 현재 계약서 상태 표시 */} + {currentContractStatus?.status === 'completed' ? ( + <Badge variant="outline" className="ml-2 bg-green-50 text-green-700 border-green-200"> + <CheckCircle2 className="h-3 w-3 mr-1" /> + 서명 완료 + </Badge> + ) : currentContractStatus?.status === 'error' ? ( + <Badge variant="outline" className="ml-2 bg-red-50 text-red-700 border-red-200"> + <AlertCircle className="h-3 w-3 mr-1" /> + 처리 실패 + </Badge> + ) : ( + <Badge variant="outline" className="ml-2 bg-yellow-50 text-yellow-700 border-yellow-200"> + 서명 대기 + </Badge> + )} + {/* 준법 템플릿 표시 */} {selectedContract.templateName?.includes('준법') && ( <Badge variant="outline" className="ml-2 bg-amber-50 text-amber-700 border-amber-200"> 준법 서류 </Badge> )} + {/* 비밀유지 계약서인 경우 추가 파일 수 표시 */} {selectedContract.templateName === "비밀유지 계약서" && additionalFiles.length > 0 && ( <Badge variant="outline" className="ml-2 bg-blue-50 text-blue-700 border-blue-200"> @@ -388,59 +658,128 @@ export function BasicContractSignDialog({ {/* 뷰어 영역 - 남은 공간 모두 사용 */} <div className="flex-1 min-h-0 overflow-hidden"> <BasicContractSignViewer - key={selectedContract.id} // key 추가로 컴포넌트 재생성 강제 + key={selectedContract.id} contractId={selectedContract.id} filePath={selectedContract.signedFilePath || undefined} templateName={selectedContract.templateName || ""} - additionalFiles={additionalFiles} // 추가 파일 전달 + additionalFiles={additionalFiles} instance={instance} setInstance={setInstance} + onSurveyComplete={() => handleSurveyComplete(selectedContract.id)} // 🔥 추가 + onSignatureComplete={() => handleSignatureComplete(selectedContract.id)} // 🔥 추가 t={t} /> </div> - {/* 고정 푸터 */} + {/* 고정 푸터 - 동적 버튼 */} <div className="p-4 flex justify-between items-center bg-gray-50 border-t flex-shrink-0"> <div className="flex items-center space-x-4"> - <p className="text-sm text-gray-600 flex items-center"> - <AlertCircle className="h-4 w-4 text-yellow-500 mr-1" /> - {t("basicContracts.dialog.signWarning")} - </p> - {/* 준법 템플릿인 경우 추가 안내 */} - {selectedContract.templateName?.includes('준법') && ( - <p className="text-xs text-amber-600 flex items-center"> - <AlertCircle className="h-3 w-3 text-amber-500 mr-1" /> - 모든 준법 항목을 체크해주세요 + {/* 현재 계약서가 완료된 경우 */} + {currentContractStatus?.status === 'completed' ? ( + <p className="text-sm text-green-600 flex items-center"> + <CheckCircle2 className="h-4 w-4 text-green-500 mr-1" /> + 이 계약서는 이미 서명이 완료되었습니다 </p> - )} - {/* 비밀유지 계약서인 경우 추가 안내 */} - {selectedContract.templateName === "비밀유지 계약서" && additionalFiles.length > 0 && ( - <p className="text-xs text-blue-600 flex items-center"> - <FileText className="h-3 w-3 text-blue-500 mr-1" /> - 첨부 서류도 확인해주세요 + ) : currentContractStatus?.status === 'error' ? ( + <p className="text-sm text-red-600 flex items-center"> + <AlertCircle className="h-4 w-4 text-red-500 mr-1" /> + 서명 처리 중 오류가 발생했습니다. 다시 시도해주세요. </p> - )} - </div> - <Button - className="gap-2 bg-blue-600 hover:bg-blue-700 transition-colors" - onClick={completeSign} - disabled={isSubmitting} - > - {isSubmitting ? ( - <> - <svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> - <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> - <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> - </svg> - {t("basicContracts.dialog.processing")} - </> ) : ( <> - <FileSignature className="h-4 w-4" /> - {t("basicContracts.dialog.completeSign")} + {/* 🔥 완료 조건 안내 메시지 개선 */} + <div className="flex flex-col space-y-1"> + <p className="text-sm text-gray-600 flex items-center"> + <AlertCircle className="h-4 w-4 text-yellow-500 mr-1" /> + {t("basicContracts.dialog.signWarning")} + </p> + + {/* 완료 상태 체크리스트 */} + <div className="flex items-center space-x-4 text-xs"> + {selectedContract.templateName?.includes('준법') && ( + <span className={`flex items-center ${surveyCompletionStatus[selectedContract.id] ? 'text-green-600' : 'text-red-600'}`}> + <CheckCircle2 className={`h-3 w-3 mr-1 ${surveyCompletionStatus[selectedContract.id] ? 'text-green-500' : 'text-red-500'}`} /> + 설문조사 {surveyCompletionStatus[selectedContract.id] ? '완료' : '미완료'} + </span> + )} + <span className={`flex items-center ${signatureStatus[selectedContract.id] ? 'text-green-600' : 'text-red-600'}`}> + <Target className={`h-3 w-3 mr-1 ${signatureStatus[selectedContract.id] ? 'text-green-500' : 'text-red-500'}`} /> + 서명 {signatureStatus[selectedContract.id] ? '완료' : '미완료'} + </span> + </div> + </div> + + {/* 비밀유지 계약서인 경우 추가 안내 */} + {selectedContract.templateName === "비밀유지 계약서" && additionalFiles.length > 0 && ( + <p className="text-xs text-blue-600 flex items-center"> + <FileText className="h-3 w-3 text-blue-500 mr-1" /> + 첨부 서류도 확인해주세요 + </p> + )} </> )} - </Button> + </div> + + {/* 동적 버튼 영역 */} + <div className="flex items-center space-x-2"> + {allCompleted ? ( + // 모든 계약서 완료시 + <Button + className="gap-2 bg-green-600 hover:bg-green-700 transition-colors" + onClick={completeAllSigns} + > + <Trophy className="h-4 w-4" /> + 모든 서명 완료 + </Button> + ) : currentContractStatus?.status === 'completed' ? ( + // 현재 계약서가 완료된 경우 + <Button + variant="outline" + className="gap-2" + onClick={() => { + const nextContract = getNextPendingContract(); + if (nextContract) { + setSelectedContract(nextContract); + } + }} + disabled={!getNextPendingContract()} + > + <ArrowRight className="h-4 w-4" /> + 다음 계약서 + </Button> + ) : ( + // 현재 계약서를 서명해야 하는 경우 + <Button + className={`gap-2 transition-colors ${ + canCompleteCurrentContract + ? "bg-blue-600 hover:bg-blue-700" + : "bg-gray-400 cursor-not-allowed" + }`} + onClick={completeSign} + disabled={!canCompleteCurrentContract || isSubmitting} // 🔥 조건 수정 + > + {isSubmitting ? ( + <> + <svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> + <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> + <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> + </svg> + 처리중... + </> + ) : ( + <> + <FileSignature className="h-4 w-4" /> + 서명 완료 + {totalCount > 1 && ( + <span className="ml-1 text-xs"> + ({completedCount + 1}/{totalCount}) + </span> + )} + </> + )} + </Button> + )} + </div> </div> </> ) : ( |
