summaryrefslogtreecommitdiff
path: root/lib/basic-contract/viewer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract/viewer')
-rw-r--r--lib/basic-contract/viewer/basic-contract-sign-viewer.tsx224
1 files changed, 224 insertions, 0 deletions
diff --git a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
new file mode 100644
index 00000000..0409151e
--- /dev/null
+++ b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
@@ -0,0 +1,224 @@
+"use client";
+
+import React, {
+ useState,
+ useEffect,
+ useRef,
+ SetStateAction,
+ Dispatch,
+} from "react";
+import { WebViewerInstance } from "@pdftron/webviewer";
+import { Loader2 } from "lucide-react";
+import { toast } from "sonner";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+
+interface BasicContractSignViewerProps {
+ contractId?: number;
+ filePath?: string;
+ isOpen?: boolean;
+ onClose?: () => void;
+ onSign?: (documentData: ArrayBuffer) => Promise<void>;
+ instance: WebViewerInstance | null;
+ setInstance: Dispatch<SetStateAction<WebViewerInstance | null>>;
+}
+
+export function BasicContractSignViewer({
+ contractId,
+ filePath,
+ isOpen = false,
+ onClose,
+ onSign,
+ instance,
+ setInstance,
+}: BasicContractSignViewerProps) {
+ const [fileLoading, setFileLoading] = useState<boolean>(true);
+ const viewer = useRef<HTMLDivElement>(null);
+ const initialized = useRef(false);
+ const isCancelled = useRef(false);
+ const [showDialog, setShowDialog] = useState(isOpen);
+
+ // 다이얼로그 상태 동기화
+ useEffect(() => {
+ setShowDialog(isOpen);
+ }, [isOpen]);
+
+ // WebViewer 초기화
+ useEffect(() => {
+ if (!initialized.current && viewer.current) {
+ initialized.current = true;
+ isCancelled.current = false;
+
+ requestAnimationFrame(() => {
+ if (viewer.current) {
+ import("@pdftron/webviewer").then(({ default: WebViewer }) => {
+ if (isCancelled.current) {
+ console.log("📛 WebViewer 초기화 취소됨");
+ return;
+ }
+
+ // viewerElement이 확실히 존재함을 확인
+ const viewerElement = viewer.current;
+ if (!viewerElement) return;
+
+ WebViewer(
+ {
+ path: "/pdftronWeb",
+ licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY,
+ fullAPI: true,
+ },
+ viewerElement
+ ).then((instance: WebViewerInstance) => {
+ setInstance(instance);
+ setFileLoading(false);
+
+ const { disableElements, setToolbarGroup } = instance.UI;
+
+ disableElements([
+ "toolbarGroup-Annotate",
+ "toolbarGroup-Shapes",
+ "toolbarGroup-Insert",
+ "toolbarGroup-Edit",
+ // "toolbarGroup-FillAndSign",
+ "toolbarGroup-Forms",
+ ]);
+ setToolbarGroup("toolbarGroup-View");
+ });
+ });
+ }
+ });
+ }
+
+ return () => {
+ if (instance) {
+ instance.UI.dispose();
+ }
+ isCancelled.current = true;
+ setTimeout(() => cleanupHtmlStyle(), 500);
+ };
+ }, []);
+
+ // 문서 로드
+ useEffect(() => {
+ if (!instance || !filePath) return;
+
+ loadDocument(instance, filePath);
+ }, [instance, filePath]);
+
+ // 간소화된 문서 로드 함수
+ const loadDocument = async (instance: WebViewerInstance, documentPath: string) => {
+ setFileLoading(true);
+ try {
+ const { documentViewer } = instance.Core;
+
+ await documentViewer.loadDocument(documentPath, { extension: 'pdf' });
+
+ } catch (err) {
+ console.error("문서 로딩 중 오류 발생:", err);
+ toast.error("문서를 불러오는데 실패했습니다.");
+ } finally {
+ setFileLoading(false);
+ }
+ };
+
+ // 서명 저장 핸들러
+ const handleSave = async () => {
+ if (!instance) return;
+
+ try {
+ const { documentViewer } = instance.Core;
+ const doc = documentViewer.getDocument();
+
+ // 서명된 문서 데이터 가져오기
+ const documentData = await doc.getFileData({
+ includeAnnotations: true,
+ });
+
+ // 외부에서 제공된 onSign 핸들러가 있으면 호출
+ if (onSign) {
+ await onSign(documentData);
+ } else {
+ // 기본 동작 - 서명 성공 메시지 표시
+ toast.success("계약서가 성공적으로 서명되었습니다.");
+ }
+
+ handleClose();
+ } catch (err) {
+ console.error("서명 저장 중 오류 발생:", err);
+ toast.error("서명을 저장하는데 실패했습니다.");
+ }
+ };
+
+ // 다이얼로그 닫기 핸들러
+ const handleClose = () => {
+ if (onClose) {
+ onClose();
+ } else {
+ setShowDialog(false);
+ }
+ };
+
+ // 인라인 뷰어 렌더링 (다이얼로그 모드가 아닐 때)
+ if (!isOpen && !onClose) {
+ return (
+ <div className="border rounded-md overflow-hidden" style={{ height: '600px' }}>
+ <div ref={viewer} className="h-[100%]">
+ {fileLoading && (
+ <div className="flex flex-col items-center justify-center py-12">
+ <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
+ <p className="text-sm text-muted-foreground">문서 로딩 중...</p>
+ </div>
+ )}
+ </div>
+ </div>
+ );
+ }
+
+ // 다이얼로그 뷰어 렌더링
+ return (
+ <Dialog open={showDialog} onOpenChange={handleClose}>
+ <DialogContent className="w-[70vw]" style={{ maxWidth: "none" }}>
+ <DialogHeader>
+ <DialogTitle>기본계약서 서명</DialogTitle>
+ <DialogDescription>
+ 계약서를 확인하고 서명을 진행해주세요.
+ </DialogDescription>
+ </DialogHeader>
+ <div className="h-[calc(70vh-60px)]">
+ <div ref={viewer} className="h-[100%]">
+ {fileLoading && (
+ <div className="flex flex-col items-center justify-center py-12">
+ <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
+ <p className="text-sm text-muted-foreground">문서 로딩 중...</p>
+ </div>
+ )}
+ </div>
+ </div>
+ <DialogFooter>
+ <Button variant="outline" onClick={handleClose} disabled={fileLoading}>
+ 취소
+ </Button>
+ <Button onClick={handleSave} disabled={fileLoading}>
+ 서명 완료
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ );
+}
+
+// WebViewer 정리 함수
+const cleanupHtmlStyle = () => {
+ // iframe 스타일 정리 (WebViewer가 추가한 스타일)
+ const elements = document.querySelectorAll('.Document_container');
+ elements.forEach((elem) => {
+ elem.remove();
+ });
+}; \ No newline at end of file