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
0b787b4c4c
|
|
@ -245,6 +245,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
maxHeight: "95vh",
|
maxHeight: "95vh",
|
||||||
zIndex: 9999, // 모든 컴포넌트보다 위에 표시
|
zIndex: 9999, // 모든 컴포넌트보다 위에 표시
|
||||||
}}
|
}}
|
||||||
|
data-radix-portal="true"
|
||||||
>
|
>
|
||||||
<DialogHeader className="sr-only">
|
<DialogHeader className="sr-only">
|
||||||
<DialogTitle>수정</DialogTitle>
|
<DialogTitle>수정</DialogTitle>
|
||||||
|
|
@ -271,16 +272,17 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
>
|
>
|
||||||
{/* 화면 컴포넌트들 원본 레이아웃 유지하여 렌더링 */}
|
{/* 화면 컴포넌트들 원본 레이아웃 유지하여 렌더링 */}
|
||||||
<div className="relative" style={{ minHeight: "300px" }}>
|
<div className="relative" style={{ minHeight: "300px" }}>
|
||||||
{components.map((component) => (
|
{components.map((component, index) => (
|
||||||
<div
|
<div
|
||||||
key={component.id}
|
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={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: component.position?.y || 0,
|
top: component.position?.y || 0,
|
||||||
left: component.position?.x || 0,
|
left: component.position?.x || 0,
|
||||||
width: component.size?.width || 200,
|
width: component.size?.width || 200,
|
||||||
height: component.size?.height || 40,
|
height: component.size?.height || 40,
|
||||||
zIndex: 1,
|
zIndex: component.position?.z || (1000 + index), // 모달 내부에서 충분히 높은 z-index
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시를 위해) */}
|
{/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시를 위해) */}
|
||||||
|
|
@ -288,7 +290,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
<InteractiveScreenViewer
|
<InteractiveScreenViewer
|
||||||
component={component}
|
component={component}
|
||||||
allComponents={components}
|
allComponents={components}
|
||||||
hideLabel={false} // 라벨 표시 활성화
|
hideLabel={true} // 라벨 숨김 (원래 화면과 동일하게)
|
||||||
formData={formData}
|
formData={formData}
|
||||||
onFormDataChange={(fieldName, value) => {
|
onFormDataChange={(fieldName, value) => {
|
||||||
console.log("📝 폼 데이터 변경:", fieldName, value);
|
console.log("📝 폼 데이터 변경:", fieldName, value);
|
||||||
|
|
@ -312,7 +314,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
...component,
|
...component,
|
||||||
style: {
|
style: {
|
||||||
...component.style,
|
...component.style,
|
||||||
labelDisplay: true, // 수정 모달에서는 라벨 강제 표시
|
labelDisplay: false, // 라벨 숨김 (원래 화면과 동일하게)
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
screenId={screenId}
|
screenId={screenId}
|
||||||
|
|
|
||||||
|
|
@ -1726,19 +1726,19 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
|
|
||||||
return (
|
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">
|
<div className="h-full w-full">
|
||||||
{/* 라벨이 있는 경우 표시 (데이터 테이블 제외) */}
|
{/* 라벨이 있는 경우 표시 (데이터 테이블 제외) */}
|
||||||
{shouldShowLabel && (
|
{shouldShowLabel && (
|
||||||
<div className="block mb-3" style={labelStyle}>
|
<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">
|
<div className="inline-flex items-center bg-gray-100 px-3 py-1 rounded-lg text-sm font-semibold">
|
||||||
{labelText}
|
{labelText}
|
||||||
{component.required && <span style={{ color: "#f97316", marginLeft: "4px" }}>*</span>}
|
{component.required && <span style={{ color: "#f97316", marginLeft: "4px" }}>*</span>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 실제 위젯 */}
|
{/* 실제 위젯 */}
|
||||||
<div className="flex-1 rounded-lg overflow-hidden">{renderInteractiveWidget(component)}</div>
|
<div className="h-full w-full">{renderInteractiveWidget(component)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 개선된 검증 패널 (선택적 표시) */}
|
{/* 개선된 검증 패널 (선택적 표시) */}
|
||||||
|
|
|
||||||
|
|
@ -532,19 +532,11 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="absolute" style={componentStyle}>
|
<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">
|
<div className="h-full w-full">
|
||||||
{/* 라벨 표시 - 컴포넌트 내부에서 라벨을 처리하므로 외부에서는 표시하지 않음 */}
|
{/* 라벨 숨김 - 모달에서는 라벨을 표시하지 않음 */}
|
||||||
{!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="flex-1 rounded-lg overflow-hidden">{renderInteractiveWidget(component)}</div>
|
<div className="h-full w-full">{renderInteractiveWidget(component)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -292,15 +292,23 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.addEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener);
|
try {
|
||||||
|
window.addEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener);
|
||||||
|
|
||||||
// 전역 강제 업데이트 함수 등록
|
// 전역 강제 업데이트 함수 등록
|
||||||
if (!(window as any).forceRealtimePreviewUpdate) {
|
if (!(window as any).forceRealtimePreviewUpdate) {
|
||||||
(window as any).forceRealtimePreviewUpdate = forceUpdate;
|
(window as any).forceRealtimePreviewUpdate = forceUpdate;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("RealtimePreview 이벤트 리스너 등록 실패:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener);
|
try {
|
||||||
|
window.removeEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("RealtimePreview 이벤트 리스너 제거 실패:", error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [component.id, fileUpdateTrigger]);
|
}, [component.id, fileUpdateTrigger]);
|
||||||
|
|
|
||||||
|
|
@ -600,55 +600,71 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
||||||
|
|
||||||
// 🎯 RealtimePreview 동기화를 위한 전역 이벤트 발생
|
// 🎯 RealtimePreview 동기화를 위한 전역 이벤트 발생
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const eventDetail = {
|
try {
|
||||||
componentId: component.id,
|
const eventDetail = {
|
||||||
files: updatedFiles,
|
componentId: component.id,
|
||||||
fileCount: updatedFiles.length,
|
files: updatedFiles,
|
||||||
action: 'delete',
|
fileCount: updatedFiles.length,
|
||||||
timestamp: timestamp,
|
action: 'delete',
|
||||||
source: 'designMode' // 🎯 화면설계 모드에서 온 이벤트임을 표시
|
timestamp: timestamp,
|
||||||
};
|
source: 'designMode' // 🎯 화면설계 모드에서 온 이벤트임을 표시
|
||||||
|
};
|
||||||
|
|
||||||
console.log("🚀🚀🚀 FileComponentConfigPanel 삭제 이벤트 발생:", eventDetail);
|
console.log("🚀🚀🚀 FileComponentConfigPanel 삭제 이벤트 발생:", eventDetail);
|
||||||
|
|
||||||
const event = new CustomEvent('globalFileStateChanged', {
|
const event = new CustomEvent('globalFileStateChanged', {
|
||||||
detail: eventDetail
|
detail: eventDetail
|
||||||
});
|
});
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
console.log("✅✅✅ globalFileStateChanged 삭제 이벤트 발생 완료");
|
console.log("✅✅✅ globalFileStateChanged 삭제 이벤트 발생 완료");
|
||||||
|
|
||||||
// 추가 지연 이벤트들
|
// 추가 지연 이벤트들
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log("🔄 추가 삭제 이벤트 발생 (지연 100ms)");
|
try {
|
||||||
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
console.log("🔄 추가 삭제 이벤트 발생 (지연 100ms)");
|
||||||
detail: { ...eventDetail, delayed: true }
|
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
||||||
}));
|
detail: { ...eventDetail, delayed: true }
|
||||||
}, 100);
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("FileComponentConfigPanel 지연 이벤트 발생 실패:", error);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log("🔄 추가 삭제 이벤트 발생 (지연 300ms)");
|
try {
|
||||||
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
console.log("🔄 추가 삭제 이벤트 발생 (지연 300ms)");
|
||||||
detail: { ...eventDetail, delayed: true, attempt: 2 }
|
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
||||||
}));
|
detail: { ...eventDetail, delayed: true, attempt: 2 }
|
||||||
}, 300);
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("FileComponentConfigPanel 지연 이벤트 발생 실패:", error);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("FileComponentConfigPanel 이벤트 발생 실패:", error);
|
||||||
|
}
|
||||||
|
|
||||||
// 그리드 파일 상태 새로고침 이벤트도 유지
|
// 그리드 파일 상태 새로고침 이벤트도 유지
|
||||||
const tableName = currentTableName || 'screen_files';
|
try {
|
||||||
const recordId = component.id;
|
const tableName = currentTableName || 'screen_files';
|
||||||
const columnName = component.columnName || component.id || 'file_attachment';
|
const recordId = component.id;
|
||||||
const targetObjid = `${tableName}:${recordId}:${columnName}`;
|
const columnName = component.columnName || component.id || 'file_attachment';
|
||||||
|
const targetObjid = `${tableName}:${recordId}:${columnName}`;
|
||||||
|
|
||||||
const refreshEvent = new CustomEvent('refreshFileStatus', {
|
const refreshEvent = new CustomEvent('refreshFileStatus', {
|
||||||
detail: {
|
detail: {
|
||||||
tableName: tableName,
|
tableName: tableName,
|
||||||
recordId: recordId,
|
recordId: recordId,
|
||||||
columnName: columnName,
|
columnName: columnName,
|
||||||
targetObjid: targetObjid,
|
targetObjid: targetObjid,
|
||||||
fileCount: updatedFiles.length
|
fileCount: updatedFiles.length
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.dispatchEvent(refreshEvent);
|
window.dispatchEvent(refreshEvent);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("FileComponentConfigPanel refreshFileStatus 이벤트 발생 실패:", error);
|
||||||
|
}
|
||||||
console.log("🔄 FileComponentConfigPanel 파일 삭제 후 그리드 새로고침:", {
|
console.log("🔄 FileComponentConfigPanel 파일 삭제 후 그리드 새로고침:", {
|
||||||
tableName,
|
tableName,
|
||||||
recordId,
|
recordId,
|
||||||
|
|
|
||||||
|
|
@ -605,38 +605,50 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf
|
||||||
|
|
||||||
// 🎯 화면설계 모드와 동기화를 위한 전역 이벤트 발생
|
// 🎯 화면설계 모드와 동기화를 위한 전역 이벤트 발생
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const eventDetail = {
|
try {
|
||||||
componentId: component.id,
|
const eventDetail = {
|
||||||
files: filteredFiles,
|
componentId: component.id,
|
||||||
fileCount: filteredFiles.length,
|
files: filteredFiles,
|
||||||
action: 'delete',
|
fileCount: filteredFiles.length,
|
||||||
timestamp: Date.now(),
|
action: 'delete',
|
||||||
source: 'realScreen' // 실제 화면에서 온 이벤트임을 표시
|
timestamp: Date.now(),
|
||||||
};
|
source: 'realScreen' // 실제 화면에서 온 이벤트임을 표시
|
||||||
|
};
|
||||||
|
|
||||||
console.log("🚀🚀🚀 FileUpload 위젯 삭제 이벤트 발생:", eventDetail);
|
console.log("🚀🚀🚀 FileUpload 위젯 삭제 이벤트 발생:", eventDetail);
|
||||||
|
|
||||||
const event = new CustomEvent('globalFileStateChanged', {
|
const event = new CustomEvent('globalFileStateChanged', {
|
||||||
detail: eventDetail
|
detail: eventDetail
|
||||||
});
|
});
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
console.log("✅✅✅ FileUpload 위젯 → 화면설계 모드 동기화 이벤트 발생 완료");
|
console.log("✅✅✅ FileUpload 위젯 → 화면설계 모드 동기화 이벤트 발생 완료");
|
||||||
|
|
||||||
// 추가 지연 이벤트들
|
// 추가 지연 이벤트들
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log("🔄 FileUpload 위젯 추가 삭제 이벤트 발생 (지연 100ms)");
|
try {
|
||||||
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
console.log("🔄 FileUpload 위젯 추가 삭제 이벤트 발생 (지연 100ms)");
|
||||||
detail: { ...eventDetail, delayed: true }
|
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
||||||
}));
|
detail: { ...eventDetail, delayed: true }
|
||||||
}, 100);
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("FileUpload 지연 이벤트 발생 실패:", error);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log("🔄 FileUpload 위젯 추가 삭제 이벤트 발생 (지연 300ms)");
|
try {
|
||||||
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
console.log("🔄 FileUpload 위젯 추가 삭제 이벤트 발생 (지연 300ms)");
|
||||||
detail: { ...eventDetail, delayed: true, attempt: 2 }
|
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
||||||
}));
|
detail: { ...eventDetail, delayed: true, attempt: 2 }
|
||||||
}, 300);
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("FileUpload 지연 이벤트 발생 실패:", error);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("FileUpload 이벤트 발생 실패:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateComponent({
|
onUpdateComponent({
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,11 @@ function SelectContent({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Portal>
|
<SelectPrimitive.Portal container={document.querySelector('[data-radix-portal]') || document.body}>
|
||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
className={cn(
|
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" &&
|
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",
|
"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,
|
className,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue