"use client"; /** * UnifiedRepeater 설정 패널 * * 렌더링 모드별 설정: * - inline: 현재 화면 테이블 컬럼 직접 입력 * - modal: 엔티티 선택 + 추가 입력 (FK 저장 + 추가 컬럼 입력) */ import React, { useState, useEffect, useMemo, useCallback } from "react"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Checkbox } from "@/components/ui/checkbox"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Database, Link2, Plus, Trash2, GripVertical, ArrowRight, Calculator, ChevronDown, ChevronRight, Eye, EyeOff, Wand2, Check, ChevronsUpDown, } from "lucide-react"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { tableTypeApi } from "@/lib/api/screen"; import { tableManagementApi } from "@/lib/api/tableManagement"; import { getAvailableNumberingRules, getAvailableNumberingRulesForScreen } from "@/lib/api/numberingRule"; import { NumberingRuleConfig } from "@/types/numbering-rule"; import { cn } from "@/lib/utils"; import { UnifiedRepeaterConfig, RepeaterColumnConfig, DEFAULT_REPEATER_CONFIG, RENDER_MODE_OPTIONS, MODAL_SIZE_OPTIONS, } from "@/types/unified-repeater"; // 테이블 엔티티 관계 정보 interface TableRelation { tableName: string; tableLabel: string; foreignKeyColumn: string; // 저장 테이블의 FK 컬럼 referenceColumn: string; // 마스터 테이블의 PK 컬럼 } interface UnifiedRepeaterConfigPanelProps { config: UnifiedRepeaterConfig; onChange: (config: UnifiedRepeaterConfig) => void; currentTableName?: string; screenTableName?: string; tableColumns?: any[]; menuObjid?: number | string; // 🆕 메뉴 ID (채번 규칙 조회용) } interface ColumnOption { columnName: string; displayName: string; inputType?: string; detailSettings?: { codeGroup?: string; referenceTable?: string; referenceColumn?: string; displayColumn?: string; format?: string; }; } interface EntityColumnOption { columnName: string; displayName: string; referenceTable?: string; referenceColumn?: string; displayColumn?: string; } interface CalculationRule { id: string; targetColumn: string; formula: string; label?: string; } export const UnifiedRepeaterConfigPanel: React.FC = ({ config: propConfig, onChange, currentTableName: propCurrentTableName, screenTableName, menuObjid, }) => { const currentTableName = screenTableName || propCurrentTableName; // config 안전하게 초기화 const config: UnifiedRepeaterConfig = useMemo(() => ({ ...DEFAULT_REPEATER_CONFIG, ...propConfig, renderMode: propConfig?.renderMode || DEFAULT_REPEATER_CONFIG.renderMode, dataSource: { ...DEFAULT_REPEATER_CONFIG.dataSource, ...propConfig?.dataSource, }, columns: propConfig?.columns || [], modal: { ...DEFAULT_REPEATER_CONFIG.modal, ...propConfig?.modal, }, features: { ...DEFAULT_REPEATER_CONFIG.features, ...propConfig?.features, }, }), [propConfig]); // 상태 관리 const [currentTableColumns, setCurrentTableColumns] = useState([]); // 현재 테이블 컬럼 const [entityColumns, setEntityColumns] = useState([]); // 엔티티 타입 컬럼 const [sourceTableColumns, setSourceTableColumns] = useState([]); // 소스(엔티티) 테이블 컬럼 const [calculationRules, setCalculationRules] = useState([]); const [loadingColumns, setLoadingColumns] = useState(false); const [loadingSourceColumns, setLoadingSourceColumns] = useState(false); // 저장 테이블 관련 상태 const [allTables, setAllTables] = useState>([]); const [loadingTables, setLoadingTables] = useState(false); const [relatedTables, setRelatedTables] = useState([]); // 현재 테이블과 연관된 테이블 목록 const [loadingRelations, setLoadingRelations] = useState(false); const [tableComboboxOpen, setTableComboboxOpen] = useState(false); // 테이블 Combobox 열림 상태 // 🆕 확장된 컬럼 (상세 설정 표시용) const [expandedColumn, setExpandedColumn] = useState(null); // 🆕 채번 규칙 목록 const [numberingRules, setNumberingRules] = useState([]); const [loadingNumberingRules, setLoadingNumberingRules] = useState(false); // 🆕 대상 메뉴 목록 (채번 규칙 선택용) const [parentMenus, setParentMenus] = useState([]); const [loadingMenus, setLoadingMenus] = useState(false); // 🆕 선택된 메뉴 OBJID (컬럼별로 저장, 한 번 선택하면 공유) const [selectedMenuObjid, setSelectedMenuObjid] = useState(() => { // 기존 config에서 저장된 값이 있으면 복원 const existingAutoFill = config.columns.find(c => c.autoFill?.type === "numbering" && c.autoFill.selectedMenuObjid); return existingAutoFill?.autoFill?.selectedMenuObjid || (menuObjid ? Number(menuObjid) : undefined); }); // 자동 입력 타입 옵션 const autoFillOptions = [ { value: "none", label: "없음" }, { value: "currentDate", label: "현재 날짜" }, { value: "currentDateTime", label: "현재 날짜+시간" }, { value: "sequence", label: "순번 (1, 2, 3...)" }, { value: "numbering", label: "채번 규칙" }, { value: "fromMainForm", label: "메인 폼에서 복사" }, { value: "fixed", label: "고정값" }, ]; // 🆕 대상 메뉴 목록 로드 (사용자 메뉴의 레벨 2) useEffect(() => { const loadMenus = async () => { setLoadingMenus(true); try { const { apiClient } = await import("@/lib/api/client"); const response = await apiClient.get("/admin/menus"); if (response.data.success && response.data.data) { const allMenus = response.data.data; // 사용자 메뉴(menu_type='1')의 레벨 2만 필터링 const level2UserMenus = allMenus.filter((menu: any) => menu.menu_type === '1' && menu.lev === 2 ); setParentMenus(level2UserMenus); } } catch (error) { console.error("부모 메뉴 로드 실패:", error); } finally { setLoadingMenus(false); } }; loadMenus(); }, []); // 🆕 채번 규칙 로드 (선택된 메뉴 기준) useEffect(() => { const loadNumberingRules = async () => { // 메뉴가 선택되지 않았으면 로드하지 않음 if (!selectedMenuObjid) { setNumberingRules([]); return; } setLoadingNumberingRules(true); try { const result = await getAvailableNumberingRules(selectedMenuObjid); if (result?.success && result.data) { setNumberingRules(result.data); } } catch (error) { console.error("채번 규칙 로드 실패:", error); setNumberingRules([]); } finally { setLoadingNumberingRules(false); } }; loadNumberingRules(); }, [selectedMenuObjid]); // 전체 테이블 목록 로드 useEffect(() => { const loadTables = async () => { setLoadingTables(true); try { const response = await tableManagementApi.getTableList(); if (response.success && response.data) { setAllTables(response.data.map((t: any) => ({ tableName: t.tableName || t.table_name, displayName: t.displayName || t.table_label || t.tableName || t.table_name, }))); } } catch (error) { console.error("테이블 목록 로드 실패:", error); } finally { setLoadingTables(false); } }; loadTables(); }, []); // 현재 테이블과 연관된 테이블 목록 로드 (엔티티 관계 기반) useEffect(() => { const loadRelatedTables = async () => { if (!currentTableName) { setRelatedTables([]); return; } setLoadingRelations(true); try { // column_labels에서 현재 테이블을 reference_table로 참조하는 테이블 찾기 const { apiClient } = await import("@/lib/api/client"); const response = await apiClient.get(`/table-management/columns/${currentTableName}/referenced-by`); if (response.data.success && response.data.data) { const relations: TableRelation[] = response.data.data.map((rel: any) => ({ tableName: rel.tableName || rel.table_name, tableLabel: rel.tableLabel || rel.table_label || rel.tableName || rel.table_name, foreignKeyColumn: rel.columnName || rel.column_name, // FK 컬럼 referenceColumn: rel.referenceColumn || rel.reference_column || "id", // PK 컬럼 })); setRelatedTables(relations); } } catch (error) { console.error("연관 테이블 로드 실패:", error); setRelatedTables([]); } finally { setLoadingRelations(false); } }; loadRelatedTables(); }, [currentTableName]); // 설정 업데이트 헬퍼 const updateConfig = useCallback( (updates: Partial) => { onChange({ ...config, ...updates }); }, [config, onChange], ); const updateDataSource = useCallback( (field: string, value: any) => { updateConfig({ dataSource: { ...config.dataSource, [field]: value }, }); }, [config.dataSource, updateConfig], ); const updateModal = useCallback( (field: string, value: any) => { updateConfig({ modal: { ...config.modal, [field]: value }, }); }, [config.modal, updateConfig], ); const updateFeatures = useCallback( (field: string, value: boolean) => { updateConfig({ features: { ...config.features, [field]: value }, }); }, [config.features, updateConfig], ); // 저장 테이블 선택 핸들러 - 엔티티 관계에서 FK/PK 자동 설정 const handleSaveTableSelect = useCallback((tableName: string) => { // 빈 값 선택 시 (현재 테이블로 복원) if (!tableName || tableName === currentTableName) { updateConfig({ useCustomTable: false, mainTableName: undefined, foreignKeyColumn: undefined, foreignKeySourceColumn: undefined, }); return; } // 연관 테이블에서 FK 관계 찾기 const relation = relatedTables.find(r => r.tableName === tableName); if (relation) { // 엔티티 관계가 있으면 자동으로 FK/PK 설정 updateConfig({ useCustomTable: true, mainTableName: tableName, foreignKeyColumn: relation.foreignKeyColumn, foreignKeySourceColumn: relation.referenceColumn, }); } else { // 엔티티 관계가 없으면 직접 입력 필요 updateConfig({ useCustomTable: true, mainTableName: tableName, foreignKeyColumn: undefined, foreignKeySourceColumn: "id", }); } }, [currentTableName, relatedTables, updateConfig]); // 저장 테이블 컬럼 로드 (저장 테이블이 설정되면 해당 테이블, 아니면 현재 화면 테이블) // 실제 저장할 테이블의 컬럼을 보여줘야 함 const targetTableForColumns = config.useCustomTable && config.mainTableName ? config.mainTableName : currentTableName; useEffect(() => { const loadCurrentTableColumns = async () => { if (!targetTableForColumns) { setCurrentTableColumns([]); setEntityColumns([]); return; } setLoadingColumns(true); try { const columnData = await tableTypeApi.getColumns(targetTableForColumns); const cols: ColumnOption[] = []; const entityCols: EntityColumnOption[] = []; for (const c of columnData) { // detailSettings 파싱 let detailSettings: any = null; if (c.detailSettings) { try { detailSettings = typeof c.detailSettings === "string" ? JSON.parse(c.detailSettings) : c.detailSettings; } catch (e) { console.warn("detailSettings 파싱 실패:", c.detailSettings); } } const col: ColumnOption = { columnName: c.columnName || c.column_name, displayName: c.displayName || c.columnLabel || c.columnName || c.column_name, inputType: c.inputType || c.input_type, detailSettings: detailSettings ? { codeGroup: detailSettings.codeGroup, referenceTable: detailSettings.referenceTable, referenceColumn: detailSettings.referenceColumn, displayColumn: detailSettings.displayColumn, format: detailSettings.format, } : undefined, }; cols.push(col); // 엔티티 타입 컬럼 감지 if (col.inputType === "entity") { const referenceTable = detailSettings?.referenceTable || c.referenceTable; const referenceColumn = detailSettings?.referenceColumn || c.referenceColumn || "id"; const displayColumn = detailSettings?.displayColumn || c.displayColumn; if (referenceTable) { entityCols.push({ columnName: col.columnName, displayName: col.displayName, referenceTable, referenceColumn, displayColumn, }); } } } setCurrentTableColumns(cols); setEntityColumns(entityCols); } catch (error) { console.error("저장 테이블 컬럼 로드 실패:", error); setCurrentTableColumns([]); setEntityColumns([]); } finally { setLoadingColumns(false); } }; loadCurrentTableColumns(); }, [targetTableForColumns]); // 소스(엔티티) 테이블 컬럼 로드 (모달 모드일 때) useEffect(() => { const loadSourceTableColumns = async () => { const sourceTable = config.dataSource?.sourceTable; if (!sourceTable) { setSourceTableColumns([]); return; } setLoadingSourceColumns(true); try { const columnData = await tableTypeApi.getColumns(sourceTable); const cols: ColumnOption[] = columnData.map((c: any) => ({ columnName: c.columnName || c.column_name, displayName: c.displayName || c.columnLabel || c.columnName || c.column_name, inputType: c.inputType || c.input_type, })); setSourceTableColumns(cols); } catch (error) { console.error("소스 테이블 컬럼 로드 실패:", error); setSourceTableColumns([]); } finally { setLoadingSourceColumns(false); } }; if (config.renderMode === "modal") { loadSourceTableColumns(); } }, [config.dataSource?.sourceTable, config.renderMode]); // 컬럼 토글 (현재 테이블 컬럼 - 입력용) const toggleInputColumn = (column: ColumnOption) => { const existingIndex = config.columns.findIndex((c) => c.key === column.columnName); if (existingIndex >= 0) { const newColumns = config.columns.filter((c) => c.key !== column.columnName); updateConfig({ columns: newColumns }); } else { // 컬럼의 inputType과 detailSettings 정보 포함 const newColumn: RepeaterColumnConfig = { key: column.columnName, title: column.displayName, width: "auto", visible: true, editable: true, inputType: column.inputType || "text", detailSettings: column.detailSettings ? { codeGroup: column.detailSettings.codeGroup, referenceTable: column.detailSettings.referenceTable, referenceColumn: column.detailSettings.referenceColumn, displayColumn: column.detailSettings.displayColumn, format: column.detailSettings.format, } : undefined, }; updateConfig({ columns: [...config.columns, newColumn] }); } }; // 🆕 소스 컬럼 토글 - columns 배열에 isSourceDisplay: true로 추가 const toggleSourceDisplayColumn = (column: ColumnOption) => { const exists = config.columns.some((c) => c.key === column.columnName && c.isSourceDisplay); if (exists) { // 제거 updateConfig({ columns: config.columns.filter((c) => c.key !== column.columnName) }); } else { // 추가 (isSourceDisplay: true) const newColumn: RepeaterColumnConfig = { key: column.columnName, title: column.displayName, width: "auto", visible: true, editable: false, // 소스 표시 컬럼은 편집 불가 isSourceDisplay: true, }; updateConfig({ columns: [...config.columns, newColumn] }); } }; const isColumnAdded = (columnName: string) => { return config.columns.some((c) => c.key === columnName && !c.isSourceDisplay); }; const isSourceColumnSelected = (columnName: string) => { return config.columns.some((c) => c.key === columnName && c.isSourceDisplay); }; // 컬럼 속성 업데이트 const updateColumnProp = (key: string, field: keyof RepeaterColumnConfig, value: any) => { const newColumns = config.columns.map((col) => (col.key === key ? { ...col, [field]: value } : col)); updateConfig({ columns: newColumns }); }; // 계산 규칙 추가 const addCalculationRule = () => { setCalculationRules(prev => [ ...prev, { id: `calc_${Date.now()}`, targetColumn: "", formula: "" } ]); }; // 계산 규칙 삭제 const removeCalculationRule = (id: string) => { setCalculationRules(prev => prev.filter(r => r.id !== id)); }; // 계산 규칙 업데이트 const updateCalculationRule = (id: string, field: keyof CalculationRule, value: string) => { setCalculationRules(prev => prev.map(r => r.id === id ? { ...r, [field]: value } : r) ); }; // 엔티티 컬럼 선택 시 소스 테이블 자동 설정 const handleEntityColumnSelect = (columnName: string) => { const selectedEntity = entityColumns.find(c => c.columnName === columnName); if (selectedEntity) { console.log("엔티티 컬럼 선택:", selectedEntity); // 소스 테이블 컬럼에서 라벨 정보 찾기 const displayColInfo = sourceTableColumns.find(c => c.columnName === selectedEntity.displayColumn); const displayLabel = displayColInfo?.displayName || selectedEntity.displayColumn || ""; updateConfig({ dataSource: { ...config.dataSource, sourceTable: selectedEntity.referenceTable || "", foreignKey: selectedEntity.columnName, referenceKey: selectedEntity.referenceColumn || "id", displayColumn: selectedEntity.displayColumn, }, modal: { ...config.modal, searchFields: selectedEntity.displayColumn ? [selectedEntity.displayColumn] : [], // 라벨 포함 형식으로 저장 sourceDisplayColumns: selectedEntity.displayColumn ? [{ key: selectedEntity.displayColumn, label: displayLabel }] : [], }, }); } }; // 모드 여부 const isInlineMode = config.renderMode === "inline"; const isModalMode = config.renderMode === "modal"; // 엔티티 컬럼 제외한 입력 가능 컬럼 (FK 컬럼 제외) const inputableColumns = useMemo(() => { const fkColumn = config.dataSource?.foreignKey; return currentTableColumns.filter(col => col.columnName !== fkColumn && // FK 컬럼 제외 col.inputType !== "entity" // 다른 엔티티 컬럼도 제외 (필요시) ); }, [currentTableColumns, config.dataSource?.foreignKey]); return (
기본 컬럼 {/* 기본 설정 탭 */} {/* 렌더링 모드 */}
{/* 저장 대상 테이블 */}
{/* 현재 선택된 테이블 표시 (기존 테이블 UI와 동일한 스타일) */}

{config.useCustomTable && config.mainTableName ? (allTables.find(t => t.tableName === config.mainTableName)?.displayName || config.mainTableName) : (currentTableName || "미설정") }

{config.useCustomTable && config.mainTableName && config.foreignKeyColumn && (

FK: {config.foreignKeyColumn} → {currentTableName}.{config.foreignKeySourceColumn || "id"}

)} {!config.useCustomTable && currentTableName && (

화면 메인 테이블

)}
{/* 테이블 변경 Combobox */} 테이블을 찾을 수 없습니다. {/* 현재 테이블 (기본) */} {currentTableName && ( { handleSaveTableSelect(currentTableName); setTableComboboxOpen(false); }} className="text-xs" > {currentTableName} (기본) )} {/* 연관 테이블 (엔티티 관계) */} {relatedTables.length > 0 && ( {relatedTables.map((rel) => ( { handleSaveTableSelect(rel.tableName); setTableComboboxOpen(false); }} className="text-xs" > {rel.tableLabel} ({rel.foreignKeyColumn}) ))} )} {/* 전체 테이블 목록 */} {allTables .filter(t => t.tableName !== currentTableName && !relatedTables.some(r => r.tableName === t.tableName)) .map((table) => ( { handleSaveTableSelect(table.tableName); setTableComboboxOpen(false); }} className="text-xs" > {table.displayName} )) } {/* FK 직접 입력 (연관 테이블이 아닌 경우만) */} {config.useCustomTable && config.mainTableName && !relatedTables.some(r => r.tableName === config.mainTableName) && (

엔티티 관계가 설정되지 않은 테이블입니다. FK 컬럼을 직접 입력하세요.

updateConfig({ foreignKeyColumn: e.target.value })} placeholder="예: master_id" className="h-7 text-xs" />
updateConfig({ foreignKeySourceColumn: e.target.value })} placeholder="id" className="h-7 text-xs" />
)}
{/* 현재 화면 정보 */}
{currentTableName ? (

{currentTableName}

컬럼 {currentTableColumns.length}개 / 엔티티 {entityColumns.length}개

) : (

화면에 테이블을 먼저 설정해주세요

)}
{/* 모달 모드: 엔티티 컬럼 선택 */} {isModalMode && ( <>

모달에서 검색할 엔티티를 선택하세요 (FK만 저장됨)

{entityColumns.length > 0 ? ( ) : (

{loadingColumns ? "로딩 중..." : "엔티티 타입 컬럼이 없습니다"}

)} {/* 선택된 엔티티 정보 */} {config.dataSource?.sourceTable && (

선택된 엔티티

검색 테이블: {config.dataSource.sourceTable}

저장 컬럼: {config.dataSource.foreignKey} (FK)

)}
)} {/* 기능 옵션 */}
updateFeatures("showAddButton", !!checked)} />
updateFeatures("showDeleteButton", !!checked)} />
updateFeatures("inlineEdit", !!checked)} />
updateFeatures("multiSelect", !!checked)} />
updateFeatures("showRowNumber", !!checked)} />
updateFeatures("selectable", !!checked)} />
{/* 컬럼 설정 탭 - 🆕 통합 컬럼 선택 */} {/* 통합 컬럼 선택 */}

{isModalMode ? "표시할 컬럼과 입력 컬럼을 선택하세요. 아이콘으로 표시/입력 구분" : "입력받을 컬럼을 선택하세요" }

{/* 모달 모드: 소스 테이블 컬럼 (표시용) */} {isModalMode && config.dataSource?.sourceTable && ( <>
소스 테이블 ({config.dataSource.sourceTable}) - 표시용
{loadingSourceColumns ? (

로딩 중...

) : sourceTableColumns.length === 0 ? (

컬럼 정보가 없습니다

) : (
{sourceTableColumns.map((column) => (
toggleSourceDisplayColumn(column)} > toggleSourceDisplayColumn(column)} className="pointer-events-none h-3.5 w-3.5" /> {column.displayName} 표시
))}
)} )} {/* 저장 테이블 컬럼 (입력용) */}
저장 테이블 ({targetTableForColumns || "미선택"}) - 입력용
{loadingColumns ? (

로딩 중...

) : inputableColumns.length === 0 ? (

컬럼 정보가 없습니다

) : (
{inputableColumns.map((column) => (
toggleInputColumn(column)} > toggleInputColumn(column)} className="pointer-events-none h-3.5 w-3.5" /> {column.displayName} {column.inputType}
))}
)}
{/* 선택된 컬럼 상세 설정 - 🆕 모든 컬럼 통합, 순서 변경 가능 */} {config.columns.length > 0 && ( <>
{config.columns.map((col, index) => (
{/* 컬럼 헤더 (드래그 가능) */}
{ e.dataTransfer.setData("columnIndex", String(index)); }} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { e.preventDefault(); const fromIndex = parseInt(e.dataTransfer.getData("columnIndex"), 10); if (fromIndex !== index) { const newColumns = [...config.columns]; const [movedCol] = newColumns.splice(fromIndex, 1); newColumns.splice(index, 0, movedCol); updateConfig({ columns: newColumns }); } }} > {/* 확장/축소 버튼 (입력 컬럼만) */} {!col.isSourceDisplay && ( )} {col.isSourceDisplay ? ( ) : ( )} updateColumnProp(col.key, "title", e.target.value)} placeholder="제목" className="h-6 flex-1 text-xs" /> {/* 히든 토글 (입력 컬럼만) */} {!col.isSourceDisplay && ( )} {/* 자동입력 표시 아이콘 */} {!col.isSourceDisplay && col.autoFill?.type && col.autoFill.type !== "none" && ( )} {/* 편집 가능 체크박스 */} {!col.isSourceDisplay && ( updateColumnProp(col.key, "editable", !!checked)} title="편집 가능" /> )}
{/* 확장된 상세 설정 (입력 컬럼만) */} {!col.isSourceDisplay && expandedColumn === col.key && (
{/* 자동 입력 설정 */}
{/* 채번 규칙 선택 */} {col.autoFill?.type === "numbering" && (
{/* 대상 메뉴 선택 */}

이 입력 필드가 어느 메뉴에 속할지 선택하세요 (해당 메뉴의 채번규칙이 적용됩니다)

{/* 채번 규칙 선택 (메뉴 선택 후) */} {selectedMenuObjid ? (
{loadingNumberingRules ? (

규칙 로딩 중...

) : numberingRules.length === 0 ? (
선택된 메뉴에 사용 가능한 채번 규칙이 없습니다
) : ( )} {col.autoFill?.numberingRuleId && (

저장 시 채번 API를 통해 자동 생성됩니다.

)}
) : (
먼저 대상 메뉴를 선택하세요
)}
)} {/* 메인 폼에서 복사 설정 */} {col.autoFill?.type === "fromMainForm" && (
updateColumnProp(col.key, "autoFill", { ...col.autoFill, sourceField: e.target.value, })} placeholder="order_no" className="h-6 text-xs" />
)} {/* 고정값 설정 */} {col.autoFill?.type === "fixed" && (
updateColumnProp(col.key, "autoFill", { ...col.autoFill, fixedValue: e.target.value, })} placeholder="고정값 입력" className="h-6 text-xs" />
)}
)}
))}
)} {/* 계산 규칙 */} {(isModalMode || isInlineMode) && config.columns.length > 0 && ( <>

예: 금액 = 수량 * 단가

{calculationRules.map((rule) => (
= updateCalculationRule(rule.id, "formula", e.target.value)} placeholder="quantity * unit_price" className="h-7 flex-1 text-xs" />
))} {calculationRules.length === 0 && (

계산 규칙이 없습니다

)}
)}
); }; UnifiedRepeaterConfigPanel.displayName = "UnifiedRepeaterConfigPanel"; export default UnifiedRepeaterConfigPanel;