summaryrefslogtreecommitdiff
path: root/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx
diff options
context:
space:
mode:
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.tsx631
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>
)}