feat: 조건부 컨테이너를 화면 선택 방식으로 개선
- ConditionalSection 타입 변경 (components[] → screenId, screenName) * 각 조건마다 컴포넌트를 직접 배치하는 대신 기존 화면을 선택 * 복잡한 입력 폼도 화면 재사용으로 간단히 구성 - ConditionalSectionDropZone을 ConditionalSectionViewer로 교체 * 드롭존 대신 InteractiveScreenViewer 사용 * 선택된 화면을 조건별로 렌더링 * 디자인 모드에서 화면 미선택 시 안내 메시지 표시 - ConfigPanel에서 화면 선택 드롭다운 구현 * screenManagementApi.getScreenList()로 화면 목록 로드 * 각 섹션마다 화면 선택 Select 컴포넌트 * 선택된 화면의 ID와 이름 자동 저장 및 표시 * 로딩 상태 표시 - 기본 설정 업데이트 * defaultConfig에서 components 제거, screenId 추가 * 모든 섹션 기본값을 screenId: null로 설정 - README 문서 개선 * 화면 선택 방식으로 사용법 업데이트 * 사용 사례에 화면 ID 예시 추가 * 구조 다이어그램 수정 (드롭존 → 화면 표시) * 디자인/실행 모드 설명 업데이트 장점: - 기존 화면 재사용으로 생산성 향상 - 복잡한 입력 폼도 간단히 조건부 표시 - 화면 수정 시 자동 반영 - 유지보수 용이
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user