diff --git a/docker/dev/docker-compose.frontend.mac.yml b/docker/dev/docker-compose.frontend.mac.yml index e02a1287..23bcb654 100644 --- a/docker/dev/docker-compose.frontend.mac.yml +++ b/docker/dev/docker-compose.frontend.mac.yml @@ -9,6 +9,7 @@ services: - "9771:3000" environment: - NEXT_PUBLIC_API_URL=http://localhost:8080/api + - NODE_OPTIONS=--max-old-space-size=4096 volumes: - ../../frontend:/app - /app/node_modules diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index 066ecc40..75f81a21 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -504,18 +504,18 @@ export const ScreenModal: React.FC = ({ className }) => { } // 화면관리에서 설정한 크기 = 컨텐츠 영역 크기 - // 실제 모달 크기 = 컨텐츠 + 헤더 + 연속등록 체크박스 + gap + padding - const headerHeight = 52; // DialogHeader (타이틀 + border-b + py-3) - const footerHeight = 52; // 연속 등록 모드 체크박스 영역 - const dialogGap = 16; // DialogContent gap-4 - const extraPadding = 24; // 추가 여백 (안전 마진) + // 실제 모달 크기 = 컨텐츠 + 헤더 + 연속등록 체크박스 + // 🔧 여백 최소화: 디자이너와 일치하도록 조정 + const headerHeight = 48; // DialogHeader (타이틀 + border-b + py-3) + const footerHeight = 44; // 연속 등록 모드 체크박스 영역 + const horizontalPadding = 16; // 좌우 패딩 최소화 - const totalHeight = screenDimensions.height + headerHeight + footerHeight + dialogGap + extraPadding; + const totalHeight = screenDimensions.height + headerHeight + footerHeight; return { className: "overflow-hidden p-0", style: { - width: `${Math.min(screenDimensions.width + 48, window.innerWidth * 0.98)}px`, // 좌우 패딩 추가 + width: `${Math.min(screenDimensions.width + horizontalPadding, window.innerWidth * 0.98)}px`, height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`, maxWidth: "98vw", maxHeight: "95vh", @@ -587,7 +587,7 @@ export const ScreenModal: React.FC = ({ className }) => { return ( @@ -602,7 +602,9 @@ export const ScreenModal: React.FC = ({ className }) => { -
+
{loading ? (
@@ -614,11 +616,10 @@ export const ScreenModal: React.FC = ({ className }) => {
{screenData.components.map((component) => { diff --git a/frontend/components/screen/RealtimePreviewDynamic.tsx b/frontend/components/screen/RealtimePreviewDynamic.tsx index 886e3977..911618ba 100644 --- a/frontend/components/screen/RealtimePreviewDynamic.tsx +++ b/frontend/components/screen/RealtimePreviewDynamic.tsx @@ -598,12 +598,7 @@ const RealtimePreviewDynamicComponent: React.FC = ({ (contentRef as any).current = node; } }} - className={`${ - (component.type === "component" && (component as any).componentType === "flow-widget") || - ((component as any).componentType === "conditional-container" && !isDesignMode) - ? "h-auto" - : "h-full" - } overflow-visible`} + className="h-full overflow-visible" style={{ width: "100%", maxWidth: "100%" }} > ( const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; + // 라벨 높이 계산 (기본 20px, 사용자 설정에 따라 조정) + const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14; + const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4; + const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2; + return (
+ {/* 🔧 라벨을 absolute로 컴포넌트 위에 배치 */} {showLabel && ( )} -
+
{renderBiz()}
diff --git a/frontend/components/v2/V2Date.tsx b/frontend/components/v2/V2Date.tsx index f3102d5a..7e930840 100644 --- a/frontend/components/v2/V2Date.tsx +++ b/frontend/components/v2/V2Date.tsx @@ -75,10 +75,11 @@ const SingleDatePicker = forwardRef< disabled?: boolean; readonly?: boolean; className?: string; + placeholder?: string; } >( ( - { value, onChange, dateFormat = "YYYY-MM-DD", showToday = true, minDate, maxDate, disabled, readonly, className }, + { value, onChange, dateFormat = "YYYY-MM-DD", showToday = true, minDate, maxDate, disabled, readonly, className, placeholder = "날짜 선택" }, ref, ) => { const [open, setOpen] = useState(false); @@ -87,6 +88,16 @@ const SingleDatePicker = forwardRef< const minDateObj = useMemo(() => parseDate(minDate, dateFormat), [minDate, dateFormat]); const maxDateObj = useMemo(() => parseDate(maxDate, dateFormat), [maxDate, dateFormat]); + // 표시할 날짜 텍스트 계산 (ISO 형식이면 포맷팅, 아니면 그대로) + const displayText = useMemo(() => { + if (!value) return ""; + // Date 객체로 변환 후 포맷팅 + if (date && isValid(date)) { + return formatDate(date, dateFormat); + } + return value; + }, [value, date, dateFormat]); + const handleSelect = useCallback( (selectedDate: Date | undefined) => { if (selectedDate) { @@ -115,13 +126,13 @@ const SingleDatePicker = forwardRef< variant="outline" disabled={disabled || readonly} className={cn( - "h-10 w-full justify-start text-left font-normal", - !value && "text-muted-foreground", + "h-full w-full justify-start text-left font-normal", + !displayText && "text-muted-foreground", className, )} > - - {value || "날짜 선택"} + + {displayText || placeholder} @@ -409,6 +420,7 @@ export const V2Date = forwardRef((props, ref) => { maxDate={config.maxDate} disabled={isDisabled} readonly={readonly} + placeholder={config.placeholder} /> ); @@ -444,6 +456,7 @@ export const V2Date = forwardRef((props, ref) => { showToday={config.showToday} disabled={isDisabled} readonly={readonly} + placeholder={config.placeholder} /> ); } @@ -453,37 +466,40 @@ export const V2Date = forwardRef((props, ref) => { const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; + // 라벨 높이 계산 (기본 20px, 사용자 설정에 따라 조정) + const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14; + const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4; + const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2; + return (
+ {/* 🔧 라벨을 absolute로 컴포넌트 위에 배치 */} {showLabel && ( )} -
+
{renderDatePicker()}
diff --git a/frontend/components/v2/V2Hierarchy.tsx b/frontend/components/v2/V2Hierarchy.tsx index 23e4fd85..28f51fee 100644 --- a/frontend/components/v2/V2Hierarchy.tsx +++ b/frontend/components/v2/V2Hierarchy.tsx @@ -462,37 +462,40 @@ export const V2Hierarchy = forwardRef( const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; + // 라벨 높이 계산 (기본 20px, 사용자 설정에 따라 조정) + const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14; + const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4; + const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2; + return (
+ {/* 🔧 라벨을 absolute로 컴포넌트 위에 배치 */} {showLabel && ( )} -
+
{renderHierarchy()}
diff --git a/frontend/components/v2/V2Input.tsx b/frontend/components/v2/V2Input.tsx index 10be3bf2..85929cbc 100644 --- a/frontend/components/v2/V2Input.tsx +++ b/frontend/components/v2/V2Input.tsx @@ -792,37 +792,40 @@ export const V2Input = forwardRef((props, ref) => const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; + // 라벨 높이 계산 (기본 20px, 사용자 설정에 따라 조정) + const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14; + const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4; + const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2; // 라벨 높이 + 여백 + return (
+ {/* 🔧 라벨을 absolute로 컴포넌트 위에 배치 (높이에 포함되지 않음) */} {showLabel && ( )} -
+
{renderInput()}
diff --git a/frontend/components/v2/V2Media.tsx b/frontend/components/v2/V2Media.tsx index c457b3f7..27503c3e 100644 --- a/frontend/components/v2/V2Media.tsx +++ b/frontend/components/v2/V2Media.tsx @@ -536,37 +536,40 @@ export const V2Media = forwardRef( const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; + // 라벨 높이 계산 (기본 20px, 사용자 설정에 따라 조정) + const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14; + const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4; + const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2; + return (
+ {/* 🔧 라벨을 absolute로 컴포넌트 위에 배치 */} {showLabel && ( )} -
+
{renderMedia()}
diff --git a/frontend/components/v2/V2Select.tsx b/frontend/components/v2/V2Select.tsx index 1aeac80d..d1954350 100644 --- a/frontend/components/v2/V2Select.tsx +++ b/frontend/components/v2/V2Select.tsx @@ -744,37 +744,40 @@ export const V2Select = forwardRef( const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; + // 라벨 높이 계산 (기본 20px, 사용자 설정에 따라 조정) + const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14; + const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4; + const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2; + return (
+ {/* 🔧 라벨을 absolute로 컴포넌트 위에 배치 */} {showLabel && ( )} -
+
{renderSelect()}
diff --git a/frontend/components/v2/config-panels/V2DateConfigPanel.tsx b/frontend/components/v2/config-panels/V2DateConfigPanel.tsx index 1308a700..6d7ba5d7 100644 --- a/frontend/components/v2/config-panels/V2DateConfigPanel.tsx +++ b/frontend/components/v2/config-panels/V2DateConfigPanel.tsx @@ -48,6 +48,20 @@ export const V2DateConfigPanel: React.FC = ({ + {/* 플레이스홀더 */} +
+ + updateConfig("placeholder", e.target.value)} + placeholder="날짜 선택" + className="h-8 text-xs" + /> +

날짜가 선택되지 않았을 때 표시할 텍스트

+
+ + + {/* 표시 형식 */}
diff --git a/frontend/lib/registry/components/v2-date/V2DateRenderer.tsx b/frontend/lib/registry/components/v2-date/V2DateRenderer.tsx index dfbbceb1..1550bbe3 100644 --- a/frontend/lib/registry/components/v2-date/V2DateRenderer.tsx +++ b/frontend/lib/registry/components/v2-date/V2DateRenderer.tsx @@ -29,10 +29,15 @@ export class V2DateRenderer extends AutoRegisteringComponentRenderer { } }; + // 라벨: style.labelText 우선, 없으면 component.label 사용 + // style.labelDisplay가 false면 라벨 숨김 + const style = component.style || {}; + const effectiveLabel = style.labelDisplay === false ? undefined : (style.labelText || component.label); + return ( = ({ return (
- {/* 라벨 렌더링 */} - {component.label && (component.style?.labelDisplay ?? true) && ( - - )} - + {/* v2-text-display는 텍스트 표시 전용이므로 별도 라벨 불필요 */}
{componentConfig.text || "텍스트를 입력하세요"}