조건부 컨테이너
This commit is contained in:
parent
d1ce14de7a
commit
2c099feea0
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
|
|
@ -34,6 +34,8 @@ export function ConditionalContainerComponent({
|
|||
onDeleteComponent,
|
||||
onSelectComponent,
|
||||
selectedComponentId,
|
||||
onHeightChange,
|
||||
componentId,
|
||||
style,
|
||||
className,
|
||||
}: ConditionalContainerProps) {
|
||||
|
|
@ -70,6 +72,33 @@ export function ConditionalContainerComponent({
|
|||
}
|
||||
};
|
||||
|
||||
// 컨테이너 높이 측정용 ref
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const previousHeightRef = useRef<number>(0);
|
||||
|
||||
// 높이 변화 감지 및 콜백 호출
|
||||
useEffect(() => {
|
||||
if (!containerRef.current || isDesignMode || !onHeightChange) return;
|
||||
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const newHeight = entry.contentRect.height;
|
||||
|
||||
// 높이가 실제로 변경되었을 때만 콜백 호출
|
||||
if (Math.abs(newHeight - previousHeightRef.current) > 5) {
|
||||
console.log(`📏 조건부 컨테이너 높이 변화: ${previousHeightRef.current}px → ${newHeight}px`);
|
||||
previousHeightRef.current = newHeight;
|
||||
onHeightChange(newHeight);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
resizeObserver.observe(containerRef.current);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [isDesignMode, onHeightChange, selectedValue]); // selectedValue 변경 시에도 감지
|
||||
|
||||
// 간격 스타일
|
||||
const spacingClass = {
|
||||
|
|
@ -80,6 +109,7 @@ export function ConditionalContainerComponent({
|
|||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn("h-full w-full flex flex-col", spacingClass, className)}
|
||||
style={style}
|
||||
>
|
||||
|
|
@ -106,7 +136,7 @@ export function ConditionalContainerComponent({
|
|||
</div>
|
||||
|
||||
{/* 조건별 섹션들 */}
|
||||
<div className="flex-1 min-h-0 overflow-auto">
|
||||
<div className="flex-1 min-h-0">
|
||||
{isDesignMode ? (
|
||||
// 디자인 모드: 모든 섹션 표시
|
||||
<div className={spacingClass}>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ConditionalSectionViewerProps } from "./types";
|
||||
import { InteractiveScreenViewer } from "@/components/screen/InteractiveScreenViewer";
|
||||
import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
|
|
@ -27,32 +27,33 @@ export function ConditionalSectionViewer({
|
|||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [components, setComponents] = useState<ComponentData[]>([]);
|
||||
const [screenInfo, setScreenInfo] = useState<{ id: number; tableName?: string } | null>(null);
|
||||
const [screenResolution, setScreenResolution] = useState<{ width: number; height: number } | null>(null);
|
||||
|
||||
// 화면 로드
|
||||
useEffect(() => {
|
||||
if (!screenId) {
|
||||
setComponents([]);
|
||||
setScreenInfo(null);
|
||||
setScreenResolution(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const loadScreen = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const [layout, screen] = await Promise.all([
|
||||
screenApi.getLayout(screenId),
|
||||
screenApi.getScreen(screenId),
|
||||
]);
|
||||
const [layout, screen] = await Promise.all([screenApi.getLayout(screenId), screenApi.getScreen(screenId)]);
|
||||
|
||||
setComponents(layout.components || []);
|
||||
setScreenInfo({
|
||||
id: screenId,
|
||||
tableName: screen.tableName,
|
||||
});
|
||||
setScreenResolution(layout.screenResolution || null);
|
||||
} catch (error) {
|
||||
console.error("화면 로드 실패:", error);
|
||||
setComponents([]);
|
||||
setScreenInfo(null);
|
||||
setScreenResolution(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -69,20 +70,18 @@ export function ConditionalSectionViewer({
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative min-h-[200px] transition-all",
|
||||
showBorder && "rounded-lg border-2",
|
||||
isDesignMode ? (
|
||||
"border-dashed border-muted-foreground/30 bg-muted/20"
|
||||
) : (
|
||||
showBorder ? "border-border bg-card" : ""
|
||||
),
|
||||
!isDesignMode && !isActive && "hidden"
|
||||
"relative w-full transition-all",
|
||||
isDesignMode && showBorder && "border-muted-foreground/30 bg-muted/20 rounded-lg border-2 border-dashed",
|
||||
!isDesignMode && !isActive && "hidden",
|
||||
)}
|
||||
style={{
|
||||
minHeight: isDesignMode ? "200px" : undefined,
|
||||
}}
|
||||
data-section-id={sectionId}
|
||||
>
|
||||
{/* 섹션 라벨 (디자인 모드에서만 표시) */}
|
||||
{isDesignMode && (
|
||||
<div className="absolute -top-3 left-4 bg-background px-2 text-xs font-medium text-muted-foreground z-10">
|
||||
<div className="bg-background text-muted-foreground absolute -top-3 left-4 z-10 px-2 text-xs font-medium">
|
||||
{label} {isActive && "(활성)"}
|
||||
{screenId && ` - 화면 ID: ${screenId}`}
|
||||
</div>
|
||||
|
|
@ -91,40 +90,65 @@ export function ConditionalSectionViewer({
|
|||
{/* 화면 미선택 안내 (디자인 모드 + 화면 없을 때) */}
|
||||
{isDesignMode && !screenId && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center text-muted-foreground">
|
||||
<div className="text-muted-foreground text-center">
|
||||
<p className="text-sm">설정 패널에서 화면을 선택하세요</p>
|
||||
<p className="text-xs mt-1">조건: {condition}</p>
|
||||
<p className="mt-1 text-xs">조건: {condition}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 로딩 중 */}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50 z-20">
|
||||
<div className="bg-background/50 absolute inset-0 z-20 flex items-center justify-center">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-primary" />
|
||||
<p className="text-xs text-muted-foreground">화면 로드 중...</p>
|
||||
<Loader2 className="text-primary h-6 w-6 animate-spin" />
|
||||
<p className="text-muted-foreground text-xs">화면 로드 중...</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 화면 렌더링 */}
|
||||
{screenId && components.length > 0 && (
|
||||
<div className="relative min-h-[200px] w-full">
|
||||
{components.map((component) => (
|
||||
<InteractiveScreenViewer
|
||||
key={component.id}
|
||||
component={component}
|
||||
allComponents={components}
|
||||
formData={formData}
|
||||
onFormDataChange={onFormDataChange}
|
||||
hideLabel={false}
|
||||
screenInfo={screenInfo || undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
{isDesignMode ? (
|
||||
/* 디자인 모드: 화면 정보만 표시 */
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<p className="text-foreground mb-2 text-sm font-medium">{screenName || `화면 ID: ${screenId}`}</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{screenResolution?.width} x {screenResolution?.height}
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs">컴포넌트 {components.length}개</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* 실행 모드: 실제 화면 렌더링 */
|
||||
<div className="w-full">
|
||||
{/* 화면 크기만큼의 절대 위치 캔버스 */}
|
||||
<div
|
||||
className="relative mx-auto"
|
||||
style={{
|
||||
width: screenResolution?.width ? `${screenResolution.width}px` : "100%",
|
||||
height: screenResolution?.height ? `${screenResolution.height}px` : "auto",
|
||||
minHeight: "200px",
|
||||
}}
|
||||
>
|
||||
{components.map((component) => (
|
||||
<RealtimePreview
|
||||
key={component.id}
|
||||
component={component}
|
||||
isSelected={false}
|
||||
isDesignMode={false}
|
||||
onClick={() => {}}
|
||||
screenId={screenInfo?.id}
|
||||
tableName={screenInfo?.tableName}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ export const ConditionalContainerDefinition: Omit<
|
|||
tags: ["조건부", "분기", "동적", "레이아웃"],
|
||||
|
||||
defaultSize: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
width: 1400,
|
||||
height: 800,
|
||||
},
|
||||
|
||||
defaultConfig: {
|
||||
|
|
@ -48,8 +48,8 @@ export const ConditionalContainerDefinition: Omit<
|
|||
|
||||
defaultProps: {
|
||||
style: {
|
||||
width: "800px",
|
||||
height: "600px",
|
||||
width: "1400px",
|
||||
height: "800px",
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ export interface ConditionalContainerProps {
|
|||
onSelectComponent?: (componentId: string) => void;
|
||||
selectedComponentId?: string;
|
||||
|
||||
// 높이 변화 알림 (아래 컴포넌트 재배치용)
|
||||
onHeightChange?: (newHeight: number) => void;
|
||||
componentId?: string; // 자신의 컴포넌트 ID
|
||||
|
||||
// 스타일
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue