"use client"; import React, { useState, useEffect, useRef } from "react"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { ConditionalContainerProps, ConditionalSection } from "./types"; import { ConditionalSectionViewer } from "./ConditionalSectionViewer"; import { cn } from "@/lib/utils"; import { useScreenContextOptional } from "@/contexts/ScreenContext"; import type { DataProvidable } from "@/types/data-transfer"; /** * 조건부 컨테이너 컴포넌트 * 상단 셀렉트박스 값에 따라 하단에 다른 UI를 표시 */ export function ConditionalContainerComponent({ config, controlField: propControlField, controlLabel: propControlLabel, sections: propSections, defaultValue: propDefaultValue, showBorder: propShowBorder, spacing: propSpacing, value, onChange, formData, onFormDataChange, isDesignMode = false, onUpdateComponent, onDeleteComponent, onSelectComponent, selectedComponentId, onHeightChange, componentId, style, className, groupedData, // 🆕 그룹 데이터 onSave, // 🆕 EditModal의 handleSave 콜백 }: ConditionalContainerProps) { // 화면 컨텍스트 (데이터 제공자로 등록) const screenContext = useScreenContextOptional(); // config prop 우선, 없으면 개별 prop 사용 const controlField = config?.controlField || propControlField || "condition"; const controlLabel = config?.controlLabel || propControlLabel || "조건 선택"; const sections = config?.sections || propSections || []; const defaultValue = config?.defaultValue || propDefaultValue || sections[0]?.condition; const showBorder = config?.showBorder ?? propShowBorder ?? true; const spacing = config?.spacing || propSpacing || "normal"; // 초기값 계산 (한 번만) const initialValue = React.useMemo(() => { return value || formData?.[controlField] || defaultValue || ""; }, []); // 의존성 없음 - 마운트 시 한 번만 계산 // 현재 선택된 값 const [selectedValue, setSelectedValue] = useState(initialValue); // 최신 값을 ref로 유지 (클로저 문제 방지) const selectedValueRef = React.useRef(selectedValue); selectedValueRef.current = selectedValue; // 렌더링마다 업데이트 (useEffect 대신) // 콜백 refs (의존성 제거) const onChangeRef = React.useRef(onChange); const onFormDataChangeRef = React.useRef(onFormDataChange); onChangeRef.current = onChange; onFormDataChangeRef.current = onFormDataChange; // 값 변경 핸들러 - 의존성 없음 const handleValueChange = React.useCallback((newValue: string) => { // 같은 값이면 무시 if (newValue === selectedValueRef.current) return; setSelectedValue(newValue); if (onChangeRef.current) { onChangeRef.current(newValue); } if (onFormDataChangeRef.current) { onFormDataChangeRef.current(controlField, newValue); } }, [controlField]); // sectionsRef 추가 (dataProvider에서 사용) const sectionsRef = React.useRef(sections); React.useEffect(() => { sectionsRef.current = sections; }, [sections]); // dataProvider를 useMemo로 감싸서 불필요한 재생성 방지 const dataProvider = React.useMemo(() => ({ componentId: componentId || "conditional-container", componentType: "conditional-container", getSelectedData: () => { // ref를 통해 최신 값 참조 (클로저 문제 방지) const currentValue = selectedValueRef.current; const currentSections = sectionsRef.current; return [{ [controlField]: currentValue, condition: currentValue, label: currentSections.find(s => s.condition === currentValue)?.label || currentValue, }]; }, getAllData: () => { const currentSections = sectionsRef.current; return currentSections.map(section => ({ condition: section.condition, label: section.label, })); }, clearSelection: () => { // 조건부 컨테이너는 초기화하지 않음 console.log("조건부 컨테이너는 선택 초기화를 지원하지 않습니다."); }, }), [componentId, controlField]); // selectedValue, sections는 ref로 참조 // 화면 컨텍스트에 데이터 제공자로 등록 useEffect(() => { if (screenContext && componentId) { screenContext.registerDataProvider(componentId, dataProvider); return () => { screenContext.unregisterDataProvider(componentId); }; } }, [screenContext, componentId, dataProvider]); // 컨테이너 높이 측정용 ref const containerRef = useRef(null); const previousHeightRef = useRef(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 = { tight: "space-y-2", normal: "space-y-4", loose: "space-y-8", }[spacing]; return (
{/* 제어 셀렉트박스 */}
{/* 조건별 섹션들 */}
{isDesignMode ? ( // 디자인 모드: 모든 섹션 표시
{sections.map((section) => ( ))}
) : ( // 실행 모드: 활성 섹션만 표시 sections.map((section) => selectedValue === section.condition ? ( ) : null ) )} {/* 섹션이 없는 경우 안내 */} {sections.length === 0 && isDesignMode && (

설정 패널에서 조건을 추가하세요

)}
); }