"use client"; /** * ConditionalConfigPanel * * 비개발자도 쉽게 조건부 표시/숨김/활성화/비활성화를 설정할 수 있는 UI * * 사용처: * - 화면관리 > 상세설정 패널 * - 화면관리 > 속성 패널 */ import React, { useState, useEffect, useMemo } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Zap, Plus, Trash2, HelpCircle } from "lucide-react"; import { ConditionalConfig } from "@/types/unified-components"; // ===== 타입 정의 ===== interface FieldOption { id: string; label: string; type?: string; // text, number, select, checkbox 등 options?: Array<{ value: string; label: string }>; // select 타입일 경우 옵션들 } interface ConditionalConfigPanelProps { /** 현재 조건부 설정 */ config?: ConditionalConfig; /** 설정 변경 콜백 */ onChange: (config: ConditionalConfig | undefined) => void; /** 같은 화면에 있는 다른 필드들 (조건 필드로 선택 가능) */ availableFields: FieldOption[]; /** 현재 컴포넌트 ID (자기 자신은 조건 필드에서 제외) */ currentComponentId?: string; } // 연산자 옵션 const OPERATORS: Array<{ value: ConditionalConfig["operator"]; label: string; description: string }> = [ { value: "=", label: "같음", description: "값이 정확히 일치할 때" }, { value: "!=", label: "다름", description: "값이 일치하지 않을 때" }, { value: ">", label: "보다 큼", description: "값이 더 클 때 (숫자)" }, { value: "<", label: "보다 작음", description: "값이 더 작을 때 (숫자)" }, { value: "in", label: "포함됨", description: "여러 값 중 하나일 때" }, { value: "notIn", label: "포함 안됨", description: "여러 값 중 아무것도 아닐 때" }, { value: "isEmpty", label: "비어있음", description: "값이 없을 때" }, { value: "isNotEmpty", label: "값이 있음", description: "값이 있을 때" }, ]; // 동작 옵션 const ACTIONS: Array<{ value: ConditionalConfig["action"]; label: string; description: string }> = [ { value: "show", label: "표시", description: "조건 만족 시 이 필드를 표시" }, { value: "hide", label: "숨김", description: "조건 만족 시 이 필드를 숨김" }, { value: "enable", label: "활성화", description: "조건 만족 시 이 필드를 활성화" }, { value: "disable", label: "비활성화", description: "조건 만족 시 이 필드를 비활성화" }, ]; // ===== 컴포넌트 ===== export function ConditionalConfigPanel({ config, onChange, availableFields, currentComponentId, }: ConditionalConfigPanelProps) { // 로컬 상태 const [enabled, setEnabled] = useState(config?.enabled ?? false); const [field, setField] = useState(config?.field ?? ""); const [operator, setOperator] = useState(config?.operator ?? "="); const [value, setValue] = useState(String(config?.value ?? "")); const [action, setAction] = useState(config?.action ?? "show"); // 자기 자신을 제외한 필드 목록 const selectableFields = useMemo(() => { return availableFields.filter((f) => f.id !== currentComponentId); }, [availableFields, currentComponentId]); // 선택된 필드 정보 const selectedField = useMemo(() => { return selectableFields.find((f) => f.id === field); }, [selectableFields, field]); // config prop 변경 시 로컬 상태 동기화 useEffect(() => { setEnabled(config?.enabled ?? false); setField(config?.field ?? ""); setOperator(config?.operator ?? "="); setValue(String(config?.value ?? "")); setAction(config?.action ?? "show"); }, [config]); // 설정 변경 시 부모에게 알림 const updateConfig = (updates: Partial) => { const newConfig: ConditionalConfig = { enabled: updates.enabled ?? enabled, field: updates.field ?? field, operator: updates.operator ?? operator, value: updates.value ?? value, action: updates.action ?? action, }; // enabled가 false이면 undefined 반환 (설정 제거) if (!newConfig.enabled) { onChange(undefined); } else { onChange(newConfig); } }; // 활성화 토글 const handleEnabledChange = (checked: boolean) => { setEnabled(checked); updateConfig({ enabled: checked }); }; // 조건 필드 변경 const handleFieldChange = (newField: string) => { setField(newField); setValue(""); // 필드 변경 시 값 초기화 updateConfig({ field: newField, value: "" }); }; // 연산자 변경 const handleOperatorChange = (newOperator: ConditionalConfig["operator"]) => { setOperator(newOperator); // 비어있음/값이있음 연산자는 value 필요 없음 if (newOperator === "isEmpty" || newOperator === "isNotEmpty") { setValue(""); updateConfig({ operator: newOperator, value: "" }); } else { updateConfig({ operator: newOperator }); } }; // 값 변경 const handleValueChange = (newValue: string) => { setValue(newValue); // 타입에 따라 적절한 값으로 변환 let parsedValue: unknown = newValue; if (selectedField?.type === "number") { parsedValue = Number(newValue); } else if (newValue === "true") { parsedValue = true; } else if (newValue === "false") { parsedValue = false; } updateConfig({ value: parsedValue }); }; // 동작 변경 const handleActionChange = (newAction: ConditionalConfig["action"]) => { setAction(newAction); updateConfig({ action: newAction }); }; // 값 입력 필드 렌더링 (필드 타입에 따라 다르게) const renderValueInput = () => { // 비어있음/값이있음은 값 입력 불필요 if (operator === "isEmpty" || operator === "isNotEmpty") { return (
(값 입력 불필요)
); } // 선택된 필드에 옵션이 있으면 Select로 표시 if (selectedField?.options && selectedField.options.length > 0) { return ( ); } // 체크박스 타입이면 true/false Select if (selectedField?.type === "checkbox" || selectedField?.type === "boolean") { return ( ); } // 숫자 타입 if (selectedField?.type === "number") { return ( handleValueChange(e.target.value)} placeholder="숫자 입력" className="h-8 text-xs" /> ); } // 기본: 텍스트 입력 return ( handleValueChange(e.target.value)} placeholder="값 입력" className="h-8 text-xs" /> ); }; return (
{/* 헤더 */}
조건부 표시
{/* 조건 설정 영역 */} {enabled && (
{/* 조건 필드 선택 */}

이 필드의 값에 따라 조건이 적용됩니다

{/* 연산자 선택 */}
{/* 값 입력 */}
{renderValueInput()}
{/* 동작 선택 */}

조건이 만족되면 이 필드를 {ACTIONS.find(a => a.value === action)?.label}합니다

{/* 미리보기 */} {field && (

설정 요약:

"{selectableFields.find(f => f.id === field)?.label || field}" 필드가{" "} {operator === "isEmpty" ? "비어있으면" : operator === "isNotEmpty" ? "값이 있으면" : `"${value}"${operator === "=" ? "이면" : operator === "!=" ? "이 아니면" : operator === ">" ? "보다 크면" : operator === "<" ? "보다 작으면" : operator === "in" ? "에 포함되면" : "에 포함되지 않으면"}`} {" "} → 이 필드를{" "} {action === "show" ? "표시" : action === "hide" ? "숨김" : action === "enable" ? "활성화" : "비활성화"}

)}
)}
); } export default ConditionalConfigPanel;