From d861eb5196773cb9c3610730f88fc850f2ada073 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Mon, 29 Sep 2025 19:32:20 +0900 Subject: [PATCH] Fix modal label display issues and DOM node removal errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hide rounded background labels in modal (계약구분, 국내/해외, 기본 버튼) - Add try-catch blocks for DOM operations to prevent removeChild errors - Fix event listener registration/removal in RealtimePreview, FileUpload, FileComponentConfigPanel - Improve error handling for CustomEvent dispatching --- frontend/components/screen/EditModal.tsx | 10 +- .../screen/InteractiveScreenViewer.tsx | 20 ++-- .../screen/InteractiveScreenViewerDynamic.tsx | 14 +-- .../components/screen/RealtimePreview.tsx | 20 +++- .../panels/FileComponentConfigPanel.tsx | 110 ++++++++++-------- .../components/screen/widgets/FileUpload.tsx | 76 +++++++----- frontend/components/ui/select.tsx | 4 +- 7 files changed, 142 insertions(+), 112 deletions(-) diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index b0cc0c2f..bbd06f58 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -245,6 +245,7 @@ export const EditModal: React.FC = ({ maxHeight: "95vh", zIndex: 9999, // 모든 컴포넌트보다 위에 표시 }} + data-radix-portal="true" > 수정 @@ -271,16 +272,17 @@ export const EditModal: React.FC = ({ > {/* 화면 컴포넌트들 원본 레이아웃 유지하여 렌더링 */}
- {components.map((component) => ( + {components.map((component, index) => (
{/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시를 위해) */} @@ -288,7 +290,7 @@ export const EditModal: React.FC = ({ { console.log("📝 폼 데이터 변경:", fieldName, value); @@ -312,7 +314,7 @@ export const EditModal: React.FC = ({ ...component, style: { ...component.style, - labelDisplay: true, // 수정 모달에서는 라벨 강제 표시 + labelDisplay: false, // 라벨 숨김 (원래 화면과 동일하게) }, }} screenId={screenId} diff --git a/frontend/components/screen/InteractiveScreenViewer.tsx b/frontend/components/screen/InteractiveScreenViewer.tsx index 2b2e2b52..aeadcc1f 100644 --- a/frontend/components/screen/InteractiveScreenViewer.tsx +++ b/frontend/components/screen/InteractiveScreenViewer.tsx @@ -1726,19 +1726,19 @@ export const InteractiveScreenViewer: React.FC = ( return ( <> -
- {/* 라벨이 있는 경우 표시 (데이터 테이블 제외) */} - {shouldShowLabel && ( -
-
- {labelText} - {component.required && *} +
+ {/* 라벨이 있는 경우 표시 (데이터 테이블 제외) */} + {shouldShowLabel && ( +
+
+ {labelText} + {component.required && *} +
-
- )} + )} {/* 실제 위젯 */} -
{renderInteractiveWidget(component)}
+
{renderInteractiveWidget(component)}
{/* 개선된 검증 패널 (선택적 표시) */} diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index e182afa2..8212f0d0 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -532,19 +532,11 @@ export const InteractiveScreenViewerDynamic: React.FC
-
- {/* 라벨 표시 - 컴포넌트 내부에서 라벨을 처리하므로 외부에서는 표시하지 않음 */} - {!hideLabel && component.label && component.style?.labelDisplay === false && ( -
-
- {component.label} - {(component as WidgetComponent).required && *} -
-
- )} +
+ {/* 라벨 숨김 - 모달에서는 라벨을 표시하지 않음 */} {/* 위젯 렌더링 */} -
{renderInteractiveWidget(component)}
+
{renderInteractiveWidget(component)}
diff --git a/frontend/components/screen/RealtimePreview.tsx b/frontend/components/screen/RealtimePreview.tsx index 9ed0c2e9..82e1db47 100644 --- a/frontend/components/screen/RealtimePreview.tsx +++ b/frontend/components/screen/RealtimePreview.tsx @@ -292,15 +292,23 @@ export const RealtimePreviewDynamic: React.FC = ({ }; if (typeof window !== 'undefined') { - window.addEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener); - - // 전역 강제 업데이트 함수 등록 - if (!(window as any).forceRealtimePreviewUpdate) { - (window as any).forceRealtimePreviewUpdate = forceUpdate; + try { + window.addEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener); + + // 전역 강제 업데이트 함수 등록 + if (!(window as any).forceRealtimePreviewUpdate) { + (window as any).forceRealtimePreviewUpdate = forceUpdate; + } + } catch (error) { + console.warn("RealtimePreview 이벤트 리스너 등록 실패:", error); } return () => { - window.removeEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener); + try { + window.removeEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener); + } catch (error) { + console.warn("RealtimePreview 이벤트 리스너 제거 실패:", error); + } }; } }, [component.id, fileUpdateTrigger]); diff --git a/frontend/components/screen/panels/FileComponentConfigPanel.tsx b/frontend/components/screen/panels/FileComponentConfigPanel.tsx index 91057fdb..5f2d9b95 100644 --- a/frontend/components/screen/panels/FileComponentConfigPanel.tsx +++ b/frontend/components/screen/panels/FileComponentConfigPanel.tsx @@ -600,55 +600,71 @@ export const FileComponentConfigPanel: React.FC = // 🎯 RealtimePreview 동기화를 위한 전역 이벤트 발생 if (typeof window !== 'undefined') { - const eventDetail = { - componentId: component.id, - files: updatedFiles, - fileCount: updatedFiles.length, - action: 'delete', - timestamp: timestamp, - source: 'designMode' // 🎯 화면설계 모드에서 온 이벤트임을 표시 - }; - - console.log("🚀🚀🚀 FileComponentConfigPanel 삭제 이벤트 발생:", eventDetail); - - const event = new CustomEvent('globalFileStateChanged', { - detail: eventDetail - }); - window.dispatchEvent(event); - - console.log("✅✅✅ globalFileStateChanged 삭제 이벤트 발생 완료"); - - // 추가 지연 이벤트들 - setTimeout(() => { - console.log("🔄 추가 삭제 이벤트 발생 (지연 100ms)"); - window.dispatchEvent(new CustomEvent('globalFileStateChanged', { - detail: { ...eventDetail, delayed: true } - })); - }, 100); - - setTimeout(() => { - console.log("🔄 추가 삭제 이벤트 발생 (지연 300ms)"); - window.dispatchEvent(new CustomEvent('globalFileStateChanged', { - detail: { ...eventDetail, delayed: true, attempt: 2 } - })); - }, 300); + try { + const eventDetail = { + componentId: component.id, + files: updatedFiles, + fileCount: updatedFiles.length, + action: 'delete', + timestamp: timestamp, + source: 'designMode' // 🎯 화면설계 모드에서 온 이벤트임을 표시 + }; + + console.log("🚀🚀🚀 FileComponentConfigPanel 삭제 이벤트 발생:", eventDetail); + + const event = new CustomEvent('globalFileStateChanged', { + detail: eventDetail + }); + window.dispatchEvent(event); + + console.log("✅✅✅ globalFileStateChanged 삭제 이벤트 발생 완료"); + + // 추가 지연 이벤트들 + setTimeout(() => { + try { + console.log("🔄 추가 삭제 이벤트 발생 (지연 100ms)"); + window.dispatchEvent(new CustomEvent('globalFileStateChanged', { + detail: { ...eventDetail, delayed: true } + })); + } catch (error) { + console.warn("FileComponentConfigPanel 지연 이벤트 발생 실패:", error); + } + }, 100); + + setTimeout(() => { + try { + console.log("🔄 추가 삭제 이벤트 발생 (지연 300ms)"); + window.dispatchEvent(new CustomEvent('globalFileStateChanged', { + detail: { ...eventDetail, delayed: true, attempt: 2 } + })); + } catch (error) { + console.warn("FileComponentConfigPanel 지연 이벤트 발생 실패:", error); + } + }, 300); + } catch (error) { + console.warn("FileComponentConfigPanel 이벤트 발생 실패:", error); + } // 그리드 파일 상태 새로고침 이벤트도 유지 - const tableName = currentTableName || 'screen_files'; - const recordId = component.id; - const columnName = component.columnName || component.id || 'file_attachment'; - const targetObjid = `${tableName}:${recordId}:${columnName}`; - - const refreshEvent = new CustomEvent('refreshFileStatus', { - detail: { - tableName: tableName, - recordId: recordId, - columnName: columnName, - targetObjid: targetObjid, - fileCount: updatedFiles.length - } - }); - window.dispatchEvent(refreshEvent); + try { + const tableName = currentTableName || 'screen_files'; + const recordId = component.id; + const columnName = component.columnName || component.id || 'file_attachment'; + const targetObjid = `${tableName}:${recordId}:${columnName}`; + + const refreshEvent = new CustomEvent('refreshFileStatus', { + detail: { + tableName: tableName, + recordId: recordId, + columnName: columnName, + targetObjid: targetObjid, + fileCount: updatedFiles.length + } + }); + window.dispatchEvent(refreshEvent); + } catch (error) { + console.warn("FileComponentConfigPanel refreshFileStatus 이벤트 발생 실패:", error); + } console.log("🔄 FileComponentConfigPanel 파일 삭제 후 그리드 새로고침:", { tableName, recordId, diff --git a/frontend/components/screen/widgets/FileUpload.tsx b/frontend/components/screen/widgets/FileUpload.tsx index 2bddb598..d7510675 100644 --- a/frontend/components/screen/widgets/FileUpload.tsx +++ b/frontend/components/screen/widgets/FileUpload.tsx @@ -605,38 +605,50 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf // 🎯 화면설계 모드와 동기화를 위한 전역 이벤트 발생 if (typeof window !== 'undefined') { - const eventDetail = { - componentId: component.id, - files: filteredFiles, - fileCount: filteredFiles.length, - action: 'delete', - timestamp: Date.now(), - source: 'realScreen' // 실제 화면에서 온 이벤트임을 표시 - }; - - console.log("🚀🚀🚀 FileUpload 위젯 삭제 이벤트 발생:", eventDetail); - - const event = new CustomEvent('globalFileStateChanged', { - detail: eventDetail - }); - window.dispatchEvent(event); - - console.log("✅✅✅ FileUpload 위젯 → 화면설계 모드 동기화 이벤트 발생 완료"); - - // 추가 지연 이벤트들 - setTimeout(() => { - console.log("🔄 FileUpload 위젯 추가 삭제 이벤트 발생 (지연 100ms)"); - window.dispatchEvent(new CustomEvent('globalFileStateChanged', { - detail: { ...eventDetail, delayed: true } - })); - }, 100); - - setTimeout(() => { - console.log("🔄 FileUpload 위젯 추가 삭제 이벤트 발생 (지연 300ms)"); - window.dispatchEvent(new CustomEvent('globalFileStateChanged', { - detail: { ...eventDetail, delayed: true, attempt: 2 } - })); - }, 300); + try { + const eventDetail = { + componentId: component.id, + files: filteredFiles, + fileCount: filteredFiles.length, + action: 'delete', + timestamp: Date.now(), + source: 'realScreen' // 실제 화면에서 온 이벤트임을 표시 + }; + + console.log("🚀🚀🚀 FileUpload 위젯 삭제 이벤트 발생:", eventDetail); + + const event = new CustomEvent('globalFileStateChanged', { + detail: eventDetail + }); + window.dispatchEvent(event); + + console.log("✅✅✅ FileUpload 위젯 → 화면설계 모드 동기화 이벤트 발생 완료"); + + // 추가 지연 이벤트들 + setTimeout(() => { + try { + console.log("🔄 FileUpload 위젯 추가 삭제 이벤트 발생 (지연 100ms)"); + window.dispatchEvent(new CustomEvent('globalFileStateChanged', { + detail: { ...eventDetail, delayed: true } + })); + } catch (error) { + console.warn("FileUpload 지연 이벤트 발생 실패:", error); + } + }, 100); + + setTimeout(() => { + try { + console.log("🔄 FileUpload 위젯 추가 삭제 이벤트 발생 (지연 300ms)"); + window.dispatchEvent(new CustomEvent('globalFileStateChanged', { + detail: { ...eventDetail, delayed: true, attempt: 2 } + })); + } catch (error) { + console.warn("FileUpload 지연 이벤트 발생 실패:", error); + } + }, 300); + } catch (error) { + console.warn("FileUpload 이벤트 발생 실패:", error); + } } onUpdateComponent({ diff --git a/frontend/components/ui/select.tsx b/frontend/components/ui/select.tsx index 980e27a1..b4b2d24c 100644 --- a/frontend/components/ui/select.tsx +++ b/frontend/components/ui/select.tsx @@ -51,11 +51,11 @@ function SelectContent({ ...props }: React.ComponentProps) { return ( - +