diff options
| -rw-r--r-- | lib/basic-contract/template/basic-contract-template-viewer.tsx | 31 | ||||
| -rw-r--r-- | lib/basic-contract/template/template-editor-wrapper.tsx | 78 |
2 files changed, 90 insertions, 19 deletions
diff --git a/lib/basic-contract/template/basic-contract-template-viewer.tsx b/lib/basic-contract/template/basic-contract-template-viewer.tsx index 38438d33..59273dca 100644 --- a/lib/basic-contract/template/basic-contract-template-viewer.tsx +++ b/lib/basic-contract/template/basic-contract-template-viewer.tsx @@ -17,6 +17,7 @@ interface BasicContractTemplateViewerProps { instance: WebViewerInstance | null; setInstance: Dispatch<SetStateAction<WebViewerInstance | null>>; onSignatureFieldFound?: (searchText: string) => void; // 서명란 발견 시 콜백 + onLoadError?: (error: Error) => void; // 파일 로드 실패 시 콜백 } // 서명란 텍스트 찾기 함수 (변수 추가를 위해 사용) - 모든 서명란 텍스트를 찾아서 반환 @@ -77,8 +78,10 @@ export function BasicContractTemplateViewer({ instance, setInstance, onSignatureFieldFound, + onLoadError, }: BasicContractTemplateViewerProps) { const [fileLoading, setFileLoading] = useState<boolean>(true); + const [hasError, setHasError] = useState<boolean>(false); const viewer = useRef<HTMLDivElement>(null); const initialized = useRef(false); const isCancelled = useRef(false); @@ -234,6 +237,8 @@ export function BasicContractTemplateViewer({ // 한글 지원 Office 문서 로드 const loadDocument = async (instance: WebViewerInstance, documentPath: string) => { setFileLoading(true); + setHasError(false); + try { // 절대 URL로 변환 const fullPath = documentPath.startsWith('http') @@ -246,6 +251,21 @@ export function BasicContractTemplateViewer({ console.log("📄 한글 지원 Office 문서 로드 시작:", fullPath); console.log("📎 파일명:", fileName); + // 파일 존재 여부 먼저 확인 + try { + const response = await fetch(fullPath, { method: 'HEAD' }); + if (!response.ok) { + throw new Error(`파일을 찾을 수 없습니다 (${response.status})`); + } + } catch (fetchError) { + const error = new Error('파일이 존재하지 않거나 접근할 수 없습니다.'); + setHasError(true); + if (onLoadError) { + onLoadError(error); + } + throw error; + } + // PDFTron 공식 방법: instance.UI.loadDocument() + 한글 지원 옵션 await instance.UI.loadDocument(fullPath, { filename: fileName, @@ -283,7 +303,14 @@ export function BasicContractTemplateViewer({ } catch (err) { console.error("❌ Office 문서 로딩 중 오류:", err); - toast.error(`Office 문서 로드 실패: ${err instanceof Error ? err.message : '알 수 없는 오류'}`); + const errorMessage = err instanceof Error ? err.message : '알 수 없는 오류'; + + setHasError(true); + if (onLoadError) { + onLoadError(err instanceof Error ? err : new Error(errorMessage)); + } + + toast.error(`Office 문서 로드 실패: ${errorMessage}`); } finally { setFileLoading(false); } @@ -301,7 +328,7 @@ export function BasicContractTemplateViewer({ contain: 'layout style paint', // CSS containment로 격리 }} > - {fileLoading && ( + {fileLoading && !hasError && ( <div className="absolute inset-0 flex flex-col items-center justify-center bg-white bg-opacity-90 z-10"> <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" /> <p className="text-sm text-muted-foreground">문서 로딩 중...</p> diff --git a/lib/basic-contract/template/template-editor-wrapper.tsx b/lib/basic-contract/template/template-editor-wrapper.tsx index 8d2ce97f..be5e6856 100644 --- a/lib/basic-contract/template/template-editor-wrapper.tsx +++ b/lib/basic-contract/template/template-editor-wrapper.tsx @@ -102,6 +102,7 @@ export function TemplateEditorWrapper({ const [templateName, setTemplateName] = React.useState<string>(""); const [predefinedVariables, setPredefinedVariables] = React.useState<string[]>([]); const [signatureVariables, setSignatureVariables] = React.useState<string[]>([]); // 서명란 변수 + const [fileLoadError, setFileLoadError] = React.useState<Error | null>(null); // 파일 로드 오류 // 템플릿 이름 로드 및 변수 설정 React.useEffect(() => { @@ -503,23 +504,66 @@ export function TemplateEditorWrapper({ {/* 뷰어 영역 (확대 문제 해결을 위한 컨테이너 격리) */} <div className="flex-1 relative overflow-hidden"> - <div className="absolute inset-0"> - <BasicContractTemplateViewer - templateId={templateId} - filePath={filePath} - instance={instance} - setInstance={setInstance} - onSignatureFieldFound={(searchText) => { - // 서명란 발견 시 원본 텍스트를 그대로 추가 (자동 서명 기능을 위해) - setSignatureVariables(prev => { - if (!prev.includes(searchText)) { - return [...prev, searchText]; - } - return prev; - }); - }} - /> - </div> + {fileLoadError ? ( + <div className="absolute inset-0 flex flex-col items-center justify-center bg-gray-50 p-8"> + <div className="max-w-md text-center space-y-4"> + <AlertCircle className="h-16 w-16 text-red-500 mx-auto" /> + <div> + <h3 className="text-lg font-semibold text-gray-900">파일을 불러올 수 없습니다</h3> + <p className="text-sm text-gray-600 mt-2"> + {fileLoadError.message} + </p> + </div> + <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 text-left"> + <p className="text-sm text-yellow-800"> + <strong>가능한 원인:</strong> + </p> + <ul className="text-sm text-yellow-700 mt-2 space-y-1 list-disc list-inside"> + <li>템플릿이 폐기되어 파일이 삭제되었습니다</li> + <li>템플릿이 DB에서 삭제되었습니다</li> + <li>파일 경로가 변경되었거나 손상되었습니다</li> + </ul> + </div> + <div className="flex gap-2 justify-center"> + <Button + variant="outline" + onClick={() => window.history.back()} + > + 목록으로 돌아가기 + </Button> + <Button + onClick={() => { + setFileLoadError(null); + window.location.reload(); + }} + > + 새로고침 + </Button> + </div> + </div> + </div> + ) : ( + <div className="absolute inset-0"> + <BasicContractTemplateViewer + templateId={templateId} + filePath={filePath} + instance={instance} + setInstance={setInstance} + onSignatureFieldFound={(searchText) => { + // 서명란 발견 시 원본 텍스트를 그대로 추가 (자동 서명 기능을 위해) + setSignatureVariables(prev => { + if (!prev.includes(searchText)) { + return [...prev, searchText]; + } + return prev; + }); + }} + onLoadError={(error) => { + setFileLoadError(error); + }} + /> + </div> + )} </div> {/* 하단 안내 (한글 입력 팁 포함) */} |
