summaryrefslogtreecommitdiff
path: root/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract/viewer/basic-contract-sign-viewer.tsx')
-rw-r--r--lib/basic-contract/viewer/basic-contract-sign-viewer.tsx391
1 files changed, 265 insertions, 126 deletions
diff --git a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
index 49efb551..b92df089 100644
--- a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
+++ b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
@@ -150,12 +150,6 @@ class AutoSignatureFieldDetector {
console.log("πŸ“„ λ¬Έμ„œ 확인 μ™„λ£Œ, κΈ°μ‘΄ ν•„λ“œ 검사...");
- // βœ… 3단계: κΈ°μ‘΄ μ„œλͺ… ν•„λ“œ 확인 (μ•ˆμ „ν•œ 방법)
- const existingFields = await this.checkExistingFieldsSafely();
- if (existingFields.length > 0) {
- console.log(`βœ… κΈ°μ‘΄ μ„œλͺ… ν•„λ“œ 발견: ${existingFields.length}개`);
- return existingFields;
- }
// βœ… 4단계: λ‹¨μˆœ κΈ°λ³Έ μ„œλͺ… ν•„λ“œ 생성 (ν…μŠ€νŠΈ 뢄석 μŠ€ν‚΅)
console.log("πŸ“ κΈ°λ³Έ μ„œλͺ… ν•„λ“œ 생성...");
@@ -182,34 +176,7 @@ class AutoSignatureFieldDetector {
}
}
- // βœ… μ•ˆμ „ν•œ κΈ°μ‘΄ ν•„λ“œ 확인 (PDFDoc μ ‘κ·Ό μ•ˆν•¨)
- private async checkExistingFieldsSafely(): Promise<string[]> {
- try {
- const { annotationManager } = this.instance.Core;
- const annotations = annotationManager.getAnnotationsList();
-
- const signatureFields: string[] = [];
-
- for (const annotation of annotations) {
- try {
- if (annotation.getCustomData && annotation.getCustomData('fieldName')) {
- const fieldName = annotation.getCustomData('fieldName');
- if (fieldName.includes('signature') || fieldName.includes('μ„œλͺ…')) {
- signatureFields.push(fieldName);
- }
- }
- } catch (annotError) {
- // κ°œλ³„ μ–΄λ…Έν…Œμ΄μ…˜ μ—λŸ¬ λ¬΄μ‹œ
- continue;
- }
- }
-
- return signatureFields;
- } catch (error) {
- console.warn("κΈ°μ‘΄ ν•„λ“œ 확인 μ‹€νŒ¨ (λ¬΄μ‹œ):", error);
- return [];
- }
- }
+
// βœ… μ΄ˆκ°„λ‹¨ μ„œλͺ… ν•„λ“œ 생성 (λ³΅μž‘ν•œ ν…μŠ€νŠΈ 뢄석 없이)
private async createSimpleSignatureField(): Promise<string> {
@@ -229,28 +196,40 @@ class AutoSignatureFieldDetector {
// βœ… κ°„λ‹¨ν•œ μ„œλͺ… μ–΄λ…Έν…Œμ΄μ…˜ 생성 (PDFDoc μ ‘κ·Ό 없이)
const fieldName = `simple_signature_${Date.now()}`;
+
+ const flags = new Annotations.WidgetFlags();
+ // flags.set(Annotations.WidgetFlags.REQUIRED, true);
+ // flags.set(Annotations.WidgetFlags.READ_ONLY, true);
+
+ const formField = new Core.Annotations.Forms.Field(
+ `SignatureFormField`,
+ {
+ type: "Sig",
+ flags,
+ }
+ );
// μ„œλͺ… μœ„μ ― μ–΄λ…Έν…Œμ΄μ…˜ 생성
- const signatureWidget = new Annotations.SignatureWidgetAnnotation({
- appearance: Annotations.SignatureWidgetAnnotation.DefaultAppearance.MATERIAL_OUTLINE,
+ const signatureWidget = new Annotations.SignatureWidgetAnnotation(formField,{
+ // appearance: Annotations.SignatureWidgetAnnotation.DefaultAppearance.MATERIAL_OUTLINE,
Width: 150,
Height: 50
});
// μœ„μΉ˜ μ„€μ • (λ§ˆμ§€λ§‰ νŽ˜μ΄μ§€ ν•˜λ‹¨)
signatureWidget.setPageNumber(pageCount);
- signatureWidget.setX(pageWidth * 0.3);
- signatureWidget.setY(pageHeight * 0.15);
+ signatureWidget.setX(pageWidth * 0.7);
+ signatureWidget.setY(pageHeight * 0.85);
signatureWidget.setWidth(150);
signatureWidget.setHeight(50);
// ν•„λ“œλͺ… μ„€μ •
- signatureWidget.setFieldName(fieldName);
- signatureWidget.setCustomData('fieldName', fieldName);
+ // signatureWidget.setFieldName(fieldName);
+ // signatureWidget.setCustomData('fieldName', fieldName);
- // μŠ€νƒ€μΌ μ„€μ •
- signatureWidget.StrokeColor = new Annotations.Color(0, 100, 200); // νŒŒλž€μƒ‰
- signatureWidget.StrokeThickness = 2;
+ // // μŠ€νƒ€μΌ μ„€μ •
+ // signatureWidget.StrokeColor = new Annotations.Color(0, 100, 200); // νŒŒλž€μƒ‰
+ // signatureWidget.StrokeThickness = 2;
// μ–΄λ…Έν…Œμ΄μ…˜ μΆ”κ°€
annotationManager.addAnnotation(signatureWidget);
@@ -263,47 +242,47 @@ class AutoSignatureFieldDetector {
console.error("πŸ“› 간단 μ„œλͺ… ν•„λ“œ 생성 μ‹€νŒ¨:", error);
// βœ… μ΅œν›„μ˜ μˆ˜λ‹¨: ν…μŠ€νŠΈ μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μ•ˆλ‚΄
- return await this.createTextGuidance();
+ // return await this.createTextGuidance();
}
}
// βœ… μ΅œν›„μ˜ μˆ˜λ‹¨: ν…μŠ€νŠΈ μ•ˆλ‚΄ 생성
- private async createTextGuidance(): Promise<string> {
- try {
- const { Core } = this.instance;
- const { documentViewer, annotationManager, Annotations } = Core;
+ // private async createTextGuidance(): Promise<string> {
+ // try {
+ // const { Core } = this.instance;
+ // const { documentViewer, annotationManager, Annotations } = Core;
- const pageCount = documentViewer.getPageCount();
- const pageWidth = documentViewer.getPageWidth(pageCount) || 612;
- const pageHeight = documentViewer.getPageHeight(pageCount) || 792;
+ // const pageCount = documentViewer.getPageCount();
+ // const pageWidth = documentViewer.getPageWidth(pageCount) || 612;
+ // const pageHeight = documentViewer.getPageHeight(pageCount) || 792;
- // ν…μŠ€νŠΈ μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μ„œλͺ… μ•ˆλ‚΄
- const textAnnot = new Annotations.FreeTextAnnotation();
- textAnnot.setPageNumber(pageCount);
- textAnnot.setX(pageWidth * 0.25);
- textAnnot.setY(pageHeight * 0.1);
- textAnnot.setWidth(pageWidth * 0.5);
- textAnnot.setHeight(60);
- textAnnot.setContents("πŸ‘† μ—¬κΈ°λ₯Ό ν΄λ¦­ν•˜μ—¬ μ„œλͺ…ν•΄μ£Όμ„Έμš”");
- textAnnot.FontSize = '14pt';
- textAnnot.TextColor = new Annotations.Color(255, 0, 0); // 빨간색
- textAnnot.StrokeColor = new Annotations.Color(255, 200, 200);
- textAnnot.FillColor = new Annotations.Color(255, 240, 240);
+ // // ν…μŠ€νŠΈ μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μ„œλͺ… μ•ˆλ‚΄
+ // const textAnnot = new Annotations.FreeTextAnnotation();
+ // textAnnot.setPageNumber(pageCount);
+ // textAnnot.setX(pageWidth * 0.25);
+ // textAnnot.setY(pageHeight * 0.1);
+ // textAnnot.setWidth(pageWidth * 0.5);
+ // textAnnot.setHeight(60);
+ // textAnnot.setContents("πŸ‘† μ—¬κΈ°λ₯Ό ν΄λ¦­ν•˜μ—¬ μ„œλͺ…ν•΄μ£Όμ„Έμš”");
+ // textAnnot.FontSize = '14pt';
+ // textAnnot.TextColor = new Annotations.Color(255, 0, 0); // 빨간색
+ // textAnnot.StrokeColor = new Annotations.Color(255, 200, 200);
+ // textAnnot.FillColor = new Annotations.Color(255, 240, 240);
- const fieldName = `text_guidance_${Date.now()}`;
- textAnnot.setCustomData('fieldName', fieldName);
+ // const fieldName = `text_guidance_${Date.now()}`;
+ // textAnnot.setCustomData('fieldName', fieldName);
- annotationManager.addAnnotation(textAnnot);
- annotationManager.redrawAnnotation(textAnnot);
+ // annotationManager.addAnnotation(textAnnot);
+ // annotationManager.redrawAnnotation(textAnnot);
- console.log(`βœ… ν…μŠ€νŠΈ μ•ˆλ‚΄ 생성: ${fieldName}`);
- return fieldName;
+ // console.log(`βœ… ν…μŠ€νŠΈ μ•ˆλ‚΄ 생성: ${fieldName}`);
+ // return fieldName;
- } catch (error) {
- console.error("πŸ“› ν…μŠ€νŠΈ μ•ˆλ‚΄ 생성도 μ‹€νŒ¨:", error);
- return "manual_signature_required";
- }
- }
+ // } catch (error) {
+ // console.error("πŸ“› ν…μŠ€νŠΈ μ•ˆλ‚΄ 생성도 μ‹€νŒ¨:", error);
+ // return "manual_signature_required";
+ // }
+ // }
}
function useAutoSignatureFields(instance: WebViewerInstance | null) {
@@ -692,7 +671,10 @@ useEffect(() => {
{
path: "/pdftronWeb",
licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY,
- fullAPI: true
+ fullAPI: true ,
+ disabledElements: [
+
+ ]
},
viewerElement
).then((newInstance) => {
@@ -737,6 +719,19 @@ useEffect(() => {
newInstance.UI.setMinZoomLevel('25%');
newInstance.UI.setMaxZoomLevel('400%');
+
+ newInstance.UI.disableElements([
+ "toolbarGroup-Annotate",
+ "toolbarGroup-Shapes",
+ "toolbarGroup-Insert",
+ "toolbarGroup-Edit",
+ "toolbarGroup-FillAndSign",
+ "toolbarGroup-Forms",
+ "saveAsButton",
+ "downloadButton",
+
+ ])
+
documentViewer.addEventListener('documentLoadingError', (error) => {
console.error("πŸ“› WebViewer λ¬Έμ„œ λ‘œλ”© μ—λŸ¬:", error);
@@ -1359,7 +1354,7 @@ const SignatureFieldsStatus = () => {
);
};
-// 인라인 λ·°μ–΄ λ Œλ”λ§
+// 인라인 λ·°μ–΄ λ Œλ”λ§ λΆ€λΆ„ μˆ˜μ •
if (!isOpen && !onClose) {
return (
<div className="h-full w-full flex flex-col overflow-hidden">
@@ -1368,33 +1363,33 @@ if (!isOpen && !onClose) {
<div className="border-b bg-gray-50 px-3 py-2 flex-shrink-0">
<SignatureFieldsStatus />
<TabsList className="grid w-full h-8" style={{ gridTemplateColumns: `repeat(${allFiles.length}, 1fr)` }}>
- {allFiles.map((file, index) => {
- let tabId: string;
- if (index === 0) {
- tabId = 'main';
- } else if (file.type === 'survey') {
- tabId = 'survey';
- } else {
- const fileOnlyIndex = allFiles.slice(0, index).filter(f => f.type !== 'survey').length;
- tabId = `file-${fileOnlyIndex}`;
- }
-
- return (
- <TabsTrigger key={tabId} value={tabId} className="text-xs">
- <div className="flex items-center space-x-1">
- {file.type === 'survey' ? (
- <ClipboardList className="h-3 w-3" />
- ) : (
- <FileText className="h-3 w-3" />
- )}
- <span className="truncate">{file.name}</span>
- {file.type === 'survey' && surveyData.completed && (
- <Badge variant="secondary" className="ml-1 h-4 px-1 text-xs">μ™„λ£Œ</Badge>
- )}
- </div>
- </TabsTrigger>
- );
-})}
+ {allFiles.map((file, index) => {
+ let tabId: string;
+ if (index === 0) {
+ tabId = 'main';
+ } else if (file.type === 'survey') {
+ tabId = 'survey';
+ } else {
+ const fileOnlyIndex = allFiles.slice(0, index).filter(f => f.type !== 'survey').length;
+ tabId = `file-${fileOnlyIndex}`;
+ }
+
+ return (
+ <TabsTrigger key={tabId} value={tabId} className="text-xs">
+ <div className="flex items-center space-x-1">
+ {file.type === 'survey' ? (
+ <ClipboardList className="h-3 w-3" />
+ ) : (
+ <FileText className="h-3 w-3" />
+ )}
+ <span className="truncate">{file.name}</span>
+ {file.type === 'survey' && surveyData.completed && (
+ <Badge variant="secondary" className="ml-1 h-4 px-1 text-xs">μ™„λ£Œ</Badge>
+ )}
+ </div>
+ </TabsTrigger>
+ );
+ })}
</TabsList>
</div>
@@ -1408,37 +1403,55 @@ if (!isOpen && !onClose) {
<div
className={`absolute inset-0 ${activeTab !== 'survey' ? 'block' : 'hidden'}`}
>
- <div
- ref={viewer}
- className="w-full h-full"
- style={{ position: 'relative', minHeight: '400px' }}
- >
- {fileLoading && (
- <div className="absolute inset-0 flex flex-col items-center justify-center bg-white z-10">
- <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
- <p className="text-sm text-muted-foreground">λ¬Έμ„œ λ‘œλ”© 쀑...</p>
- </div>
- )}
+ {/* βœ… μˆ˜μ •: λ™μΌν•œ ꡬ쑰둜 ν†΅μΌν•˜κ³  슀크둀 ν™œμ„±ν™” */}
+ <div className="w-full h-full overflow-auto">
+ <div
+ ref={viewer}
+ className="w-full h-full min-h-[400px]"
+ style={{
+ position: 'relative',
+ // βœ… WebViewerκ°€ μŠ€ν¬λ‘€μ„ μ œμ–΄ν•˜λ„λ‘ μ„€μ •
+ overflow: 'visible'
+ }}
+ >
+ {fileLoading && (
+ <div className="absolute inset-0 flex flex-col items-center justify-center bg-white z-10">
+ <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
+ <p className="text-sm text-muted-foreground">λ¬Έμ„œ λ‘œλ”© 쀑...</p>
+ </div>
+ )}
+ </div>
</div>
</div>
</div>
</Tabs>
) : (
- <div className="h-full w-full relative">
- <div className="absolute top-2 left-2 z-10">
+ // βœ… μˆ˜μ •: Tabsκ°€ μ—†λŠ” κ²½μš°λ„ λ™μΌν•œ ꡬ쑰둜 λ³€κ²½
+ <div className="h-full w-full flex flex-col">
+ <div className="flex-shrink-0 p-2">
<SignatureFieldsStatus />
</div>
- <div
- ref={viewer}
- className="absolute inset-0"
- style={{ position: 'relative', minHeight: '400px' }}
- >
- {fileLoading && (
- <div className="absolute inset-0 flex flex-col items-center justify-center bg-white z-10">
- <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
- <p className="text-sm text-muted-foreground">λ¬Έμ„œ λ‘œλ”© 쀑...</p>
+ <div className="flex-1 min-h-0 overflow-hidden relative">
+ <div className="absolute inset-0">
+ <div className="w-full h-full overflow-auto">
+ <div
+ ref={viewer}
+ className="w-full h-full min-h-[400px]"
+ style={{
+ position: 'relative',
+ // βœ… WebViewerκ°€ μŠ€ν¬λ‘€μ„ μ œμ–΄ν•˜λ„λ‘ μ„€μ •
+ overflow: 'visible'
+ }}
+ >
+ {fileLoading && (
+ <div className="absolute inset-0 flex flex-col items-center justify-center bg-white z-10">
+ <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
+ <p className="text-sm text-muted-foreground">λ¬Έμ„œ λ‘œλ”© 쀑...</p>
+ </div>
+ )}
+ </div>
</div>
- )}
+ </div>
</div>
</div>
)}
@@ -1446,6 +1459,132 @@ if (!isOpen && !onClose) {
);
}
+// λ‹€μ΄μ–Όλ‘œκ·Έ λ·°μ–΄ λ Œλ”λ§ 뢀뢄도 λ™μΌν•˜κ²Œ μˆ˜μ •
+return (
+ <Dialog open={showDialog} onOpenChange={handleClose}>
+ <DialogContent className="w-[90vw] max-w-6xl h-[90vh] flex flex-col p-0">
+ <DialogHeader className="px-6 py-4 border-b flex-shrink-0">
+ <DialogTitle className="flex items-center justify-between">
+ <span>κΈ°λ³Έκ³„μ•½μ„œ μ„œλͺ…</span>
+ <SignatureFieldsStatus />
+ </DialogTitle>
+ <DialogDescription>
+ κ³„μ•½μ„œλ₯Ό ν™•μΈν•˜κ³  μ„œλͺ…을 μ§„ν–‰ν•΄μ£Όμ„Έμš”.
+ {isComplianceTemplate && (
+ <span className="block mt-1 text-amber-600">πŸ“‹ 쀀법 섀문쑰사λ₯Ό λ¨Όμ € μ™„λ£Œν•΄μ£Όμ„Έμš”.</span>
+ )}
+ {isNDATemplate && additionalFiles.length > 0 && (
+ <span className="block mt-1 text-blue-600">πŸ“Ž μ²¨λΆ€μ„œλ₯˜ {additionalFiles.length}개λ₯Ό 각 νƒ­μ—μ„œ ν™•μΈν•΄μ£Όμ„Έμš”.</span>
+ )}
+ {hasSignatureFields && (
+ <span className="block mt-1 text-green-600">
+ 🎯 μ„œλͺ… μœ„μΉ˜κ°€ μžλ™μœΌλ‘œ κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
+ {signatureFields.some(f => f.includes('_text')) && (
+ <span className="block text-sm text-amber-600">
+ πŸ’‘ 빨간색 ν…μŠ€νŠΈλ‘œ ν‘œμ‹œλœ μ˜μ—­μ„ μ°Ύμ•„ μ„œλͺ…ν•΄μ£Όμ„Έμš”.
+ </span>
+ )}
+ {signatureFields.some(f => f.startsWith('default_signature_')) && !signatureFields.some(f => f.includes('_text')) && (
+ <span className="block text-sm text-amber-600">
+ πŸ’‘ λ§ˆμ§€λ§‰ νŽ˜μ΄μ§€ ν•˜λ‹¨μ˜ 핑크색 μ˜μ—­μ—μ„œ μ„œλͺ…ν•΄μ£Όμ„Έμš”.
+ </span>
+ )}
+ </span>
+ )}
+ {autoSignError && (
+ <span className="block mt-1 text-red-600">⚠️ μžλ™ μ„œλͺ… ν•„λ“œ 생성 μ‹€νŒ¨ - μˆ˜λ™μœΌλ‘œ μ„œλͺ… μœ„μΉ˜λ₯Ό ν΄λ¦­ν•΄μ£Όμ„Έμš”.</span>
+ )}
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="flex-1 min-h-0 overflow-hidden">
+ {allFiles.length > 1 ? (
+ <Tabs value={activeTab} onValueChange={handleTabChange} className="h-full flex flex-col">
+ <div className="border-b bg-gray-50 px-3 py-2 flex-shrink-0">
+ <TabsList className="grid w-full h-8" style={{ gridTemplateColumns: `repeat(${allFiles.length}, 1fr)` }}>
+ {allFiles.map((file, index) => {
+ const tabId = index === 0 ? 'main' : file.type === 'survey' ? 'survey' : `file-${index}`;
+ return (
+ <TabsTrigger key={tabId} value={tabId} className="text-xs">
+ <div className="flex items-center space-x-1">
+ {file.type === 'survey' ? <ClipboardList className="h-3 w-3" /> : <FileText className="h-3 w-3" />}
+ <span className="truncate">{file.name}</span>
+ {file.type === 'survey' && surveyData.completed && (
+ <Badge variant="secondary" className="ml-1 h-4 px-1 text-xs">μ™„λ£Œ</Badge>
+ )}
+ </div>
+ </TabsTrigger>
+ );
+ })}
+ </TabsList>
+ </div>
+
+ <div className="flex-1 min-h-0 overflow-hidden relative">
+ <div className={`absolute inset-0 p-3 ${activeTab === 'survey' ? 'block' : 'hidden'}`}>
+ <SurveyComponent />
+ </div>
+
+ <div className={`absolute inset-0 ${activeTab !== 'survey' ? 'block' : 'hidden'}`}>
+ {/* βœ… μˆ˜μ •: 슀크둀 ν™œμ„±ν™” */}
+ <div className="w-full h-full overflow-auto">
+ <div
+ ref={viewer}
+ className="w-full h-full min-h-[400px]"
+ style={{
+ position: 'relative',
+ overflow: 'visible'
+ }}
+ >
+ {fileLoading && (
+ <div className="absolute inset-0 flex flex-col items-center justify-center bg-white z-10">
+ <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
+ <p className="text-sm text-muted-foreground">λ¬Έμ„œ λ‘œλ”© 쀑...</p>
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </Tabs>
+ ) : (
+ // βœ… μˆ˜μ •: λ‹€μ΄μ–Όλ‘œκ·Έμ—μ„œ λ·°μ–΄λ§Œ μžˆλŠ” κ²½μš°λ„ λ™μΌν•œ ꡬ쑰
+ <div className="h-full flex flex-col">
+ <div className="flex-1 min-h-0 overflow-hidden relative">
+ <div className="absolute inset-0">
+ <div className="w-full h-full overflow-auto">
+ <div
+ ref={viewer}
+ className="w-full h-full min-h-[400px]"
+ style={{
+ position: 'relative',
+ overflow: 'visible'
+ }}
+ >
+ {fileLoading && (
+ <div className="absolute inset-0 flex flex-col items-center justify-center bg-white z-10">
+ <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
+ <p className="text-sm text-muted-foreground">λ¬Έμ„œ λ‘œλ”© 쀑...</p>
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+
+ <DialogFooter className="px-6 py-4 border-t bg-white flex-shrink-0">
+ <Button variant="outline" onClick={handleClose} disabled={fileLoading}>μ·¨μ†Œ</Button>
+ <Button onClick={handleSave} disabled={fileLoading || isAutoSignProcessing}>
+ <FileSignature className="h-4 w-4 mr-2" />
+ μ„œλͺ… μ™„λ£Œ
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+);
+
// λ‹€μ΄μ–Όλ‘œκ·Έ λ·°μ–΄ λ Œλ”λ§
return (
<Dialog open={showDialog} onOpenChange={handleClose}>