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.tsx237
1 files changed, 127 insertions, 110 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 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 | WebViewerInstance>(null);
const [searchTerm, setSearchTerm] = React.useState("");
const [isSubmitting, setIsSubmitting] = React.useState(false);
-
+
// 추가된 state들
const [additionalFiles, setAdditionalFiles] = React.useState<any[]>([]);
const [isLoadingAttachments, setIsLoadingAttachments] = React.useState(false);
-
+
// 계약서 상태 관리
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 [gtcCommentStatus, setGtcCommentStatus] = React.useState<Record<number, {
+ hasComments: boolean;
+ commentCount: number;
+ reviewStatus?: string;
+ isComplete?: boolean;
+ }>>({});
+
+ 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: <Trophy className="h-5 w-5 text-yellow-500" />
@@ -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 ? (
- <Shield
- className={`size-4 ${isButtonDisabled ? 'text-gray-400' : 'text-green-500'}`}
- aria-hidden="true"
+ <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"
+ <Upload
+ className={`size-4 ${isButtonDisabled ? 'text-gray-400' : 'text-blue-500'}`}
+ aria-hidden="true"
/>
)}
<span className="hidden sm:inline flex items-center">
- {isBuyerMode ? "구매자 승인" : t("basicContracts.toolbar.sign")}
+ {isBuyerMode ? "구매자 서명" : t("basicContracts.toolbar.sign")}
{contracts.length > 0 && !isButtonDisabled && (
- <Badge
- variant="secondary"
+ <Badge
+ variant="secondary"
className={cn(
"ml-2",
- isBuyerMode
- ? "bg-green-100 text-green-700 hover:bg-green-200"
+ isBuyerMode
+ ? "bg-green-100 text-green-700 hover:bg-green-200"
: "bg-blue-100 text-blue-700 hover:bg-blue-200"
- )}최
+ )}
>
{contracts.length}
</Badge>
@@ -582,12 +599,12 @@ export function BasicContractSignDialog({
{/* 서명 다이얼로그 */}
<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'}}>
+ <DialogContent className="max-w-7xl w-[95vw] h-[90vh] p-0 flex flex-col overflow-hidden" style={{ width: '95vw', maxWidth: '95vw' }}>
{/* 고정 헤더 - 진행 상황 표시 */}
<DialogHeader className={cn(
"px-6 py-4 border-b flex-shrink-0",
- isBuyerMode
- ? "bg-gradient-to-r from-green-50 to-emerald-50"
+ 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">
@@ -599,12 +616,12 @@ export function BasicContractSignDialog({
)}
{dialogTitle}
{/* 진행 상황 표시 */}
- <Badge
- variant="outline"
+ <Badge
+ variant="outline"
className={cn(
"ml-3",
- isBuyerMode
- ? "bg-green-50 text-green-700 border-green-200"
+ isBuyerMode
+ ? "bg-green-50 text-green-700 border-green-200"
: "bg-blue-50 text-blue-700 border-blue-200"
)}
>
@@ -615,7 +632,7 @@ export function BasicContractSignDialog({
<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" />
@@ -635,8 +652,8 @@ export function BasicContractSignDialog({
<div
className={cn(
"h-2 rounded-full transition-all duration-500",
- isBuyerMode
- ? "bg-gradient-to-r from-green-500 to-emerald-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}%` }}
@@ -677,14 +694,14 @@ export function BasicContractSignDialog({
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 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 (
<Button
key={contract.id}
@@ -723,7 +740,7 @@ export function BasicContractSignDialog({
</Badge>
)}
</span>
-
+
{/* 상태 표시 */}
{isCompleted ? (
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200 text-xs ml-2 flex-shrink-0">
@@ -741,7 +758,7 @@ export function BasicContractSignDialog({
</Badge>
)}
</div>
-
+
{/* 완료 상태 표시 (구매자 모드에서는 간소화) */}
{!isCompleted && !hasError && !isBuyerMode && (
<div className="flex items-center space-x-2 text-xs">
@@ -763,7 +780,7 @@ export function BasicContractSignDialog({
</span>
</div>
)}
-
+
{/* 구매자 모드의 간소화된 상태 표시 */}
{!isCompleted && !hasError && isBuyerMode && (
<div className="flex items-center space-x-2 text-xs">
@@ -773,7 +790,7 @@ export function BasicContractSignDialog({
</span>
</div>
)}
-
+
{/* 두 번째 줄: 사용자 + 날짜 */}
<div className="flex items-center justify-between text-xs text-gray-500">
<div className="flex items-center min-w-0">
@@ -815,7 +832,7 @@ export function BasicContractSignDialog({
<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">
@@ -852,7 +869,7 @@ export function BasicContractSignDialog({
GTC 계약서
</Badge>
)}
-
+
{/* 비밀유지 계약서인 경우 추가 파일 수 표시 (구매자 모드가 아닐 때만) */}
{!isBuyerMode && selectedContract.templateName === "비밀유지 계약서" && additionalFiles.length > 0 && (
<Badge variant="outline" className="ml-2 bg-blue-50 text-blue-700 border-blue-200">
@@ -871,7 +888,7 @@ export function BasicContractSignDialog({
</span>
</div>
</div>
-
+
{/* 뷰어 영역 - 남은 공간 모두 사용 */}
<div className="flex-1 min-h-0 overflow-hidden">
<BasicContractSignViewer
@@ -884,8 +901,8 @@ export function BasicContractSignDialog({
setInstance={setInstance}
onSurveyComplete={() => handleSurveyComplete(selectedContract.id)}
onSignatureComplete={() => handleSignatureComplete(selectedContract.id)}
- onGtcCommentStatusChange={(hasComments, commentCount) =>
- handleGtcCommentStatusChange(selectedContract.id, hasComments, commentCount)
+ onGtcCommentStatusChange={(hasComments, commentCount, reviewStatus, isComplete) =>
+ handleGtcCommentStatusChange(selectedContract.id, hasComments, commentCount, reviewStatus, isComplete)
}
mode={mode}
t={t}
@@ -912,12 +929,12 @@ export function BasicContractSignDialog({
<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" />
- {isBuyerMode
+ {isBuyerMode
? "계약서에 구매자 서명을 완료해주세요."
: t("basicContracts.dialog.signWarning")
}
</p>
-
+
{/* 완료 상태 체크리스트 */}
{!isBuyerMode && (
<div className="flex items-center space-x-4 text-xs">
@@ -928,9 +945,9 @@ export function BasicContractSignDialog({
</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) ? '완료' :
+ <span className={`flex items-center ${(gtcCommentStatus[selectedContract.id]?.isComplete === true) ? 'text-green-600' : 'text-red-600'}`}>
+ <CheckCircle2 className={`h-3 w-3 mr-1 ${(gtcCommentStatus[selectedContract.id]?.isComplete === true) ? 'text-green-500' : 'text-red-500'}`} />
+ 조항검토 {(gtcCommentStatus[selectedContract.id]?.isComplete === true) ? '완료' :
`미완료 (코멘트 ${gtcCommentStatus[selectedContract.id]?.commentCount || 0}개)`}
</span>
)}
@@ -962,8 +979,8 @@ export function BasicContractSignDialog({
<Button
className={cn(
"gap-2 transition-colors",
- isBuyerMode
- ? "bg-green-600 hover:bg-green-700"
+ isBuyerMode
+ ? "bg-green-600 hover:bg-green-700"
: "bg-green-600 hover:bg-green-700"
)}
onClick={completeAllSigns}
@@ -992,7 +1009,7 @@ export function BasicContractSignDialog({
<Button
className={cn(
"gap-2 transition-colors",
- canCompleteCurrentContract
+ canCompleteCurrentContract
? isBuyerMode
? "bg-green-600 hover:bg-green-700"
: "bg-blue-600 hover:bg-blue-700"
@@ -1043,7 +1060,7 @@ export function BasicContractSignDialog({
</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">
- {isBuyerMode
+ {isBuyerMode
? "승인할 계약서를 선택해주세요."
: t("basicContracts.dialog.selectDocumentDescription")
}