모달 잘 보이게 수정

This commit is contained in:
kjs 2025-11-10 09:33:29 +09:00
parent 1d6418ca63
commit e2f4b47588
5 changed files with 85 additions and 32 deletions

View File

@ -364,7 +364,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
</div>
</ResizableDialogHeader>
<div className="flex flex-1 items-center justify-center overflow-auto">
<div className="flex-1 overflow-auto p-6">
{loading ? (
<div className="flex h-full items-center justify-center">
<div className="text-center">
@ -374,13 +374,11 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
</div>
) : screenData ? (
<div
className="relative bg-white"
className="relative bg-white mx-auto"
style={{
width: screenDimensions?.width || 800,
height: screenDimensions?.height || 600,
width: `${screenDimensions?.width || 800}px`,
height: `${screenDimensions?.height || 600}px`,
transformOrigin: "center center",
maxWidth: "100%",
maxHeight: "100%",
}}
>
{screenData.components.map((component) => {

View File

@ -401,15 +401,14 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
const applyStyles = (element: React.ReactElement) => {
if (!comp.style) return element;
// ✅ 격자 시스템 잔재 제거: style.width, style.height는 무시
// size.width, size.height가 부모 컨테이너에서 적용되므로
const { width, height, ...styleWithoutSize } = comp.style;
return React.cloneElement(element, {
style: {
...element.props.style, // 기존 스타일 유지
...comp.style,
// 크기는 부모 컨테이너에서 처리하므로 제거 (하지만 다른 스타일은 유지)
width: "100%",
height: "100%",
minHeight: "100%",
maxHeight: "100%",
...styleWithoutSize, // width/height 제외한 스타일만 적용
boxSizing: "border-box",
},
});
@ -1887,7 +1886,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
return (
<>
<div className="h-full w-full">
<div className="h-full" style={{ width: '100%', height: '100%' }}>
{/* 라벨이 있는 경우 표시 (데이터 테이블 제외) */}
{shouldShowLabel && (
<label className="mb-2 block text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
@ -1897,7 +1896,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
)}
{/* 실제 위젯 - 상위에서 라벨을 렌더링했으므로 자식은 라벨 숨김 */}
<div className="h-full w-full">{renderInteractiveWidget(componentForRendering)}</div>
<div className="h-full" style={{ width: '100%', height: '100%' }}>{renderInteractiveWidget(componentForRendering)}</div>
</div>
{/* 개선된 검증 패널 (선택적 표시) */}

View File

@ -343,10 +343,14 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
const applyStyles = (element: React.ReactElement) => {
if (!comp.style) return element;
// ✅ 격자 시스템 잔재 제거: style.width, style.height는 무시
// size.width, size.height가 부모 컨테이너에서 적용되므로
const { width, height, ...styleWithoutSize } = comp.style;
return React.cloneElement(element, {
style: {
...element.props.style,
...comp.style,
...styleWithoutSize, // width/height 제외한 스타일만 적용
width: "100%",
height: "100%",
minHeight: "100%",
@ -676,14 +680,17 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
// 메인 렌더링
const { type, position, size, style = {} } = component;
// ✅ 격자 시스템 잔재 제거: style.width, style.height 무시
const { width: styleWidth, height: styleHeight, ...styleWithoutSize } = style;
const componentStyle = {
position: "absolute" as const,
left: position?.x || 0,
top: position?.y || 0,
width: size?.width || 200,
height: size?.height || 10,
zIndex: position?.z || 1,
...style,
...styleWithoutSize, // width/height 제외한 스타일만 먼저 적용
width: size?.width || 200, // size의 픽셀 값이 최종 우선순위
height: size?.height || 10,
};
return (

View File

@ -200,8 +200,21 @@ export const SaveModal: React.FC<SaveModalProps> = ({
const calculateDynamicSize = () => {
if (!components.length) return { width: 800, height: 600 };
const maxX = Math.max(...components.map((c) => (c.position?.x || 0) + (c.size?.width || 200)));
const maxY = Math.max(...components.map((c) => (c.position?.y || 0) + (c.size?.height || 40)));
const maxX = Math.max(...components.map((c) => {
const x = c.position?.x || 0;
const width = typeof c.size?.width === 'number'
? c.size.width
: parseInt(String(c.size?.width || 200), 10);
return x + width;
}));
const maxY = Math.max(...components.map((c) => {
const y = c.position?.y || 0;
const height = typeof c.size?.height === 'number'
? c.size.height
: parseInt(String(c.size?.height || 40), 10);
return y + height;
}));
const padding = 40;
return {
@ -214,8 +227,15 @@ export const SaveModal: React.FC<SaveModalProps> = ({
return (
<ResizableDialog open={isOpen} onOpenChange={(open) => !isSaving && !open && onClose()}>
<ResizableDialogContent className={`${modalSizeClasses[modalSize]} max-h-[90vh] gap-0 p-0`}>
<ResizableDialogHeader className="border-b px-6 py-4">
<ResizableDialogContent
modalId={`save-modal-${screenId}`}
defaultWidth={dynamicSize.width + 48}
defaultHeight={dynamicSize.height + 120}
minWidth={400}
minHeight={300}
className="gap-0 p-0"
>
<ResizableDialogHeader className="border-b px-6 py-4 flex-shrink-0">
<div className="flex items-center justify-between">
<ResizableDialogTitle className="text-lg font-semibold">{initialData ? "데이터 수정" : "데이터 등록"}</ResizableDialogTitle>
<div className="flex items-center gap-2">
@ -239,29 +259,51 @@ export const SaveModal: React.FC<SaveModalProps> = ({
</div>
</ResizableDialogHeader>
<div className="overflow-auto p-6">
<div className="overflow-auto p-6 flex-1">
{loading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />
</div>
) : screenData && components.length > 0 ? (
<div
className="relative bg-white w-full h-full"
className="relative bg-white"
style={{
minWidth: dynamicSize.width,
minHeight: dynamicSize.height,
width: `${dynamicSize.width}px`,
height: `${dynamicSize.height}px`,
minWidth: `${dynamicSize.width}px`,
minHeight: `${dynamicSize.height}px`,
}}
>
<div className="relative w-full h-full" style={{ minHeight: "300px" }}>
{components.map((component, index) => (
<div className="relative" style={{ width: `${dynamicSize.width}px`, height: `${dynamicSize.height}px` }}>
{components.map((component, index) => {
// ✅ 격자 시스템 잔재 제거: size의 픽셀 값만 사용
const widthPx = typeof component.size?.width === 'number'
? component.size.width
: parseInt(String(component.size?.width || 200), 10);
const heightPx = typeof component.size?.height === 'number'
? component.size.height
: parseInt(String(component.size?.height || 40), 10);
// 디버깅: 실제 크기 확인
if (index === 0) {
console.log('🔍 SaveModal 컴포넌트 크기:', {
componentId: component.id,
'size.width (원본)': component.size?.width,
'size.width 타입': typeof component.size?.width,
'widthPx (계산)': widthPx,
'style.width': component.style?.width,
});
}
return (
<div
key={component.id}
style={{
position: "absolute",
top: component.position?.y || 0,
left: component.position?.x || 0,
width: component.size?.width || 200,
height: component.size?.height || 40,
width: `${widthPx}px`, // ✅ 픽셀 단위 강제
height: `${heightPx}px`, // ✅ 픽셀 단위 강제
zIndex: component.position?.z || 1000 + index,
}}
>
@ -306,7 +348,8 @@ export const SaveModal: React.FC<SaveModalProps> = ({
/>
)}
</div>
))}
);
})}
</div>
</div>
) : (

View File

@ -55,6 +55,7 @@ interface ResizableDialogContentProps
modalId?: string; // localStorage 저장용 고유 ID
userId?: string; // 사용자별 저장용
open?: boolean; // 🆕 모달 열림/닫힘 상태 (외부에서 전달)
disableFlexLayout?: boolean; // 🆕 flex 레이아웃 비활성화 (absolute 레이아웃용)
}
const ResizableDialogContent = React.forwardRef<
@ -74,6 +75,7 @@ const ResizableDialogContent = React.forwardRef<
modalId,
userId = "guest",
open: externalOpen, // 🆕 외부에서 전달받은 open 상태
disableFlexLayout = false, // 🆕 flex 레이아웃 비활성화
style: userStyle,
...props
},
@ -373,7 +375,11 @@ const ResizableDialogContent = React.forwardRef<
minHeight: `${minHeight}px`,
}}
>
<div ref={contentRef} className="flex flex-col h-full overflow-auto">
<div
ref={contentRef}
className="h-full w-full"
style={{ display: 'block', overflow: 'hidden' }}
>
{children}
</div>