Files
vexplor/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx
kjs f5756e184f feat: 조건부 컨테이너를 화면 선택 방식으로 개선
- ConditionalSection 타입 변경 (components[] → screenId, screenName)
  * 각 조건마다 컴포넌트를 직접 배치하는 대신 기존 화면을 선택
  * 복잡한 입력 폼도 화면 재사용으로 간단히 구성

- ConditionalSectionDropZone을 ConditionalSectionViewer로 교체
  * 드롭존 대신 InteractiveScreenViewer 사용
  * 선택된 화면을 조건별로 렌더링
  * 디자인 모드에서 화면 미선택 시 안내 메시지 표시

- ConfigPanel에서 화면 선택 드롭다운 구현
  * screenManagementApi.getScreenList()로 화면 목록 로드
  * 각 섹션마다 화면 선택 Select 컴포넌트
  * 선택된 화면의 ID와 이름 자동 저장 및 표시
  * 로딩 상태 표시

- 기본 설정 업데이트
  * defaultConfig에서 components 제거, screenId 추가
  * 모든 섹션 기본값을 screenId: null로 설정

- README 문서 개선
  * 화면 선택 방식으로 사용법 업데이트
  * 사용 사례에 화면 ID 예시 추가
  * 구조 다이어그램 수정 (드롭존 → 화면 표시)
  * 디자인/실행 모드 설명 업데이트

장점:
- 기존 화면 재사용으로 생산성 향상
- 복잡한 입력 폼도 간단히 조건부 표시
- 화면 수정 시 자동 반영
- 유지보수 용이
2025-11-14 17:40:07 +09:00

163 lines
4.9 KiB
TypeScript

"use client";
import React, { useState, useEffect } 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";
/**
* 조건부 컨테이너 컴포넌트
* 상단 셀렉트박스 값에 따라 하단에 다른 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,
style,
className,
}: ConditionalContainerProps) {
// 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 [selectedValue, setSelectedValue] = useState<string>(
value || formData?.[controlField] || defaultValue || ""
);
// formData 변경 시 동기화
useEffect(() => {
if (formData?.[controlField]) {
setSelectedValue(formData[controlField]);
}
}, [formData, controlField]);
// 값 변경 핸들러
const handleValueChange = (newValue: string) => {
setSelectedValue(newValue);
if (onChange) {
onChange(newValue);
}
if (onFormDataChange) {
onFormDataChange(controlField, newValue);
}
};
// 간격 스타일
const spacingClass = {
tight: "space-y-2",
normal: "space-y-4",
loose: "space-y-8",
}[spacing];
return (
<div
className={cn("h-full w-full flex flex-col", spacingClass, className)}
style={style}
>
{/* 제어 셀렉트박스 */}
<div className="space-y-2 flex-shrink-0">
<Label htmlFor={controlField} className="text-xs sm:text-sm">
{controlLabel}
</Label>
<Select value={selectedValue} onValueChange={handleValueChange}>
<SelectTrigger
id={controlField}
className="h-8 text-xs sm:h-10 sm:text-sm"
>
<SelectValue placeholder="선택하세요" />
</SelectTrigger>
<SelectContent>
{sections.map((section) => (
<SelectItem key={section.id} value={section.condition}>
{section.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 조건별 섹션들 */}
<div className="flex-1 min-h-0 overflow-auto">
{isDesignMode ? (
// 디자인 모드: 모든 섹션 표시
<div className={spacingClass}>
{sections.map((section) => (
<ConditionalSectionViewer
key={section.id}
sectionId={section.id}
condition={section.condition}
label={section.label}
screenId={section.screenId}
screenName={section.screenName}
isActive={selectedValue === section.condition}
isDesignMode={isDesignMode}
showBorder={showBorder}
formData={formData}
onFormDataChange={onFormDataChange}
/>
))}
</div>
) : (
// 실행 모드: 활성 섹션만 표시
sections.map((section) =>
selectedValue === section.condition ? (
<ConditionalSectionViewer
key={section.id}
sectionId={section.id}
condition={section.condition}
label={section.label}
screenId={section.screenId}
screenName={section.screenName}
isActive={true}
isDesignMode={false}
showBorder={showBorder}
formData={formData}
onFormDataChange={onFormDataChange}
/>
) : null
)
)}
{/* 섹션이 없는 경우 안내 */}
{sections.length === 0 && isDesignMode && (
<div className="flex items-center justify-center min-h-[200px] border-2 border-dashed border-muted-foreground/30 rounded-lg bg-muted/20">
<p className="text-sm text-muted-foreground">
</p>
</div>
)}
</div>
</div>
);
}