= ({
handleUpdateProperty(selectedComponent.id, "componentConfig.collapsible", checked);
}}
/>
-
{selectedComponent.componentConfig?.collapsible && (
-
+
= ({
handleUpdateProperty(selectedComponent.id, "componentConfig.defaultOpen", checked);
}}
/>
-
+
기본으로 펼치기
@@ -567,9 +573,7 @@ export const UnifiedPropertiesPanel: React.FC
= ({
Section Paper 설정
-
- 배경색 기반의 미니멀한 그룹화 컨테이너
-
+
배경색 기반의 미니멀한 그룹화 컨테이너
{/* 배경색 */}
@@ -680,7 +684,7 @@ export const UnifiedPropertiesPanel: React.FC
= ({
handleUpdateProperty(selectedComponent.id, "componentConfig.showBorder", checked);
}}
/>
-
+
미묘한 테두리 표시
@@ -691,9 +695,9 @@ export const UnifiedPropertiesPanel: React.FC = ({
// ConfigPanel이 없는 경우 경고 표시
return (
-
+
⚠️ 설정 패널 없음
-
+
컴포넌트 "{componentId || componentType}"에 대한 설정 패널이 없습니다.
@@ -1418,7 +1422,7 @@ export const UnifiedPropertiesPanel: React.FC = ({
{/* 통합 컨텐츠 (탭 제거) */}
-
+
{/* 해상도 설정 - 항상 맨 위에 표시 */}
{currentResolution && onResolutionChange && (
diff --git a/frontend/components/ui/resizable-dialog.tsx b/frontend/components/ui/resizable-dialog.tsx
index fb93f085..54d18ed7 100644
--- a/frontend/components/ui/resizable-dialog.tsx
+++ b/frontend/components/ui/resizable-dialog.tsx
@@ -176,7 +176,7 @@ const ResizableDialogContent = React.forwardRef<
height: Math.max(minHeight, Math.min(maxHeight, parsed.height)),
userResized: true,
};
- console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
+ // console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
}
}
} catch (error) {
diff --git a/frontend/components/webtypes/RepeaterInput.tsx b/frontend/components/webtypes/RepeaterInput.tsx
index f81e8c9c..ade700e1 100644
--- a/frontend/components/webtypes/RepeaterInput.tsx
+++ b/frontend/components/webtypes/RepeaterInput.tsx
@@ -1,6 +1,6 @@
"use client";
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useRef } from "react";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -8,8 +8,9 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Separator } from "@/components/ui/separator";
+import { Badge } from "@/components/ui/badge";
import { Plus, X, GripVertical, ChevronDown, ChevronUp } from "lucide-react";
-import { RepeaterFieldGroupConfig, RepeaterData, RepeaterItemData, RepeaterFieldDefinition } from "@/types/repeater";
+import { RepeaterFieldGroupConfig, RepeaterData, RepeaterItemData, RepeaterFieldDefinition, CalculationFormula } from "@/types/repeater";
import { cn } from "@/lib/utils";
import { useBreakpoint } from "@/hooks/useBreakpoint";
import { usePreviewBreakpoint } from "@/components/screen/ResponsivePreviewModal";
@@ -21,6 +22,7 @@ export interface RepeaterInputProps {
disabled?: boolean;
readonly?: boolean;
className?: string;
+ menuObjid?: number; // 카테고리 조회용 메뉴 ID
}
/**
@@ -34,6 +36,7 @@ export const RepeaterInput: React.FC
= ({
disabled = false,
readonly = false,
className,
+ menuObjid,
}) => {
// 현재 브레이크포인트 감지
const globalBreakpoint = useBreakpoint();
@@ -42,6 +45,9 @@ export const RepeaterInput: React.FC = ({
// 미리보기 모달 내에서는 previewBreakpoint 우선 사용
const breakpoint = previewBreakpoint || globalBreakpoint;
+ // 카테고리 매핑 데이터 (값 -> {label, color})
+ const [categoryMappings, setCategoryMappings] = useState>>({});
+
// 설정 기본값
const {
fields = [],
@@ -72,6 +78,12 @@ export const RepeaterInput: React.FC = ({
// 접힌 상태 관리 (각 항목별)
const [collapsedItems, setCollapsedItems] = useState>(new Set());
+
+ // 🆕 초기 계산 완료 여부 추적 (무한 루프 방지)
+ const initialCalcDoneRef = useRef(false);
+
+ // 🆕 삭제된 항목 ID 목록 추적 (ref로 관리하여 즉시 반영)
+ const deletedItemIdsRef = useRef([]);
// 빈 항목 생성
function createEmptyItem(): RepeaterItemData {
@@ -82,10 +94,39 @@ export const RepeaterInput: React.FC = ({
return item;
}
- // 외부 value 변경 시 동기화
+ // 외부 value 변경 시 동기화 및 초기 계산식 필드 업데이트
useEffect(() => {
if (value.length > 0) {
- setItems(value);
+ // 🆕 초기 로드 시 계산식 필드 자동 업데이트 (한 번만 실행)
+ const calculatedFields = fields.filter(f => f.type === "calculated");
+
+ if (calculatedFields.length > 0 && !initialCalcDoneRef.current) {
+ const updatedValue = value.map(item => {
+ const updatedItem = { ...item };
+ let hasChange = false;
+
+ calculatedFields.forEach(calcField => {
+ const calculatedValue = calculateValue(calcField.formula, updatedItem);
+ if (calculatedValue !== null && updatedItem[calcField.name] !== calculatedValue) {
+ updatedItem[calcField.name] = calculatedValue;
+ hasChange = true;
+ }
+ });
+
+ return hasChange ? updatedItem : item;
+ });
+
+ setItems(updatedValue);
+ initialCalcDoneRef.current = true;
+
+ // 계산된 값이 있으면 onChange 호출 (초기 1회만)
+ const dataWithMeta = config.targetTable
+ ? updatedValue.map((item) => ({ ...item, _targetTable: config.targetTable }))
+ : updatedValue;
+ onChange?.(dataWithMeta);
+ } else {
+ setItems(value);
+ }
}
}, [value]);
@@ -111,14 +152,32 @@ export const RepeaterInput: React.FC = ({
if (items.length <= minItems) {
return;
}
+
+ // 🆕 삭제되는 항목의 ID 저장 (DB에서 삭제할 때 필요)
+ const removedItem = items[index];
+ if (removedItem?.id) {
+ console.log("🗑️ [RepeaterInput] 삭제할 항목 ID 추가:", removedItem.id);
+ deletedItemIdsRef.current = [...deletedItemIdsRef.current, removedItem.id];
+ }
+
const newItems = items.filter((_, i) => i !== index);
setItems(newItems);
// targetTable이 설정된 경우 각 항목에 메타데이터 추가
+ // 🆕 삭제된 항목 ID 목록도 함께 전달 (ref에서 최신값 사용)
+ const currentDeletedIds = deletedItemIdsRef.current;
+ console.log("🗑️ [RepeaterInput] 현재 삭제 목록:", currentDeletedIds);
+
const dataWithMeta = config.targetTable
- ? newItems.map((item) => ({ ...item, _targetTable: config.targetTable }))
+ ? newItems.map((item, idx) => ({
+ ...item,
+ _targetTable: config.targetTable,
+ // 첫 번째 항목에만 삭제 ID 목록 포함
+ ...(idx === 0 ? { _deletedItemIds: currentDeletedIds } : {}),
+ }))
: newItems;
+ console.log("🗑️ [RepeaterInput] onChange 호출 - dataWithMeta:", dataWithMeta);
onChange?.(dataWithMeta);
// 접힌 상태도 업데이트
@@ -134,6 +193,16 @@ export const RepeaterInput: React.FC = ({
...newItems[itemIndex],
[fieldName]: value,
};
+
+ // 🆕 계산식 필드 자동 업데이트: 변경된 항목의 모든 계산식 필드 값을 재계산
+ const calculatedFields = fields.filter(f => f.type === "calculated");
+ calculatedFields.forEach(calcField => {
+ const calculatedValue = calculateValue(calcField.formula, newItems[itemIndex]);
+ if (calculatedValue !== null) {
+ newItems[itemIndex][calcField.name] = calculatedValue;
+ }
+ });
+
setItems(newItems);
console.log("✏️ RepeaterInput 필드 변경, onChange 호출:", {
itemIndex,
@@ -143,8 +212,15 @@ export const RepeaterInput: React.FC = ({
});
// targetTable이 설정된 경우 각 항목에 메타데이터 추가
+ // 🆕 삭제된 항목 ID 목록도 유지
+ const currentDeletedIds = deletedItemIdsRef.current;
const dataWithMeta = config.targetTable
- ? newItems.map((item) => ({ ...item, _targetTable: config.targetTable }))
+ ? newItems.map((item, idx) => ({
+ ...item,
+ _targetTable: config.targetTable,
+ // 첫 번째 항목에만 삭제 ID 목록 포함 (삭제된 항목이 있는 경우에만)
+ ...(idx === 0 && currentDeletedIds.length > 0 ? { _deletedItemIds: currentDeletedIds } : {}),
+ }))
: newItems;
onChange?.(dataWithMeta);
@@ -192,24 +268,183 @@ export const RepeaterInput: React.FC = ({
setDraggedIndex(null);
};
+ /**
+ * 계산식 실행
+ * @param formula 계산식 정의
+ * @param item 현재 항목 데이터
+ * @returns 계산 결과
+ */
+ const calculateValue = (formula: CalculationFormula | undefined, item: RepeaterItemData): number | null => {
+ if (!formula || !formula.field1) return null;
+
+ const value1 = parseFloat(item[formula.field1]) || 0;
+ const value2 = formula.field2
+ ? (parseFloat(item[formula.field2]) || 0)
+ : (formula.constantValue ?? 0);
+
+ let result: number;
+
+ switch (formula.operator) {
+ case "+":
+ result = value1 + value2;
+ break;
+ case "-":
+ result = value1 - value2;
+ break;
+ case "*":
+ result = value1 * value2;
+ break;
+ case "/":
+ result = value2 !== 0 ? value1 / value2 : 0;
+ break;
+ case "%":
+ result = value2 !== 0 ? value1 % value2 : 0;
+ break;
+ case "round":
+ const decimalPlaces = formula.decimalPlaces ?? 0;
+ const multiplier = Math.pow(10, decimalPlaces);
+ result = Math.round(value1 * multiplier) / multiplier;
+ break;
+ case "floor":
+ const floorMultiplier = Math.pow(10, formula.decimalPlaces ?? 0);
+ result = Math.floor(value1 * floorMultiplier) / floorMultiplier;
+ break;
+ case "ceil":
+ const ceilMultiplier = Math.pow(10, formula.decimalPlaces ?? 0);
+ result = Math.ceil(value1 * ceilMultiplier) / ceilMultiplier;
+ break;
+ case "abs":
+ result = Math.abs(value1);
+ break;
+ default:
+ result = value1;
+ }
+
+ return result;
+ };
+
+ /**
+ * 숫자 포맷팅
+ * @param value 숫자 값
+ * @param format 포맷 설정
+ * @returns 포맷된 문자열
+ */
+ const formatNumber = (
+ value: number | null,
+ format?: RepeaterFieldDefinition["numberFormat"]
+ ): string => {
+ if (value === null || isNaN(value)) return "-";
+
+ let formattedValue = value;
+
+ // 소수점 자릿수 적용
+ if (format?.decimalPlaces !== undefined) {
+ formattedValue = parseFloat(value.toFixed(format.decimalPlaces));
+ }
+
+ // 천 단위 구분자
+ let result = format?.useThousandSeparator !== false
+ ? formattedValue.toLocaleString("ko-KR", {
+ minimumFractionDigits: format?.minimumFractionDigits ?? 0,
+ maximumFractionDigits: format?.maximumFractionDigits ?? format?.decimalPlaces ?? 0,
+ })
+ : formattedValue.toString();
+
+ // 접두사/접미사 추가
+ if (format?.prefix) result = format.prefix + result;
+ if (format?.suffix) result = result + format.suffix;
+
+ return result;
+ };
+
// 개별 필드 렌더링
const renderField = (field: RepeaterFieldDefinition, itemIndex: number, value: any) => {
+ const isReadonly = disabled || readonly || field.readonly;
+
const commonProps = {
value: value || "",
- disabled: disabled || readonly,
+ disabled: isReadonly,
placeholder: field.placeholder,
required: field.required,
};
+ // 계산식 필드: 자동으로 계산된 값을 표시 (읽기 전용)
+ if (field.type === "calculated") {
+ const item = items[itemIndex];
+ const calculatedValue = calculateValue(field.formula, item);
+ const formattedValue = formatNumber(calculatedValue, field.numberFormat);
+
+ return (
+
+ {formattedValue}
+
+ );
+ }
+
+ // 카테고리 타입은 항상 배지로 표시 (카테고리 관리에서 설정한 색상 적용)
+ if (field.type === "category") {
+ if (!value) return -;
+
+ // field.name을 키로 사용 (테이블 리스트와 동일)
+ const mapping = categoryMappings[field.name];
+ const valueStr = String(value); // 값을 문자열로 변환
+ const categoryData = mapping?.[valueStr];
+ const displayLabel = categoryData?.label || valueStr;
+ const displayColor = categoryData?.color || "#64748b"; // 기본 색상 (slate)
+
+ console.log(`🏷️ [RepeaterInput] 카테고리 배지 렌더링:`, {
+ fieldName: field.name,
+ value: valueStr,
+ mapping,
+ categoryData,
+ displayLabel,
+ displayColor,
+ });
+
+ // 색상이 "none"이면 일반 텍스트로 표시
+ if (displayColor === "none") {
+ return {displayLabel};
+ }
+
+ return (
+
+ {displayLabel}
+
+ );
+ }
+
+ // 읽기 전용 모드: 텍스트로 표시
+ // displayMode가 "readonly"이면 isReadonly 여부와 관계없이 텍스트로 표시
+ if (field.displayMode === "readonly") {
+ // select 타입인 경우 옵션에서 라벨 찾기
+ if (field.type === "select" && value && field.options) {
+ const option = field.options.find(opt => opt.value === value);
+ return {option?.label || value};
+ }
+
+ // 일반 텍스트
+ return (
+
+ {value || "-"}
+
+ );
+ }
+
switch (field.type) {
case "select":
return (
+ {/* 그룹화 컬럼 설정 */}
+
+
수정 시 그룹화 컬럼 (선택)
+
+
+ 수정 모드에서 이 컬럼 값을 기준으로 관련된 모든 데이터를 조회합니다.
+
+ 예: 입고번호를 선택하면 같은 입고번호를 가진 모든 품목이 표시됩니다.
+
+
+
{/* 필드 정의 */}
필드 정의
@@ -235,10 +261,23 @@ export const RepeaterConfigPanel: React.FC = ({
key={column.columnName}
value={column.columnName}
onSelect={() => {
+ // input_type (DB에서 설정한 타입) 우선 사용, 없으면 webType/widgetType
+ const col = column as any;
+ const fieldType = col.input_type || col.inputType || col.webType || col.widgetType || "text";
+
+ console.log("🔍 [RepeaterConfigPanel] 필드 타입 결정:", {
+ columnName: column.columnName,
+ input_type: col.input_type,
+ inputType: col.inputType,
+ webType: col.webType,
+ widgetType: col.widgetType,
+ finalType: fieldType,
+ });
+
updateField(index, {
name: column.columnName,
label: column.columnLabel || column.columnName,
- type: (column.widgetType as RepeaterFieldType) || "text",
+ type: fieldType as RepeaterFieldType,
});
// 로컬 입력 상태도 업데이트
setLocalInputs(prev => ({
@@ -293,13 +332,25 @@ export const RepeaterConfigPanel: React.FC = ({
- 텍스트
- 숫자
- 이메일
- 전화번호
- 날짜
- 선택박스
- 텍스트영역
+ {/* 테이블 타입 관리에서 사용하는 input_type 목록 */}
+ 텍스트 (text)
+ 숫자 (number)
+ 텍스트영역 (textarea)
+ 날짜 (date)
+ 선택박스 (select)
+ 체크박스 (checkbox)
+ 라디오 (radio)
+ 카테고리 (category)
+ 엔티티 참조 (entity)
+ 공통코드 (code)
+ 이미지 (image)
+ 직접입력 (direct)
+
+
+
+ 계산식 (calculated)
+
+
@@ -316,16 +367,316 @@ export const RepeaterConfigPanel: React.FC
= ({
-
- updateField(index, { required: checked as boolean })}
- />
-
- 필수 입력
-
-
+ {/* 계산식 타입일 때 계산식 설정 */}
+ {field.type === "calculated" && (
+
+
+
+ 계산식 설정
+
+
+ {/* 필드 1 선택 */}
+
+ 필드 1
+
+
+
+ {/* 연산자 선택 */}
+
+ 연산자
+
+
+
+ {/* 두 번째 필드 또는 상수값 */}
+ {!["round", "floor", "ceil", "abs"].includes(field.formula?.operator || "") ? (
+
+ 필드 2 / 상수
+
+
+ ) : (
+
+ 소수점 자릿수
+ updateField(index, {
+ formula: { ...field.formula, decimalPlaces: parseInt(e.target.value) || 0 } as CalculationFormula
+ })}
+ className="h-8 text-xs"
+ />
+
+ )}
+
+ {/* 상수값 입력 필드 */}
+ {field.formula?.constantValue !== undefined && (
+
+ 상수값
+ updateField(index, {
+ formula: { ...field.formula, constantValue: parseFloat(e.target.value) || 0 } as CalculationFormula
+ })}
+ placeholder="숫자 입력"
+ className="h-8 text-xs"
+ />
+
+ )}
+
+ {/* 숫자 포맷 설정 */}
+
+
숫자 표시 형식
+
+
+ updateField(index, {
+ numberFormat: { ...field.numberFormat, useThousandSeparator: checked as boolean }
+ })}
+ />
+
+ 천 단위 구분자
+
+
+
+ 소수점:
+ updateField(index, {
+ numberFormat: { ...field.numberFormat, decimalPlaces: parseInt(e.target.value) || 0 }
+ })}
+ type="number"
+ min={0}
+ max={10}
+ className="h-6 w-12 text-[10px]"
+ />
+
+
+
+ updateField(index, {
+ numberFormat: { ...field.numberFormat, prefix: e.target.value }
+ })}
+ placeholder="접두사 (₩)"
+ className="h-7 text-[10px]"
+ />
+ updateField(index, {
+ numberFormat: { ...field.numberFormat, suffix: e.target.value }
+ })}
+ placeholder="접미사 (원)"
+ className="h-7 text-[10px]"
+ />
+
+
+
+ {/* 계산식 미리보기 */}
+
+ 계산식:
+
+ {field.formula?.field1 || "필드1"} {field.formula?.operator || "+"} {
+ field.formula?.field2 ||
+ (field.formula?.constantValue !== undefined ? field.formula.constantValue : "필드2")
+ }
+
+
+
+ )}
+
+ {/* 숫자 타입일 때 숫자 표시 형식 설정 */}
+ {field.type === "number" && (
+
+
숫자 표시 형식
+
+
+ updateField(index, {
+ numberFormat: { ...field.numberFormat, useThousandSeparator: checked as boolean }
+ })}
+ />
+
+ 천 단위 구분자
+
+
+
+ 소수점:
+ updateField(index, {
+ numberFormat: { ...field.numberFormat, decimalPlaces: parseInt(e.target.value) || 0 }
+ })}
+ type="number"
+ min={0}
+ max={10}
+ className="h-6 w-12 text-[10px]"
+ />
+
+
+
+ updateField(index, {
+ numberFormat: { ...field.numberFormat, prefix: e.target.value }
+ })}
+ placeholder="접두사 (₩)"
+ className="h-7 text-[10px]"
+ />
+ updateField(index, {
+ numberFormat: { ...field.numberFormat, suffix: e.target.value }
+ })}
+ placeholder="접미사 (원)"
+ className="h-7 text-[10px]"
+ />
+
+
+ )}
+
+ {/* 카테고리 타입일 때 카테고리 코드 입력 */}
+ {field.type === "category" && (
+
+
카테고리 코드
+
updateField(index, { categoryCode: e.target.value })}
+ placeholder="카테고리 코드 (예: INBOUND_TYPE)"
+ className="h-8 w-full text-xs"
+ />
+
+ 카테고리 관리에서 설정한 색상으로 배지가 표시됩니다
+
+
+ )}
+
+ {/* 카테고리 타입이 아닐 때만 표시 모드 선택 */}
+ {field.type !== "category" && (
+
+
+ 표시 모드
+
+
+
+
+
+ updateField(index, { required: checked as boolean })}
+ />
+
+ 필수
+
+
+
+
+ )}
+
+ {/* 카테고리 타입일 때는 필수만 표시 */}
+ {field.type === "category" && (
+
+ updateField(index, { required: checked as boolean })}
+ />
+
+ 필수 입력
+
+
+ )}
))}
diff --git a/frontend/contexts/ScreenContext.tsx b/frontend/contexts/ScreenContext.tsx
new file mode 100644
index 00000000..f8c703dd
--- /dev/null
+++ b/frontend/contexts/ScreenContext.tsx
@@ -0,0 +1,133 @@
+/**
+ * 화면 컨텍스트
+ * 같은 화면 내의 컴포넌트들이 서로 통신할 수 있도록 합니다.
+ */
+
+"use client";
+
+import React, { createContext, useContext, useCallback, useRef } from "react";
+import type { DataProvidable, DataReceivable } from "@/types/data-transfer";
+import { logger } from "@/lib/utils/logger";
+import type { SplitPanelPosition } from "@/contexts/SplitPanelContext";
+
+interface ScreenContextValue {
+ screenId?: number;
+ tableName?: string;
+ splitPanelPosition?: SplitPanelPosition; // 🆕 분할 패널 위치 (left/right)
+
+ // 컴포넌트 등록
+ registerDataProvider: (componentId: string, provider: DataProvidable) => void;
+ unregisterDataProvider: (componentId: string) => void;
+ registerDataReceiver: (componentId: string, receiver: DataReceivable) => void;
+ unregisterDataReceiver: (componentId: string) => void;
+
+ // 컴포넌트 조회
+ getDataProvider: (componentId: string) => DataProvidable | undefined;
+ getDataReceiver: (componentId: string) => DataReceivable | undefined;
+
+ // 모든 컴포넌트 조회
+ getAllDataProviders: () => Map
;
+ getAllDataReceivers: () => Map;
+}
+
+const ScreenContext = createContext(null);
+
+interface ScreenContextProviderProps {
+ screenId?: number;
+ tableName?: string;
+ splitPanelPosition?: SplitPanelPosition; // 🆕 분할 패널 위치
+ children: React.ReactNode;
+}
+
+/**
+ * 화면 컨텍스트 프로바이더
+ */
+export function ScreenContextProvider({ screenId, tableName, splitPanelPosition, children }: ScreenContextProviderProps) {
+ const dataProvidersRef = useRef
@@ -179,6 +242,8 @@ export function ConditionalContainerComponent({
onFormDataChange={onFormDataChange}
groupedData={groupedData}
onSave={onSave}
+ controlField={controlField}
+ selectedCondition={selectedValue}
/>
) : null
)
diff --git a/frontend/lib/registry/components/conditional-container/ConditionalContainerConfigPanel.tsx b/frontend/lib/registry/components/conditional-container/ConditionalContainerConfigPanel.tsx
index 173bebc6..ff850346 100644
--- a/frontend/lib/registry/components/conditional-container/ConditionalContainerConfigPanel.tsx
+++ b/frontend/lib/registry/components/conditional-container/ConditionalContainerConfigPanel.tsx
@@ -12,19 +12,38 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import { Plus, Trash2, GripVertical, Loader2 } from "lucide-react";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+import { Plus, Trash2, GripVertical, Loader2, Check, ChevronsUpDown, Database } from "lucide-react";
import { ConditionalContainerConfig, ConditionalSection } from "./types";
import { screenApi } from "@/lib/api/screen";
+import { cn } from "@/lib/utils";
+import { getCategoryColumnsByMenu, getCategoryValues, getSecondLevelMenus } from "@/lib/api/tableCategoryValue";
interface ConditionalContainerConfigPanelProps {
config: ConditionalContainerConfig;
- onConfigChange: (config: ConditionalContainerConfig) => void;
+ onChange?: (config: ConditionalContainerConfig) => void;
+ onConfigChange?: (config: ConditionalContainerConfig) => void;
}
export function ConditionalContainerConfigPanel({
config,
+ onChange,
onConfigChange,
}: ConditionalContainerConfigPanelProps) {
+ // onChange 또는 onConfigChange 둘 다 지원
+ const handleConfigChange = onChange || onConfigChange;
const [localConfig, setLocalConfig] = useState