Fix modal label display issues and DOM node removal errors
- 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
This commit is contained in:
parent
d0d37d9e29
commit
d861eb5196
|
|
@ -245,6 +245,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
|||
maxHeight: "95vh",
|
||||
zIndex: 9999, // 모든 컴포넌트보다 위에 표시
|
||||
}}
|
||||
data-radix-portal="true"
|
||||
>
|
||||
<DialogHeader className="sr-only">
|
||||
<DialogTitle>수정</DialogTitle>
|
||||
|
|
@ -271,16 +272,17 @@ export const EditModal: React.FC<EditModalProps> = ({
|
|||
>
|
||||
{/* 화면 컴포넌트들 원본 레이아웃 유지하여 렌더링 */}
|
||||
<div className="relative" style={{ minHeight: "300px" }}>
|
||||
{components.map((component) => (
|
||||
{components.map((component, index) => (
|
||||
<div
|
||||
key={component.id}
|
||||
className="rounded-xl border border-gray-200/60 bg-gradient-to-br from-white to-gray-50/30 p-4 shadow-sm transition-all duration-200 hover:shadow-md"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: component.position?.y || 0,
|
||||
left: component.position?.x || 0,
|
||||
width: component.size?.width || 200,
|
||||
height: component.size?.height || 40,
|
||||
zIndex: 1,
|
||||
zIndex: component.position?.z || (1000 + index), // 모달 내부에서 충분히 높은 z-index
|
||||
}}
|
||||
>
|
||||
{/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시를 위해) */}
|
||||
|
|
@ -288,7 +290,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
|||
<InteractiveScreenViewer
|
||||
component={component}
|
||||
allComponents={components}
|
||||
hideLabel={false} // 라벨 표시 활성화
|
||||
hideLabel={true} // 라벨 숨김 (원래 화면과 동일하게)
|
||||
formData={formData}
|
||||
onFormDataChange={(fieldName, value) => {
|
||||
console.log("📝 폼 데이터 변경:", fieldName, value);
|
||||
|
|
@ -312,7 +314,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
|||
...component,
|
||||
style: {
|
||||
...component.style,
|
||||
labelDisplay: true, // 수정 모달에서는 라벨 강제 표시
|
||||
labelDisplay: false, // 라벨 숨김 (원래 화면과 동일하게)
|
||||
},
|
||||
}}
|
||||
screenId={screenId}
|
||||
|
|
|
|||
|
|
@ -1726,19 +1726,19 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full w-full rounded-xl border border-gray-200/60 bg-gradient-to-br from-white to-gray-50/30 p-4 shadow-sm transition-all duration-200 hover:shadow-md">
|
||||
{/* 라벨이 있는 경우 표시 (데이터 테이블 제외) */}
|
||||
{shouldShowLabel && (
|
||||
<div className="block mb-3" style={labelStyle}>
|
||||
<div className="inline-flex items-center bg-gray-100 px-3 py-1 rounded-lg text-sm font-semibold">
|
||||
{labelText}
|
||||
{component.required && <span style={{ color: "#f97316", marginLeft: "4px" }}>*</span>}
|
||||
<div className="h-full w-full">
|
||||
{/* 라벨이 있는 경우 표시 (데이터 테이블 제외) */}
|
||||
{shouldShowLabel && (
|
||||
<div className="block mb-3" style={labelStyle}>
|
||||
<div className="inline-flex items-center bg-gray-100 px-3 py-1 rounded-lg text-sm font-semibold">
|
||||
{labelText}
|
||||
{component.required && <span style={{ color: "#f97316", marginLeft: "4px" }}>*</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* 실제 위젯 */}
|
||||
<div className="flex-1 rounded-lg overflow-hidden">{renderInteractiveWidget(component)}</div>
|
||||
<div className="h-full w-full">{renderInteractiveWidget(component)}</div>
|
||||
</div>
|
||||
|
||||
{/* 개선된 검증 패널 (선택적 표시) */}
|
||||
|
|
|
|||
|
|
@ -532,19 +532,11 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
return (
|
||||
<>
|
||||
<div className="absolute" style={componentStyle}>
|
||||
<div className="h-full w-full rounded-xl border border-gray-200/60 bg-gradient-to-br from-white to-gray-50/30 p-4 shadow-sm transition-all duration-200 hover:shadow-md">
|
||||
{/* 라벨 표시 - 컴포넌트 내부에서 라벨을 처리하므로 외부에서는 표시하지 않음 */}
|
||||
{!hideLabel && component.label && component.style?.labelDisplay === false && (
|
||||
<div className="mb-3">
|
||||
<div className="inline-flex items-center bg-gray-100 px-3 py-1 rounded-lg text-sm font-semibold text-gray-700">
|
||||
{component.label}
|
||||
{(component as WidgetComponent).required && <span className="ml-1 text-orange-500">*</span>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-full w-full">
|
||||
{/* 라벨 숨김 - 모달에서는 라벨을 표시하지 않음 */}
|
||||
|
||||
{/* 위젯 렌더링 */}
|
||||
<div className="flex-1 rounded-lg overflow-hidden">{renderInteractiveWidget(component)}</div>
|
||||
<div className="h-full w-full">{renderInteractiveWidget(component)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -292,15 +292,23 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
|||
};
|
||||
|
||||
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]);
|
||||
|
|
|
|||
|
|
@ -600,55 +600,71 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
|||
|
||||
// 🎯 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,
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ function SelectContent({
|
|||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Portal container={document.querySelector('[data-radix-portal]') || document.body}>
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-[99999] max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-[10000] max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className,
|
||||
|
|
|
|||
Loading…
Reference in New Issue