diff options
Diffstat (limited to 'lib/basic-contract/viewer/basic-contract-sign-viewer.tsx')
| -rw-r--r-- | lib/basic-contract/viewer/basic-contract-sign-viewer.tsx | 391 |
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}> |
