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 | 631 |
1 files changed, 444 insertions, 187 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 319ae4b9..f70bed94 100644 --- a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx +++ b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx @@ -22,14 +22,15 @@ import { Loader2, ArrowRight, Trophy, - Target + Target, + Shield } from "lucide-react"; 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"; +import { getVendorAttachments, processBuyerSignatureAction } from "../service"; // 계약서 상태 타입 정의 interface ContractStatus { @@ -42,16 +43,27 @@ interface BasicContractSignDialogProps { contracts: BasicContractView[]; onSuccess?: () => void; hasSelectedRows?: boolean; + mode?: 'vendor' | 'buyer'; + onBuyerSignComplete?: (contractId: number, signedData: ArrayBuffer) => void; t: (key: string) => string; + // 외부 상태 제어를 위한 새로운 props (선택적) + open?: boolean; + onOpenChange?: (open: boolean) => void; } export function BasicContractSignDialog({ contracts, onSuccess, hasSelectedRows = false, - t + mode = 'vendor', + onBuyerSignComplete, + t, + // 새로 추가된 props + open: externalOpen, + onOpenChange: externalOnOpenChange }: BasicContractSignDialogProps) { - const [open, setOpen] = React.useState(false); + // 내부 상태 (외부 제어가 없을 때 사용) + const [internalOpen, setInternalOpen] = React.useState(false); const [selectedContract, setSelectedContract] = React.useState<BasicContractView | null>(null); const [instance, setInstance] = React.useState<null | WebViewerInstance>(null); const [searchTerm, setSearchTerm] = React.useState(""); @@ -64,14 +76,21 @@ export function BasicContractSignDialog({ // 계약서 상태 관리 const [contractStatuses, setContractStatuses] = React.useState<ContractStatus[]>([]); - // 🔥 새로 추가: 서명/설문 완료 상태 관리 + // 서명/설문/GTC 코멘트 완료 상태 관리 const [surveyCompletionStatus, setSurveyCompletionStatus] = React.useState<Record<number, boolean>>({}); const [signatureStatus, setSignatureStatus] = React.useState<Record<number, boolean>>({}); + const [gtcCommentStatus, setGtcCommentStatus] = React.useState<Record<number, { hasComments: boolean; commentCount: number }>>({}); const router = useRouter() - console.log(selectedContract,"selectedContract") - console.log(additionalFiles,"additionalFiles") + // 실제 사용할 open 상태 (외부 제어가 있으면 외부 상태 사용, 없으면 내부 상태 사용) + const isControlledExternally = externalOpen !== undefined; + const open = isControlledExternally ? externalOpen : internalOpen; + + // 모드에 따른 텍스트 + const isBuyerMode = mode === 'buyer'; + const dialogTitle = isBuyerMode ? "구매자 최종승인 서명" : t("basicContracts.dialog.title"); + const signButtonText = isBuyerMode ? "최종승인 완료" : "서명 완료 및 저장"; // 버튼 비활성화 조건 const isButtonDisabled = !hasSelectedRows || contracts.length === 0; @@ -87,29 +106,28 @@ export function BasicContractSignDialog({ return ""; }; - // 🔥 현재 선택된 계약서의 서명 완료 가능 여부 확인 + // 현재 선택된 계약서의 서명 완료 가능 여부 확인 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'); - // 1. 준법 템플릿인 경우 설문조사 완료 여부 확인 const surveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contractId] === true : true; - - // 2. 서명 완료 여부 확인 + const gtcCompleted = isGTCTemplate ? (gtcCommentStatus[contractId]?.hasComments !== true) : true; const signatureCompleted = signatureStatus[contractId] === true; - console.log('🔍 서명 완료 가능 여부 체크:', { - contractId, - isComplianceTemplate, - surveyCompleted, - signatureCompleted, - canComplete: surveyCompleted && signatureCompleted - }); - - return surveyCompleted && signatureCompleted; - }, [selectedContract, surveyCompletionStatus, signatureStatus]); + return surveyCompleted && gtcCompleted && signatureCompleted; + }, [selectedContract, surveyCompletionStatus, signatureStatus, gtcCommentStatus, isBuyerMode]); // 계약서별 상태 초기화 React.useEffect(() => { @@ -142,7 +160,7 @@ export function BasicContractSignDialog({ return contracts.find(contract => contract.id === nextPendingId) || null; }; - // 다이얼로그 열기/닫기 핸들러 + // 다이얼로그 열기/닫기 핸들러 (외부 제어 지원) const handleOpenChange = (isOpen: boolean) => { if (!isOpen && !allCompleted && completedCount > 0) { // 완료되지 않은 계약서가 있으면 확인 대화상자 @@ -152,7 +170,12 @@ export function BasicContractSignDialog({ if (!confirmClose) return; } - setOpen(isOpen); + // 외부 제어가 있으면 외부 콜백 호출, 없으면 내부 상태 업데이트 + if (isControlledExternally && externalOnOpenChange) { + externalOnOpenChange(isOpen); + } else { + setInternalOpen(isOpen); + } if (!isOpen) { // 다이얼로그 닫을 때 상태 초기화 @@ -160,8 +183,9 @@ export function BasicContractSignDialog({ setSearchTerm(""); setAdditionalFiles([]); setContractStatuses([]); - setSurveyCompletionStatus({}); // 🔥 추가 - setSignatureStatus({}); // 🔥 추가 + setSurveyCompletionStatus({}); + setSignatureStatus({}); + setGtcCommentStatus({}); // WebViewer 인스턴스 정리 if (instance) { try { @@ -202,9 +226,14 @@ export function BasicContractSignDialog({ } } }, [open, contracts, selectedContract, contractStatuses]); - - // 추가 파일 가져오기 useEffect + + // 추가 파일 가져오기 useEffect (구매자 모드에서는 스킵) React.useEffect(() => { + if (isBuyerMode) { + setAdditionalFiles([]); + return; + } + const fetchAdditionalFiles = async () => { if (!selectedContract) { setAdditionalFiles([]); @@ -235,9 +264,9 @@ export function BasicContractSignDialog({ }; fetchAdditionalFiles(); - }, [selectedContract]); + }, [selectedContract, isBuyerMode]); - // 🔥 설문조사 완료 콜백 함수 + // 설문조사 완료 콜백 함수 const handleSurveyComplete = React.useCallback((contractId: number) => { console.log(`📋 설문조사 완료: 계약서 ${contractId}`); setSurveyCompletionStatus(prev => ({ @@ -246,7 +275,7 @@ export function BasicContractSignDialog({ })); }, []); - // 🔥 서명 완료 콜백 함수 + // 서명 완료 콜백 함수 const handleSignatureComplete = React.useCallback((contractId: number) => { console.log(`✍️ 서명 완료: 계약서 ${contractId}`); setSignatureStatus(prev => ({ @@ -255,31 +284,64 @@ export function BasicContractSignDialog({ })); }, []); - // 서명 완료 핸들러 (수정됨) + // GTC 코멘트 상태 변경 콜백 함수 + const handleGtcCommentStatusChange = React.useCallback((contractId: number, hasComments: boolean, commentCount: number) => { + console.log(`📋 GTC 코멘트 상태 변경: 계약서 ${contractId}, 코멘트 ${commentCount}개`); + setGtcCommentStatus(prev => ({ + ...prev, + [contractId]: { hasComments, commentCount } + })); + }, []); + + // 서명 완료 핸들러 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; + if (isBuyerMode) { + const signatureCompleted = signatureStatus[contractId] === true; + + if (!signatureCompleted) { + toast.error("계약서에 서명을 먼저 완료해주세요.", { + description: "문서의 서명 필드에 서명해주세요.", + icon: <Target className="h-5 w-5 text-blue-500" /> + }); + return; + } + } else { + // 협력업체 모드의 기존 검증 로직 + 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 signatureCompleted = signatureStatus[contractId] === true; + + if (!surveyCompleted) { + toast.error("준법 설문조사를 먼저 완료해주세요.", { + description: "설문조사 탭에서 모든 필수 항목을 완료해주세요.", + icon: <AlertCircle className="h-5 w-5 text-red-500" /> + }); + return; + } + + if (!gtcCompleted) { + toast.error("GTC 조항에 코멘트가 있어 서명할 수 없습니다.", { + 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; @@ -303,73 +365,135 @@ export function BasicContractSignDialog({ xfdfString, downloadType: "pdf", }); - - // FormData 생성 및 파일 추가 - 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)); - } - - // 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 - ? { ...status, status: 'completed' as const } - : status - ) + + if (isBuyerMode) { + // 구매자 모드: 최종승인 처리 + const result = await processBuyerSignatureAction( + selectedContract.id, + data, + selectedContract.signedFileName || `contract_${selectedContract.id}_buyer_signed.pdf` ); - toast.success("계약서 서명이 완료되었습니다!", { - description: `${selectedContract.templateName} - ${completedCount + 1}/${totalCount}개 완료`, - icon: <CheckCircle2 className="h-5 w-5 text-green-500" /> - }); + if (result.success) { + // 성공시 해당 계약서 상태를 완료로 업데이트 + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id + ? { ...status, status: 'completed' as const } + : status + ) + ); - // 다음 미완료 계약서로 자동 이동 - const nextContract = getNextPendingContract(); - if (nextContract) { - setSelectedContract(nextContract); - toast.info(`다음 계약서로 이동합니다`, { - description: nextContract.templateName, - icon: <ArrowRight className="h-4 w-4 text-blue-500" /> + toast.success("최종승인이 완료되었습니다!", { + description: `${selectedContract.templateName} - ${completedCount + 1}/${totalCount}개 완료`, + icon: <CheckCircle2 className="h-5 w-5 text-green-500" /> }); + + // 구매자 서명 완료 콜백 호출 + if (onBuyerSignComplete) { + onBuyerSignComplete(selectedContract.id, data); + } + + // 다음 미완료 계약서로 자동 이동 + 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.success("🎉 모든 계약서 서명이 완료되었습니다!", { - description: `총 ${totalCount}개 계약서 서명 완료`, - icon: <Trophy className="h-5 w-5 text-yellow-500" /> + // 실패시 에러 상태 업데이트 + setContractStatuses(prev => + prev.map(status => + status.id === selectedContract.id + ? { ...status, status: 'error' as const, errorMessage: result.message } + : status + ) + ); + + toast.error("최종승인 처리 중 오류가 발생했습니다", { + description: result.message, + icon: <AlertCircle className="h-5 w-5 text-red-500" /> }); } - - router.refresh(); } else { - // 실패시 에러 상태 업데이트 - 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" /> + // 협력업체 모드: 기존 로직 + 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)); + } + + // 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 + ? { ...status, status: 'completed' as const } + : status + ) + ); + + toast.success("계약서 서명이 완료되었습니다!", { + description: `${selectedContract.templateName} - ${completedCount + 1}/${totalCount}개 완료`, + icon: <CheckCircle2 className="h-5 w-5 text-green-500" /> + }); + + // 다음 미완료 계약서로 자동 이동 + 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 { + // 실패시 에러 상태 업데이트 + 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); @@ -391,56 +515,99 @@ export function BasicContractSignDialog({ // 모든 서명 완료 핸들러 const completeAllSigns = () => { - setOpen(false); + handleOpenChange(false); if (onSuccess) { onSuccess(); } - toast.success("모든 계약서 서명이 완료되었습니다!", { - description: "계약서 관리 페이지가 새로고침됩니다.", + const successMessage = isBuyerMode + ? "모든 계약서 최종승인이 완료되었습니다!" + : "모든 계약서 서명이 완료되었습니다!"; + + toast.success(successMessage, { + description: "계약서 관리 페이지가 새고침됩니다.", icon: <Trophy className="h-5 w-5 text-yellow-500" /> }); }; return ( <> - {/* 서명 버튼 */} - <Button - variant="outline" - size="sm" - onClick={() => setOpen(true)} - 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" - > - <Upload - className={`size-4 ${isButtonDisabled ? 'text-gray-400' : 'text-blue-500'}`} - aria-hidden="true" - /> - <span className="hidden sm:inline flex items-center"> - {t("basicContracts.toolbar.sign")} - {contracts.length > 0 && !isButtonDisabled && ( - <Badge variant="secondary" className="ml-2 bg-blue-100 text-blue-700 hover:bg-blue-200"> - {contracts.length} - </Badge> + {/* 서명 버튼 - 외부 제어가 없을 때만 표시 */} + {!isControlledExternally && ( + <Button + variant="outline" + size="sm" + onClick={() => handleOpenChange(true)} + 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" + : "hover:bg-blue-50 hover:text-blue-600 hover:border-blue-200" )} - {isButtonDisabled && ( - <span className="ml-2 text-xs text-gray-400"> - ({getDisabledReason()}) - </span> + > + {isBuyerMode ? ( + <Shield + className={`size-4 ${isButtonDisabled ? 'text-gray-400' : 'text-green-500'}`} + aria-hidden="true" + /> + ) : ( + <Upload + className={`size-4 ${isButtonDisabled ? 'text-gray-400' : 'text-blue-500'}`} + aria-hidden="true" + /> )} - </span> - </Button> + <span className="hidden sm:inline flex items-center"> + {isBuyerMode ? "구매자 승인" : t("basicContracts.toolbar.sign")} + {contracts.length > 0 && !isButtonDisabled && ( + <Badge + variant="secondary" + className={cn( + "ml-2", + isBuyerMode + ? "bg-green-100 text-green-700 hover:bg-green-200" + : "bg-blue-100 text-blue-700 hover:bg-blue-200" + )}최 + > + {contracts.length} + </Badge> + )} + {isButtonDisabled && ( + <span className="ml-2 text-xs text-gray-400"> + ({getDisabledReason()}) + </span> + )} + </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"> + <DialogHeader className={cn( + "px-6 py-4 border-b flex-shrink-0", + isBuyerMode + ? "bg-gradient-to-r from-green-50 to-emerald-50" + : "bg-gradient-to-r from-blue-50 to-purple-50" + )}> <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")} + {isBuyerMode ? ( + <Shield className="mr-2 h-5 w-5 text-green-500" /> + ) : ( + <FileSignature className="mr-2 h-5 w-5 text-blue-500" /> + )} + {dialogTitle} {/* 진행 상황 표시 */} - <Badge variant="outline" className="ml-3 bg-blue-50 text-blue-700 border-blue-200"> + <Badge + variant="outline" + className={cn( + "ml-3", + isBuyerMode + ? "bg-green-50 text-green-700 border-green-200" + : "bg-blue-50 text-blue-700 border-blue-200" + )} + > {completedCount}/{totalCount} 완료 </Badge> {/* 추가 파일 로딩 표시 */} @@ -466,7 +633,12 @@ export function BasicContractSignDialog({ </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" + className={cn( + "h-2 rounded-full transition-all duration-500", + isBuyerMode + ? "bg-gradient-to-r from-green-500 to-emerald-500" + : "bg-gradient-to-r from-blue-500 to-green-500" + )} style={{ width: `${(completedCount / totalCount) * 100}%` }} /> </div> @@ -506,9 +678,11 @@ export function BasicContractSignDialog({ const isCompleted = contractStatus?.status === 'completed'; const hasError = contractStatus?.status === 'error'; - // 🔥 계약서별 완료 상태 확인 + // 계약서별 완료 상태 확인 const isComplianceTemplate = contract.templateName?.includes('준법'); + const isGTCTemplate = contract.templateName?.includes('GTC'); const hasSurveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contract.id] === true : true; + const hasGtcCompleted = isGTCTemplate ? (gtcCommentStatus[contract.id]?.hasComments !== true) : true; const hasSignatureCompleted = signatureStatus[contract.id] === true; return ( @@ -530,7 +704,11 @@ export function BasicContractSignDialog({ {/* 첫 번째 줄: 제목 + 상태 */} <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" /> + {isBuyerMode ? ( + <Shield className="h-3 w-3 mr-1 text-green-500 flex-shrink-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 === "비밀유지 계약서" && ( @@ -538,6 +716,12 @@ export function BasicContractSignDialog({ NDA </Badge> )} + {/* GTC 계약서인 경우 표시 */} + {contract.templateName?.includes('GTC') && ( + <Badge variant="outline" className="ml-1 bg-purple-50 text-purple-700 border-purple-200 text-xs"> + GTC + </Badge> + )} </span> {/* 상태 표시 */} @@ -558,8 +742,8 @@ export function BasicContractSignDialog({ )} </div> - {/* 🔥 완료 상태 표시 */} - {!isCompleted && !hasError && ( + {/* 완료 상태 표시 (구매자 모드에서는 간소화) */} + {!isCompleted && !hasError && !isBuyerMode && ( <div className="flex items-center space-x-2 text-xs"> {isComplianceTemplate && ( <span className={`flex items-center ${hasSurveyCompleted ? 'text-green-600' : 'text-gray-400'}`}> @@ -567,6 +751,12 @@ export function BasicContractSignDialog({ 설문 </span> )} + {isGTCTemplate && ( + <span className={`flex items-center ${hasGtcCompleted ? 'text-green-600' : 'text-red-600'}`}> + <CheckCircle2 className={`h-3 w-3 mr-1 ${hasGtcCompleted ? 'text-green-500' : 'text-red-500'}`} /> + 조항검토 + </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'}`} /> 서명 @@ -574,6 +764,16 @@ export function BasicContractSignDialog({ </div> )} + {/* 구매자 모드의 간소화된 상태 표시 */} + {!isCompleted && !hasError && isBuyerMode && ( + <div className="flex items-center space-x-2 text-xs"> + <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"> @@ -609,14 +809,18 @@ export function BasicContractSignDialog({ {/* 뷰어 헤더 */} <div className="p-4 border-b bg-gray-50 flex-shrink-0"> <h3 className="font-semibold text-gray-800 flex items-center"> - <FileText className="h-4 w-4 mr-2 text-blue-500" /> + {isBuyerMode ? ( + <Shield className="h-4 w-4 mr-2 text-green-500" /> + ) : ( + <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" /> - 서명 완료 + {isBuyerMode ? "승인 완료" : "서명 완료"} </Badge> ) : currentContractStatus?.status === 'error' ? ( <Badge variant="outline" className="ml-2 bg-red-50 text-red-700 border-red-200"> @@ -625,19 +829,32 @@ export function BasicContractSignDialog({ </Badge> ) : ( <Badge variant="outline" className="ml-2 bg-yellow-50 text-yellow-700 border-yellow-200"> - 서명 대기 + {isBuyerMode ? "승인 대기" : "서명 대기"} + </Badge> + )} + + {/* 구매자 모드 배지 */} + {isBuyerMode && ( + <Badge variant="outline" className="ml-2 bg-green-50 text-green-700 border-green-200"> + 구매자 모드 </Badge> )} - {/* 준법 템플릿 표시 */} - {selectedContract.templateName?.includes('준법') && ( + {/* 준법/GTC 템플릿 표시 (구매자 모드가 아닐 때만) */} + {!isBuyerMode && selectedContract.templateName?.includes('준법') && ( <Badge variant="outline" className="ml-2 bg-amber-50 text-amber-700 border-amber-200"> 준법 서류 </Badge> )} + + {!isBuyerMode && selectedContract.templateName?.includes('GTC') && ( + <Badge variant="outline" className="ml-2 bg-purple-50 text-purple-700 border-purple-200"> + GTC 계약서 + </Badge> + )} - {/* 비밀유지 계약서인 경우 추가 파일 수 표시 */} - {selectedContract.templateName === "비밀유지 계약서" && additionalFiles.length > 0 && ( + {/* 비밀유지 계약서인 경우 추가 파일 수 표시 (구매자 모드가 아닐 때만) */} + {!isBuyerMode && selectedContract.templateName === "비밀유지 계약서" && additionalFiles.length > 0 && ( <Badge variant="outline" className="ml-2 bg-blue-50 text-blue-700 border-blue-200"> 첨부파일 {additionalFiles.length}개 </Badge> @@ -665,8 +882,12 @@ export function BasicContractSignDialog({ additionalFiles={additionalFiles} instance={instance} setInstance={setInstance} - onSurveyComplete={() => handleSurveyComplete(selectedContract.id)} // 🔥 추가 - onSignatureComplete={() => handleSignatureComplete(selectedContract.id)} // 🔥 추가 + onSurveyComplete={() => handleSurveyComplete(selectedContract.id)} + onSignatureComplete={() => handleSignatureComplete(selectedContract.id)} + onGtcCommentStatusChange={(hasComments, commentCount) => + handleGtcCommentStatusChange(selectedContract.id, hasComments, commentCount) + } + mode={mode} t={t} /> </div> @@ -678,44 +899,58 @@ export function BasicContractSignDialog({ {currentContractStatus?.status === 'completed' ? ( <p className="text-sm text-green-600 flex items-center"> <CheckCircle2 className="h-4 w-4 text-green-500 mr-1" /> - 이 계약서는 이미 서명이 완료되었습니다 + 이 계약서는 이미 {isBuyerMode ? "승인이" : "서명이"} 완료되었습니다 </p> ) : currentContractStatus?.status === 'error' ? ( <p className="text-sm text-red-600 flex items-center"> <AlertCircle className="h-4 w-4 text-red-500 mr-1" /> - 서명 처리 중 오류가 발생했습니다. 다시 시도해주세요. + {isBuyerMode ? "승인" : "서명"} 처리 중 오류가 발생했습니다. 다시 시도해주세요. </p> ) : ( <> - {/* 🔥 완료 조건 안내 메시지 개선 */} + {/* 완료 조건 안내 메시지 */} <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")} + {isBuyerMode + ? "계약서에 구매자 서명을 완료해주세요." + : 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] ? '완료' : '미완료'} + {!isBuyerMode && ( + <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> + )} + {selectedContract.templateName?.includes('GTC') && ( + <span className={`flex items-center ${(gtcCommentStatus[selectedContract.id]?.hasComments !== true) ? 'text-green-600' : 'text-red-600'}`}> + <CheckCircle2 className={`h-3 w-3 mr-1 ${(gtcCommentStatus[selectedContract.id]?.hasComments !== true) ? 'text-green-500' : 'text-red-500'}`} /> + 조항검토 {(gtcCommentStatus[selectedContract.id]?.hasComments !== true) ? '완료' : + `미완료 (코멘트 ${gtcCommentStatus[selectedContract.id]?.commentCount || 0}개)`} + </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> - )} - <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> + )} + + {/* 구매자 모드의 간소화된 체크리스트 */} + {isBuyerMode && ( + <div className="flex items-center space-x-4 text-xs"> + <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> - )} </> )} </div> @@ -725,11 +960,16 @@ export function BasicContractSignDialog({ {allCompleted ? ( // 모든 계약서 완료시 <Button - className="gap-2 bg-green-600 hover:bg-green-700 transition-colors" + className={cn( + "gap-2 transition-colors", + isBuyerMode + ? "bg-green-600 hover:bg-green-700" + : "bg-green-600 hover:bg-green-700" + )} onClick={completeAllSigns} > <Trophy className="h-4 w-4" /> - 모든 서명 완료 + 모든 {isBuyerMode ? "승인" : "서명"} 완료 </Button> ) : currentContractStatus?.status === 'completed' ? ( // 현재 계약서가 완료된 경우 @@ -750,13 +990,16 @@ export function BasicContractSignDialog({ ) : ( // 현재 계약서를 서명해야 하는 경우 <Button - className={`gap-2 transition-colors ${ + className={cn( + "gap-2 transition-colors", canCompleteCurrentContract - ? "bg-blue-600 hover:bg-blue-700" + ? isBuyerMode + ? "bg-green-600 hover:bg-green-700" + : "bg-blue-600 hover:bg-blue-700" : "bg-gray-400 cursor-not-allowed" - }`} + )} onClick={completeSign} - disabled={!canCompleteCurrentContract || isSubmitting} // 🔥 조건 수정 + disabled={!canCompleteCurrentContract || isSubmitting} > {isSubmitting ? ( <> @@ -768,11 +1011,15 @@ export function BasicContractSignDialog({ </> ) : ( <> - <FileSignature className="h-4 w-4" /> - 서명 완료 + {isBuyerMode ? ( + <Shield className="h-4 w-4" /> + ) : ( + <FileSignature className="h-4 w-4" /> + )} + {signButtonText} {totalCount > 1 && ( <span className="ml-1 text-xs"> - ({completedCount + 1}/{totalCount}) + ({completedCount}/{totalCount}) </span> )} </> @@ -784,12 +1031,22 @@ export function BasicContractSignDialog({ </> ) : ( <div className="flex flex-col items-center justify-center h-full text-center p-6"> - <div className="bg-blue-50 p-6 rounded-full mb-4"> - <FileSignature className="h-12 w-12 text-blue-500" /> + <div className={cn( + "p-6 rounded-full mb-4", + isBuyerMode ? "bg-green-50" : "bg-blue-50" + )}> + {isBuyerMode ? ( + <Shield className="h-12 w-12 text-green-500" /> + ) : ( + <FileSignature className="h-12 w-12 text-blue-500" /> + )} </div> <h3 className="text-xl font-medium text-gray-800 mb-2">{t("basicContracts.dialog.selectDocument")}</h3> <p className="text-gray-500 max-w-md"> - {t("basicContracts.dialog.selectDocumentDescription")} + {isBuyerMode + ? "승인할 계약서를 선택해주세요." + : t("basicContracts.dialog.selectDocumentDescription") + } </p> </div> )} |
