From 77cbcaf27c9de8b361a6c5a13f0eefb37fd0d0e5 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 20 Nov 2025 02:52:16 +0000 Subject: (임수민) 기본계약서 서명 위치, 설문조사 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../viewer/basic-contract-sign-viewer.tsx | 164 ++++++++++++++++----- 1 file changed, 129 insertions(+), 35 deletions(-) (limited to 'lib') diff --git a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx index 7f5fa027..adc735e9 100644 --- a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx +++ b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx @@ -697,6 +697,9 @@ export function BasicContractSignViewer({ }: BasicContractSignViewerProps) { const { toast } = useToast(); + // 🔽 추가 + const signatureFieldsRef = useRef([]); + const [fileLoading, setFileLoading] = useState(true); const [activeTab, setActiveTab] = useState("main"); const [surveyData, setSurveyData] = useState({}); @@ -822,6 +825,11 @@ export function BasicContractSignViewer({ setTimeout(() => cleanupHtmlStyle(), 100); }; + // 🔽 최신 서명 필드 이름들을 ref에 동기화 + useEffect(() => { + signatureFieldsRef.current = signatureFields; + }, [signatureFields]); + useEffect(() => { setShowDialog(isOpen); @@ -960,33 +968,57 @@ export function BasicContractSignViewer({ documentViewer.addEventListener('documentLoaded', handleDocumentLoaded); - // 구매자 모드가 아닐 때만 자동 서명 적용 - if (mode !== 'buyer') { - annotationManager.addEventListener('annotationChanged', async (annotList, type) => { - for (const annot of annotList) { - const { fieldName, X, Y, Width, Height, PageNumber } = annot; - - if (type === "add" && annot.Subject === "Widget") { - const signatureImage = await getVendorSignatureFile() - - const stamp = new Annotations.StampAnnotation(); - stamp.PageNumber = PageNumber; - stamp.X = X; - stamp.Y = Y; - stamp.Width = Width; - stamp.Height = Height; - - const dataUrl = signatureImage?.data?.dataUrl; - if (dataUrl) { - await stamp.setImageData(dataUrl); - annot.sign(stamp); - annot.setFieldFlag(WidgetFlags.READ_ONLY, true); - } - + // 🔁 서명 관련 annotation 제어 + annotationManager.addEventListener('annotationChanged', async (annotList, type) => { + if (type !== 'add') return; + + for (const annot of annotList) { + const isWidgetSignature = + annot instanceof Annotations.SignatureWidgetAnnotation; + + // 1) 필드 없는(Signature) 서명은 전부 막기 + // - WebViewer 기본 "어디나 한 번 찍는" 서명은 보통 Subject 가 "Signature" 로 들어옵니다. + // - 혹시 다르면 console.log(annot) 찍어서 Subject 를 맞춰주면 됩니다. + if (!isWidgetSignature) { + if (annot.Subject === 'Signature') { + // 지정된 서명란 외 서명 → 즉시 삭제 + annotationManager.deleteAnnotation(annot, false); } + continue; } - }); - } + + // 2) 서명 위젯(필드)은 "우리가 생성한 필드"만 허용 + const field = annot.getField && annot.getField(); + const name = field?.name as string | undefined; + const allowed = signatureFieldsRef.current; + + if (name && allowed.length > 0 && !allowed.includes(name)) { + // 우리가 만든 서명 필드가 아니면 막기 + annotationManager.deleteAnnotation(annot, false); + continue; + } + + // 3) 여기까지 통과한 경우 = 우리가 만든 서명 필드에 대한 서명 + // → 기존 자동 서명(협력사 서명) 로직 유지 + if (mode !== 'buyer') { + const signatureImage = await getVendorSignatureFile(); + if (!signatureImage?.data?.dataUrl) continue; + + const stamp = new Annotations.StampAnnotation(); + stamp.PageNumber = annot.PageNumber; + stamp.X = annot.X; + stamp.Y = annot.Y; + stamp.Width = annot.Width; + stamp.Height = annot.Height; + + await stamp.setImageData(signatureImage.data.dataUrl); + + const { WidgetFlags } = Annotations; + annot.sign(stamp); + annot.setFieldFlag(WidgetFlags.READ_ONLY, true); + } + } + }); newInstance.UI.setMinZoomLevel('25%'); newInstance.UI.setMaxZoomLevel('400%'); @@ -1329,6 +1361,15 @@ export function BasicContractSignViewer({ {/* 구매자 모드에서는 탭 없이 단일 뷰어만 표시 */} {allFiles.length > 1 && mode !== 'buyer' ? ( + {/* 준법설문 미완료 알림 배너 */} + {isComplianceTemplate && !surveyData.completed && ( +
+ + + ⚠️ 준법 설문조사를 먼저 완료해주세요. 서명을 진행하려면 "준법 설문조사" 탭을 클릭하세요. + +
+ )}
{allFiles.map((file, index) => { @@ -1344,19 +1385,41 @@ export function BasicContractSignViewer({ tabId = `file-${fileOnlyIndex}`; } + const isSurveyTab = file.type === 'survey'; + const isSurveyIncomplete = isSurveyTab && !surveyData.completed; + return ( - +
{file.type === 'survey' ? ( - + ) : file.type === 'clauses' ? ( ) : ( )} - {file.name} - {file.type === 'survey' && surveyData.completed && ( - 완료 + + {file.name} + + {isSurveyTab && !surveyData.completed && ( + + 필수 + + )} + {isSurveyTab && surveyData.completed && ( + + 완료 + )} {file.type === 'clauses' && gtcCommentStatus.hasComments && ( @@ -1529,6 +1592,15 @@ export function BasicContractSignViewer({ {/* 구매자 모드에서는 탭 없이 단일 뷰어만 표시 */} {allFiles.length > 1 && mode !== 'buyer' ? ( + {/* 준법설문 미완료 알림 배너 */} + {isComplianceTemplate && !surveyData.completed && ( +
+ + + ⚠️ 준법 설문조사를 먼저 완료해주세요. 서명을 진행하려면 "준법 설문조사" 탭을 클릭하세요. + +
+ )}
{allFiles.map((file, index) => { @@ -1544,19 +1616,41 @@ export function BasicContractSignViewer({ tabId = `file-${fileOnlyIndex}`; } + const isSurveyTab = file.type === 'survey'; + const isSurveyIncomplete = isSurveyTab && !surveyData.completed; + return ( - +
{file.type === 'survey' ? ( - + ) : file.type === 'clauses' ? ( ) : ( )} - {file.name} - {file.type === 'survey' && surveyData.completed && ( - 완료 + + {file.name} + + {isSurveyTab && !surveyData.completed && ( + + 필수 + + )} + {isSurveyTab && surveyData.completed && ( + + 완료 + )} {file.type === 'clauses' && gtcCommentStatus.hasComments && ( -- cgit v1.2.3