"use client"; import React, { useState, useMemo, useEffect } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Card, CardContent } from "@/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Plus, X, ChevronDown, ChevronRight } from "lucide-react"; import { SelectedItemsDetailInputConfig, AdditionalFieldDefinition, FieldGroup, DisplayItem, DisplayItemType, EmptyBehavior, DisplayFieldFormat } from "./types"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Check, ChevronsUpDown } from "lucide-react"; import { cn } from "@/lib/utils"; import { getSecondLevelMenus, getCategoryColumns, getCategoryColumnsByMenu, getCategoryValues } from "@/lib/api/tableCategoryValue"; import { CalculationBuilder } from "./CalculationBuilder"; export interface SelectedItemsDetailInputConfigPanelProps { config: SelectedItemsDetailInputConfig; onChange: (config: Partial) => void; sourceTableColumns?: Array<{ columnName: string; columnLabel?: string; dataType?: string; inputType?: string }>; // πŸ†• 원본 ν…Œμ΄λΈ” 컬럼 (inputType μΆ”κ°€) targetTableColumns?: Array<{ columnName: string; columnLabel?: string; dataType?: string; inputType?: string; codeCategory?: string }>; // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” 컬럼 (inputType, codeCategory μΆ”κ°€) allTables?: Array<{ tableName: string; displayName?: string }>; screenTableName?: string; // πŸ†• ν˜„μž¬ ν™”λ©΄μ˜ ν…Œμ΄λΈ”λͺ… (μžλ™ μ„€μ •μš©) onSourceTableChange?: (tableName: string) => void; // πŸ†• 원본 ν…Œμ΄λΈ” λ³€κ²½ 콜백 onTargetTableChange?: (tableName: string) => void; // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” λ³€κ²½ 콜백 (κΈ°μ‘΄ onTableChange λŒ€μ²΄) } /** * SelectedItemsDetailInput μ„€μ • νŒ¨λ„ * μ»΄ν¬λ„ŒνŠΈμ˜ 섀정값듀을 νŽΈμ§‘ν•  수 μžˆλŠ” UI 제곡 */ export const SelectedItemsDetailInputConfigPanel: React.FC = ({ config, onChange, sourceTableColumns = [], // πŸ†• 원본 ν…Œμ΄λΈ” 컬럼 targetTableColumns = [], // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” 컬럼 allTables = [], screenTableName, // πŸ†• ν˜„μž¬ ν™”λ©΄μ˜ ν…Œμ΄λΈ”λͺ… onSourceTableChange, // πŸ†• 원본 ν…Œμ΄λΈ” λ³€κ²½ 콜백 onTargetTableChange, // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” λ³€κ²½ 콜백 }) => { const [localFields, setLocalFields] = useState(config.additionalFields || []); const [displayColumns, setDisplayColumns] = useState>(config.displayColumns || []); const [fieldPopoverOpen, setFieldPopoverOpen] = useState>({}); // πŸ†• ν•„λ“œ κ·Έλ£Ή μƒνƒœ const [localFieldGroups, setLocalFieldGroups] = useState(config.fieldGroups || []); // πŸ†• κ·Έλ£Ή μž…λ ₯값을 μœ„ν•œ 둜컬 μƒνƒœ (포컀슀 μœ μ§€μš©) const [localGroupInputs, setLocalGroupInputs] = useState>({}); // πŸ†• ν•„λ“œ μž…λ ₯값을 μœ„ν•œ 둜컬 μƒνƒœ (포컀슀 μœ μ§€μš©) const [localFieldInputs, setLocalFieldInputs] = useState>({}); // πŸ†• ν‘œμ‹œ ν•­λͺ©μ˜ μž…λ ₯값을 μœ„ν•œ 둜컬 μƒνƒœ (포컀슀 μœ μ§€μš©) const [localDisplayItemInputs, setLocalDisplayItemInputs] = useState>>({}); // πŸ†• λΆ€λͺ¨ 데이터 λ§€ν•‘μ˜ κΈ°λ³Έκ°’ μž…λ ₯을 μœ„ν•œ 둜컬 μƒνƒœ (포컀슀 μœ μ§€μš©) const [localMappingInputs, setLocalMappingInputs] = useState>({}); // πŸ†• 그룹별 펼침/μ ‘νž˜ μƒνƒœ const [expandedGroups, setExpandedGroups] = useState>({}); // πŸ†• 그룹별 ν‘œμ‹œ ν•­λͺ© μ„€μ • 펼침/μ ‘νž˜ μƒνƒœ const [expandedDisplayItems, setExpandedDisplayItems] = useState>({}); // πŸ†• μΉ΄ν…Œκ³ λ¦¬ λ§€ν•‘ μ•„μ½”λ””μ–Έ 펼침/μ ‘νž˜ μƒνƒœ const [expandedCategoryMappings, setExpandedCategoryMappings] = useState>({ discountType: false, roundingType: false, roundingUnit: false, }); // πŸ†• 원본 ν…Œμ΄λΈ” 선택 μƒνƒœ const [sourceTableSelectOpen, setSourceTableSelectOpen] = useState(false); const [sourceTableSearchValue, setSourceTableSearchValue] = useState(""); // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” 선택 μƒνƒœ (κΈ°μ‘΄ tableSelectOpen) const [tableSelectOpen, setTableSelectOpen] = useState(false); const [tableSearchValue, setTableSearchValue] = useState(""); // πŸ†• μΉ΄ν…Œκ³ λ¦¬ 맀핑을 μœ„ν•œ μƒνƒœ const [secondLevelMenus, setSecondLevelMenus] = useState>([]); const [categoryColumns, setCategoryColumns] = useState>>({}); const [categoryValues, setCategoryValues] = useState>>({}); // πŸ†• λΆ€λͺ¨ 데이터 λ§€ν•‘: 각 맀핑별 μ†ŒμŠ€ ν…Œμ΄λΈ” 컬럼 μƒνƒœ const [mappingSourceColumns, setMappingSourceColumns] = useState>>({}); // πŸ†• μΆ”κ°€ μž…λ ₯ ν•„λ“œλ³„ μžλ™ μ±„μš°κΈ° ν…Œμ΄λΈ” 컬럼 μƒνƒœ const [autoFillTableColumns, setAutoFillTableColumns] = useState>>({}); // πŸ†• 원본/λŒ€μƒ ν…Œμ΄λΈ” 컬럼 μƒνƒœ (λ‚΄λΆ€μ—μ„œ λ‘œλ“œ) const [loadedSourceTableColumns, setLoadedSourceTableColumns] = useState>([]); const [loadedTargetTableColumns, setLoadedTargetTableColumns] = useState>([]); // πŸ†• 원본 ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ useEffect(() => { if (!config.sourceTable) { setLoadedSourceTableColumns([]); return; } const loadColumns = async () => { try { console.log("πŸ” 원본 ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ:", config.sourceTable); const { tableManagementApi } = await import("@/lib/api/tableManagement"); const response = await tableManagementApi.getColumnList(config.sourceTable); if (response.success && response.data) { const columns = response.data.columns || []; setLoadedSourceTableColumns(columns.map((col: any) => ({ columnName: col.columnName, columnLabel: col.displayName || col.columnLabel || col.columnName, dataType: col.dataType, inputType: col.inputType, // πŸ”§ inputType μΆ”κ°€ }))); console.log("βœ… 원본 ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ 성곡:", columns.length); } } catch (error) { console.error("❌ 원본 ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ 였λ₯˜:", error); } }; loadColumns(); }, [config.sourceTable]); // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ useEffect(() => { if (!config.targetTable) { setLoadedTargetTableColumns([]); return; } const loadColumns = async () => { try { console.log("πŸ” λŒ€μƒ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ:", config.targetTable); const { tableManagementApi } = await import("@/lib/api/tableManagement"); const response = await tableManagementApi.getColumnList(config.targetTable); if (response.success && response.data) { const columns = response.data.columns || []; setLoadedTargetTableColumns(columns.map((col: any) => ({ columnName: col.columnName, columnLabel: col.displayName || col.columnLabel || col.columnName, dataType: col.dataType, inputType: col.inputType, // πŸ”§ inputType μΆ”κ°€ }))); console.log("βœ… λŒ€μƒ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ 성곡:", columns.length); } } catch (error) { console.error("❌ λŒ€μƒ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ 였λ₯˜:", error); } }; loadColumns(); }, [config.targetTable]); // πŸ†• ν•„λ“œ κ·Έλ£Ή λ³€κ²½ μ‹œ 둜컬 μž…λ ₯ μƒνƒœ 동기화 useEffect(() => { setLocalFieldGroups(config.fieldGroups || []); // 둜컬 μž…λ ₯ μƒνƒœλŠ” κΈ°μ‘΄ κ°’ λ³΄μ‘΄ν•˜λ©΄μ„œ μƒˆ 그룹만 μΆ”κ°€ setLocalGroupInputs(prev => { const newInputs = { ...prev }; (config.fieldGroups || []).forEach(group => { if (!(group.id in newInputs)) { newInputs[group.id] = { id: group.id, title: group.title, description: group.description, order: group.order, }; } }); return newInputs; }); // πŸ”§ ν‘œμ‹œ ν•­λͺ©μ΄ μžˆλŠ” 그룹은 아코디언을 μ—΄λ¦° μƒνƒœλ‘œ μ΄ˆκΈ°ν™” setExpandedDisplayItems(prev => { const newExpanded = { ...prev }; (config.fieldGroups || []).forEach(group => { // 이미 μƒνƒœκ°€ 있으면 μœ μ§€, μ—†μœΌλ©΄ displayItemsκ°€ μžˆμ„ λ•Œλ§Œ μ—΄κΈ° if (!(group.id in newExpanded) && group.displayItems && group.displayItems.length > 0) { newExpanded[group.id] = true; } }); return newExpanded; }); }, [config.fieldGroups]); // πŸ†• 초기 λ Œλ”λ§ μ‹œ κΈ°μ‘΄ ν•„λ“œλ“€μ˜ autoFillFromTable 컬럼 λ‘œλ“œ useEffect(() => { if (!localFields || localFields.length === 0) return; localFields.forEach((field, index) => { if (field.autoFillFromTable && !autoFillTableColumns[index]) { console.log(`πŸ” [μ΄ˆκΈ°ν™”] ν•„λ“œ ${index}의 κΈ°μ‘΄ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ:`, field.autoFillFromTable); loadAutoFillTableColumns(field.autoFillFromTable, index); } }); }, []); // 초기 ν•œ 번만 μ‹€ν–‰ // πŸ†• μžλ™ μ±„μš°κΈ° ν…Œμ΄λΈ” 선택 μ‹œ 컬럼 λ‘œλ“œ const loadAutoFillTableColumns = async (tableName: string, fieldIndex: number) => { if (!tableName) { setAutoFillTableColumns(prev => ({ ...prev, [fieldIndex]: [] })); return; } try { console.log(`πŸ” [ν•„λ“œ ${fieldIndex}] μžλ™ μ±„μš°κΈ° ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ:`, tableName); const { tableManagementApi } = await import("@/lib/api/tableManagement"); const response = await tableManagementApi.getColumnList(tableName); if (response.success && response.data) { const columns = response.data.columns || []; setAutoFillTableColumns(prev => ({ ...prev, [fieldIndex]: columns.map((col: any) => ({ columnName: col.columnName, columnLabel: col.displayName || col.columnLabel || col.columnName, dataType: col.dataType, })) })); console.log(`βœ… [ν•„λ“œ ${fieldIndex}] 컬럼 λ‘œλ“œ 성곡:`, columns.length); } else { console.error(`❌ [ν•„λ“œ ${fieldIndex}] 컬럼 λ‘œλ“œ μ‹€νŒ¨:`, response); } } catch (error) { console.error(`❌ [ν•„λ“œ ${fieldIndex}] 컬럼 λ‘œλ“œ 였λ₯˜:`, error); } }; // πŸ†• μ†ŒμŠ€ ν…Œμ΄λΈ” 선택 μ‹œ 컬럼 λ‘œλ“œ const loadMappingSourceColumns = async (tableName: string, mappingIndex: number) => { try { console.log(`πŸ” [λ§€ν•‘ ${mappingIndex}] μ†ŒμŠ€ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ:`, tableName); const { tableManagementApi } = await import("@/lib/api/tableManagement"); const response = await tableManagementApi.getColumnList(tableName); if (response.success && response.data) { const columns = response.data.columns || []; setMappingSourceColumns(prev => ({ ...prev, [mappingIndex]: columns.map((col: any) => ({ columnName: col.columnName, columnLabel: col.displayName || col.columnLabel || col.columnName, dataType: col.dataType, })) })); console.log(`βœ… [λ§€ν•‘ ${mappingIndex}] 컬럼 λ‘œλ“œ 성곡:`, columns.length); } else { console.error(`❌ [λ§€ν•‘ ${mappingIndex}] 컬럼 λ‘œλ“œ μ‹€νŒ¨:`, response); } } catch (error) { console.error(`❌ [λ§€ν•‘ ${mappingIndex}] 컬럼 λ‘œλ“œ 였λ₯˜:`, error); } }; // πŸ†• μ €μž₯된 λΆ€λͺ¨ 데이터 λ§€ν•‘μ˜ 컬럼 μžλ™ λ‘œλ“œ useEffect(() => { const loadSavedMappingColumns = async () => { if (!config.parentDataMapping || config.parentDataMapping.length === 0) { console.log("πŸ“­ [λΆ€λͺ¨ 데이터 λ§€ν•‘] 맀핑이 μ—†μŠ΅λ‹ˆλ‹€"); return; } console.log("πŸ” [λΆ€λͺ¨ 데이터 λ§€ν•‘] μ €μž₯된 λ§€ν•‘ 컬럼 μžλ™ λ‘œλ“œ μ‹œμž‘:", config.parentDataMapping.length); for (let i = 0; i < config.parentDataMapping.length; i++) { const mapping = config.parentDataMapping[i]; // 이미 λ‘œλ“œλœ 컬럼이 있으면 μŠ€ν‚΅ if (mappingSourceColumns[i] && mappingSourceColumns[i].length > 0) { console.log(`⏭️ [λ§€ν•‘ ${i}] 이미 λ‘œλ“œλœ 컬럼이 있음`); continue; } // μ†ŒμŠ€ ν…Œμ΄λΈ”μ΄ μ„ νƒλ˜μ–΄ 있으면 컬럼 λ‘œλ“œ if (mapping.sourceTable) { console.log(`πŸ“‘ [λ§€ν•‘ ${i}] μ†ŒμŠ€ ν…Œμ΄λΈ” 컬럼 μžλ™ λ‘œλ“œ:`, mapping.sourceTable); await loadMappingSourceColumns(mapping.sourceTable, i); } } }; loadSavedMappingColumns(); }, [config.parentDataMapping]); // 2레벨 메뉴 λͺ©λ‘ λ‘œλ“œ useEffect(() => { const loadMenus = async () => { const response = await getSecondLevelMenus(); if (response.success && response.data) { setSecondLevelMenus(response.data); } }; loadMenus(); }, []); // 메뉴 선택 μ‹œ μΉ΄ν…Œκ³ λ¦¬ λͺ©λ‘ λ‘œλ“œ const handleMenuSelect = async (menuObjid: number, fieldType: "discountType" | "roundingType" | "roundingUnit") => { console.log("πŸ” [handleMenuSelect] μ‹œμž‘", { menuObjid, fieldType }); // πŸ”§ 1단계: μ•„μ½”λ””μ–Έ λ¨Όμ € μ—΄κΈ° (λ¦¬λ Œλ”λ§ 전에) setExpandedCategoryMappings(prev => { const newState = { ...prev, [fieldType]: true }; console.log("πŸ”„ [handleMenuSelect] μ•„μ½”λ””μ–Έ μ—΄κΈ°:", newState); return newState; }); // πŸ”§ 2단계: 메뉴별 μΉ΄ν…Œκ³ λ¦¬ 컬럼 API 호좜 const response = await getCategoryColumnsByMenu(menuObjid); console.log("πŸ“₯ [handleMenuSelect] API 응닡:", response); if (response.success && response.data) { console.log("βœ… [handleMenuSelect] μΉ΄ν…Œκ³ λ¦¬ 컬럼 데이터:", { fieldType, columns: response.data, count: response.data.length }); // μΉ΄ν…Œκ³ λ¦¬ 컬럼 μƒνƒœ μ—…λ°μ΄νŠΈ setCategoryColumns(prev => { const newState = { ...prev, [fieldType]: response.data }; console.log("πŸ”„ [handleMenuSelect] categoryColumns μ—…λ°μ΄νŠΈ:", newState); return newState; }); } else { console.error("❌ [handleMenuSelect] μΉ΄ν…Œκ³ λ¦¬ 컬럼 λ‘œλ“œ μ‹€νŒ¨:", response); } // πŸ”§ 3단계: valueMapping μ—…λ°μ΄νŠΈ (λ§ˆμ§€λ§‰μ—) const newConfig = { ...config.autoCalculation, valueMapping: { ...config.autoCalculation.valueMapping, _selectedMenus: { ...(config.autoCalculation.valueMapping as any)?._selectedMenus, [fieldType]: menuObjid, }, }, }; console.log("πŸ”„ [handleMenuSelect] valueMapping μ—…λ°μ΄νŠΈ:", newConfig); handleChange("autoCalculation", newConfig); }; // μΉ΄ν…Œκ³ λ¦¬ 선택 μ‹œ μΉ΄ν…Œκ³ λ¦¬ κ°’ λͺ©λ‘ λ‘œλ“œ const handleCategorySelect = async (columnName: string, menuObjid: number, fieldType: "discountType" | "roundingType" | "roundingUnit") => { console.log("πŸ” [handleCategorySelect] μ‹œμž‘", { columnName, menuObjid, fieldType, targetTable: config.targetTable }); if (!config.targetTable) { console.warn("⚠️ [handleCategorySelect] targetTable이 μ—†μŠ΅λ‹ˆλ‹€"); return; } const response = await getCategoryValues(config.targetTable, columnName, false, menuObjid); console.log("πŸ“₯ [handleCategorySelect] API 응닡:", response); if (response.success && response.data) { console.log("βœ… [handleCategorySelect] μΉ΄ν…Œκ³ λ¦¬ κ°’ 데이터:", { fieldType, values: response.data, count: response.data.length }); setCategoryValues(prev => { const newState = { ...prev, [fieldType]: response.data }; console.log("πŸ”„ [handleCategorySelect] categoryValues μ—…λ°μ΄νŠΈ:", newState); return newState; }); } else { console.error("❌ [handleCategorySelect] μΉ΄ν…Œκ³ λ¦¬ κ°’ λ‘œλ“œ μ‹€νŒ¨:", response); } // πŸ”§ μΉ΄ν…Œκ³ λ¦¬ 선택 μ‹œ μ•„μ½”λ””μ–Έ μ—΄κΈ° (이미 μ—΄λ €μžˆμ„ μˆ˜λ„ 있음) setExpandedCategoryMappings(prev => { const newState = { ...prev, [fieldType]: true }; console.log("πŸ”„ [handleCategorySelect] μ•„μ½”λ””μ–Έ μƒνƒœ:", newState); return newState; }); // valueMapping μ—…λ°μ΄νŠΈ const newConfig = { ...config.autoCalculation, valueMapping: { ...config.autoCalculation.valueMapping, _selectedCategories: { ...(config.autoCalculation.valueMapping as any)?._selectedCategories, [fieldType]: columnName, }, }, }; console.log("πŸ”„ [handleCategorySelect] valueMapping μ—…λ°μ΄νŠΈ:", newConfig); handleChange("autoCalculation", newConfig); }; // πŸ†• μ €μž₯된 μ„€μ •μ—μ„œ μΉ΄ν…Œκ³ λ¦¬ 정보 볡원 useEffect(() => { const loadSavedCategories = async () => { console.log("πŸ” [loadSavedCategories] useEffect μ‹€ν–‰", { hasTargetTable: !!config.targetTable, hasAutoCalc: !!config.autoCalculation, hasValueMapping: !!config.autoCalculation?.valueMapping }); if (!config.targetTable || !config.autoCalculation?.valueMapping) { console.warn("⚠️ [loadSavedCategories] targetTable λ˜λŠ” valueMapping이 μ—†μ–΄ μ’…λ£Œ"); return; } const savedMenus = (config.autoCalculation.valueMapping as any)?._selectedMenus; const savedCategories = (config.autoCalculation.valueMapping as any)?._selectedCategories; console.log("πŸ”„ [loadSavedCategories] μ €μž₯된 μΉ΄ν…Œκ³ λ¦¬ μ„€μ • 볡원 μ‹œμž‘:", { savedMenus, savedCategories }); // 각 ν•„λ“œ νƒ€μž…λ³„λ‘œ μ €μž₯된 μΉ΄ν…Œκ³ λ¦¬ κ°’ λ‘œλ“œ const fieldTypes: Array<"discountType" | "roundingType" | "roundingUnit"> = ["discountType", "roundingType", "roundingUnit"]; // πŸ”§ 볡원할 μ•„μ½”λ””μ–Έ μƒνƒœ μ€€λΉ„ const newExpandedState: Record = {}; for (const fieldType of fieldTypes) { const menuObjid = savedMenus?.[fieldType]; const columnName = savedCategories?.[fieldType]; console.log(`πŸ” [loadSavedCategories] ${fieldType} 처리`, { menuObjid, columnName }); // πŸ”§ λ©”λ‰΄λ§Œ μ„ νƒλœ κ²½μš°μ—λ„ μΉ΄ν…Œκ³ λ¦¬ 컬럼 λ‘œλ“œ if (menuObjid) { console.log(`βœ… [loadSavedCategories] ${fieldType} 메뉴 발견, μΉ΄ν…Œκ³ λ¦¬ 컬럼 λ‘œλ“œ μ‹œμž‘:`, { menuObjid }); // πŸ”§ 메뉴가 μ„ νƒλ˜μ–΄ 있으면 μ•„μ½”λ””μ–Έ μ—΄κΈ° newExpandedState[fieldType] = true; // πŸ”§ 메뉴별 μΉ΄ν…Œκ³ λ¦¬ 컬럼 λ‘œλ“œ (μΉ΄ν…Œκ³ λ¦¬ 선택 여뢀와 무관) console.log(`πŸ“‘ [loadSavedCategories] ${fieldType} μΉ΄ν…Œκ³ λ¦¬ 컬럼 API 호좜`, { menuObjid }); const columnsResponse = await getCategoryColumnsByMenu(menuObjid); console.log(`πŸ“₯ [loadSavedCategories] ${fieldType} 컬럼 응닡:`, columnsResponse); if (columnsResponse.success && columnsResponse.data) { setCategoryColumns(prev => { const newState = { ...prev, [fieldType]: columnsResponse.data }; console.log(`πŸ”„ [loadSavedCategories] ${fieldType} categoryColumns μ—…λ°μ΄νŠΈ:`, newState); return newState; }); } else { console.error(`❌ [loadSavedCategories] ${fieldType} 컬럼 λ‘œλ“œ μ‹€νŒ¨:`, columnsResponse); } // πŸ”§ μΉ΄ν…Œκ³ λ¦¬κΉŒμ§€ μ„ νƒλœ κ²½μš°μ—λ§Œ κ°’ λ‘œλ“œ if (columnName) { console.log(`πŸ“‘ [loadSavedCategories] ${fieldType} μΉ΄ν…Œκ³ λ¦¬ κ°’ API 호좜`, { columnName }); const valuesResponse = await getCategoryValues(config.targetTable, columnName, false, menuObjid); console.log(`πŸ“₯ [loadSavedCategories] ${fieldType} κ°’ 응닡:`, valuesResponse); if (valuesResponse.success && valuesResponse.data) { console.log(`βœ… [loadSavedCategories] ${fieldType} μΉ΄ν…Œκ³ λ¦¬ κ°’:`, valuesResponse.data); setCategoryValues(prev => { const newState = { ...prev, [fieldType]: valuesResponse.data }; console.log(`πŸ”„ [loadSavedCategories] ${fieldType} categoryValues μ—…λ°μ΄νŠΈ:`, newState); return newState; }); } else { console.error(`❌ [loadSavedCategories] ${fieldType} κ°’ λ‘œλ“œ μ‹€νŒ¨:`, valuesResponse); } } } } // πŸ”§ μ €μž₯된 섀정이 μžˆλŠ” μ•„μ½”λ””μ–Έλ“€ μ—΄κΈ° if (Object.keys(newExpandedState).length > 0) { console.log("πŸ”“ [loadSavedCategories] μ•„μ½”λ””μ–Έ μ—΄κΈ°:", newExpandedState); setExpandedCategoryMappings(prev => { const finalState = { ...prev, ...newExpandedState }; console.log("πŸ”„ [loadSavedCategories] μ΅œμ’… μ•„μ½”λ””μ–Έ μƒνƒœ:", finalState); return finalState; }); } }; loadSavedCategories(); }, [config.targetTable, config.autoCalculation?.valueMapping]); // πŸ†• 초기 λ‘œλ“œ μ‹œ screenTableName을 targetTable둜 μžλ™ μ„€μ • React.useEffect(() => { if (screenTableName && !config.targetTable) { console.log("✨ ν˜„μž¬ ν™”λ©΄ ν…Œμ΄λΈ”μ„ μ €μž₯ λŒ€μƒ ν…Œμ΄λΈ”λ‘œ μžλ™ μ„€μ •:", screenTableName); handleChange("targetTable", screenTableName); // μ»¬λŸΌλ„ μžλ™ λ‘œλ“œ if (onTargetTableChange) { onTargetTableChange(screenTableName); } } }, [screenTableName]); // config.targetTable은 μ˜μ‘΄μ„±μ—μ„œ μ œμ™Έ (ν•œ 번만 μ‹€ν–‰) const handleChange = (key: keyof SelectedItemsDetailInputConfig, value: any) => { // πŸ”§ κΈ°μ‘΄ config와 λ³‘ν•©ν•˜μ—¬ λ‹€λ₯Έ 속성 μœ μ§€ onChange({ ...config, [key]: value }); }; const handleFieldsChange = (fields: AdditionalFieldDefinition[]) => { setLocalFields(fields); handleChange("additionalFields", fields); }; const handleDisplayColumnsChange = (columns: Array<{ name: string; label: string; width?: string }>) => { setDisplayColumns(columns); handleChange("displayColumns", columns); }; // ν•„λ“œ μΆ”κ°€ const addField = () => { const newField: AdditionalFieldDefinition = { name: `field_${localFields.length + 1}`, label: `ν•„λ“œ ${localFields.length + 1}`, type: "text", }; handleFieldsChange([...localFields, newField]); }; // ν•„λ“œ 제거 const removeField = (index: number) => { // 둜컬 μž…λ ₯ μƒνƒœμ—μ„œλ„ 제거 setLocalFieldInputs(prev => { const newInputs = { ...prev }; delete newInputs[index]; return newInputs; }); handleFieldsChange(localFields.filter((_, i) => i !== index)); }; // πŸ†• 둜컬 ν•„λ“œ μž…λ ₯ μ—…λ°μ΄νŠΈ (포컀슀 μœ μ§€μš©) const updateFieldLocal = (index: number, field: 'label' | 'placeholder', value: string) => { setLocalFieldInputs(prev => ({ ...prev, [index]: { ...prev[index], [field]: value } })); }; // πŸ†• μ‹€μ œ ν•„λ“œ 데이터 μ—…λ°μ΄νŠΈ (onBlur μ‹œ 호좜) const handleFieldBlur = (index: number) => { const localInput = localFieldInputs[index]; if (localInput) { const newFields = [...localFields]; newFields[index] = { ...newFields[index], ...localInput }; handleFieldsChange(newFields); } }; // ν•„λ“œ μˆ˜μ • (Switch 같은 μ¦‰μ‹œ μ—…λ°μ΄νŠΈκ°€ ν•„μš”ν•œ κ²½μš°μ—λ§Œ μ‚¬μš©) const updateField = (index: number, updates: Partial) => { const newFields = [...localFields]; newFields[index] = { ...newFields[index], ...updates }; handleFieldsChange(newFields); }; // πŸ†• ν•„λ“œ κ·Έλ£Ή 관리 const handleFieldGroupsChange = (groups: FieldGroup[]) => { setLocalFieldGroups(groups); handleChange("fieldGroups", groups); }; const addFieldGroup = () => { const newGroup: FieldGroup = { id: `group_${localFieldGroups.length + 1}`, title: `κ·Έλ£Ή ${localFieldGroups.length + 1}`, order: localFieldGroups.length, }; handleFieldGroupsChange([...localFieldGroups, newGroup]); }; const removeFieldGroup = (groupId: string) => { // 둜컬 μž…λ ₯ μƒνƒœμ—μ„œ ν•΄λ‹Ή κ·Έλ£Ή 제거 setLocalGroupInputs(prev => { const newInputs = { ...prev }; delete newInputs[groupId]; return newInputs; }); // κ·Έλ£Ή μ‚­μ œ μ‹œ ν•΄λ‹Ή 그룹에 μ†ν•œ ν•„λ“œλ“€μ˜ groupId도 제거 const updatedFields = localFields.map(field => field.groupId === groupId ? { ...field, groupId: undefined } : field ); setLocalFields(updatedFields); handleChange("additionalFields", updatedFields); handleFieldGroupsChange(localFieldGroups.filter(g => g.id !== groupId)); }; // πŸ†• 둜컬 κ·Έλ£Ή μž…λ ₯ μ—…λ°μ΄νŠΈ (포컀슀 μœ μ§€μš©) const updateGroupLocal = (groupId: string, field: 'id' | 'title' | 'description' | 'order', value: any) => { setLocalGroupInputs(prev => ({ ...prev, [groupId]: { ...prev[groupId], [field]: value } })); }; // πŸ†• μ‹€μ œ κ·Έλ£Ή 데이터 μ—…λ°μ΄νŠΈ (onBlur μ‹œ 호좜) const handleGroupBlur = (groupId: string) => { const localInput = localGroupInputs[groupId]; if (localInput) { const newGroups = localFieldGroups.map(g => g.id === groupId ? { ...g, ...localInput } : g ); handleFieldGroupsChange(newGroups); } }; const updateFieldGroup = (groupId: string, updates: Partial) => { // 2. μ‹€μ œ κ·Έλ£Ή 데이터 μ—…λ°μ΄νŠΈ (Switch 같은 μ¦‰μ‹œ μ—…λ°μ΄νŠΈκ°€ ν•„μš”ν•œ κ²½μš°μ—λ§Œ μ‚¬μš©) const newGroups = localFieldGroups.map(g => g.id === groupId ? { ...g, ...updates } : g ); handleFieldGroupsChange(newGroups); }; // ν‘œμ‹œ 컬럼 μΆ”κ°€ const addDisplayColumn = (columnName: string, columnLabel: string) => { if (!displayColumns.some(col => col.name === columnName)) { handleDisplayColumnsChange([...displayColumns, { name: columnName, label: columnLabel }]); } }; // ν‘œμ‹œ 컬럼 제거 const removeDisplayColumn = (columnName: string) => { handleDisplayColumnsChange(displayColumns.filter((col) => col.name !== columnName)); }; // πŸ†• ν‘œμ‹œ 컬럼용: 원본 ν…Œμ΄λΈ”μ—μ„œ μ‚¬μš©λ˜μ§€ μ•Šμ€ 컬럼 λͺ©λ‘ const availableColumns = useMemo(() => { // πŸ”§ λ‘œλ“œλœ 컬럼 μš°μ„  μ‚¬μš©, props둜 받은 μ»¬λŸΌμ€ λ°±μ—… const columns = loadedSourceTableColumns.length > 0 ? loadedSourceTableColumns : sourceTableColumns; const usedColumns = new Set([...displayColumns.map(c => c.name), ...localFields.map((f) => f.name)]); return columns.filter((col) => !usedColumns.has(col.columnName)); }, [loadedSourceTableColumns, sourceTableColumns, displayColumns, localFields]); // πŸ†• μΆ”κ°€ μž…λ ₯ ν•„λ“œμš©: λŒ€μƒ ν…Œμ΄λΈ”μ—μ„œ μ‚¬μš©λ˜μ§€ μ•Šμ€ 컬럼 λͺ©λ‘ const availableTargetColumns = useMemo(() => { // πŸ”§ λ‘œλ“œλœ 컬럼 μš°μ„  μ‚¬μš©, props둜 받은 μ»¬λŸΌμ€ λ°±μ—… const columns = loadedTargetTableColumns.length > 0 ? loadedTargetTableColumns : targetTableColumns; const usedColumns = new Set([...displayColumns.map(c => c.name), ...localFields.map((f) => f.name)]); return columns.filter((col) => !usedColumns.has(col.columnName)); }, [loadedTargetTableColumns, targetTableColumns, displayColumns, localFields]); // πŸ†• 원본 ν…Œμ΄λΈ” 필터링 const filteredSourceTables = useMemo(() => { if (!sourceTableSearchValue) return allTables; const searchLower = sourceTableSearchValue.toLowerCase(); return allTables.filter( (table) => table.tableName.toLowerCase().includes(searchLower) || table.displayName?.toLowerCase().includes(searchLower), ); }, [allTables, sourceTableSearchValue]); // πŸ†• 그룹별 ν•­λͺ© ν‘œμ‹œ μ„€μ • ν•Έλ“€λŸ¬ const addDisplayItemToGroup = (groupId: string, type: DisplayItemType) => { const newItem: DisplayItem = { type, id: `display-${Date.now()}`, }; if (type === "field") { // ν•΄λ‹Ή 그룹의 ν•„λ“œλ§Œ 선택 κ°€λŠ₯ν•˜λ„λ‘ const groupFields = localFields.filter(f => f.groupId === groupId); newItem.fieldName = groupFields[0]?.name || ""; newItem.format = "text"; newItem.emptyBehavior = "default"; } else if (type === "icon") { newItem.icon = "Circle"; } else if (type === "text") { newItem.value = "ν…μŠ€νŠΈ"; } const updatedGroups = localFieldGroups.map(g => { if (g.id === groupId) { return { ...g, displayItems: [...(g.displayItems || []), newItem] }; } return g; }); // πŸ”§ μ•„μ΄ν…œ μΆ”κ°€ μ‹œ ν•΄λ‹Ή 그룹의 아코디언을 μ—΄λ¦° μƒνƒœλ‘œ μœ μ§€ setExpandedDisplayItems(prev => ({ ...prev, [groupId]: true })); setLocalFieldGroups(updatedGroups); handleChange("fieldGroups", updatedGroups); }; const removeDisplayItemFromGroup = (groupId: string, itemIndex: number) => { const updatedGroups = localFieldGroups.map(g => { if (g.id === groupId) { return { ...g, displayItems: (g.displayItems || []).filter((_, i) => i !== itemIndex) }; } return g; }); setLocalFieldGroups(updatedGroups); handleChange("fieldGroups", updatedGroups); }; const updateDisplayItemInGroup = (groupId: string, itemIndex: number, updates: Partial) => { const updatedGroups = localFieldGroups.map(g => { if (g.id === groupId) { const updatedItems = [...(g.displayItems || [])]; updatedItems[itemIndex] = { ...updatedItems[itemIndex], ...updates }; return { ...g, displayItems: updatedItems }; } return g; }); setLocalFieldGroups(updatedGroups); handleChange("fieldGroups", updatedGroups); }; // πŸ†• μ„ νƒλœ 원본 ν…Œμ΄λΈ” ν‘œμ‹œλͺ… const selectedSourceTableLabel = useMemo(() => { if (!config.sourceTable) return "원본 ν…Œμ΄λΈ”μ„ μ„ νƒν•˜μ„Έμš”"; const table = allTables.find((t) => t.tableName === config.sourceTable); return table ? table.displayName || table.tableName : config.sourceTable; }, [config.sourceTable, allTables]); // λŒ€μƒ ν…Œμ΄λΈ” 필터링 const filteredTables = useMemo(() => { if (!tableSearchValue) return allTables; const searchLower = tableSearchValue.toLowerCase(); return allTables.filter( (table) => table.tableName.toLowerCase().includes(searchLower) || table.displayName?.toLowerCase().includes(searchLower), ); }, [allTables, tableSearchValue]); // μ„ νƒλœ λŒ€μƒ ν…Œμ΄λΈ” ν‘œμ‹œλͺ… const selectedTableLabel = useMemo(() => { if (!config.targetTable) return "μ €μž₯ λŒ€μƒ ν…Œμ΄λΈ”μ„ μ„ νƒν•˜μ„Έμš”"; const table = allTables.find((t) => t.tableName === config.targetTable); return table ? table.displayName || table.tableName : config.targetTable; }, [config.targetTable, allTables]); return (
{/* 데이터 μ†ŒμŠ€ ID */}
handleChange("dataSourceId", e.target.value)} placeholder="λΉ„μ›Œλ‘λ©΄ URL νŒŒλΌλ―Έν„°μ—μ„œ μžλ™ μ„€μ •" className="h-7 text-xs sm:h-8 sm:text-sm" />

✨ URL νŒŒλΌλ―Έν„°μ—μ„œ μžλ™μœΌλ‘œ κ°€μ Έμ˜΅λ‹ˆλ‹€ (Button이 전달)

ν…ŒμŠ€νŠΈμš©μœΌλ‘œ 직접 μž…λ ₯ν•˜λ €λ©΄ ν…Œμ΄λΈ”λͺ…을 μž…λ ₯ν•˜μ„Έμš”

{/* πŸ†• 원본 데이터 ν…Œμ΄λΈ” */}
ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. {filteredSourceTables.map((table) => ( { handleChange("sourceTable", currentValue); setSourceTableSelectOpen(false); setSourceTableSearchValue(""); if (onSourceTableChange) { onSourceTableChange(currentValue); } }} className="text-xs sm:text-sm" > {table.displayName || table.tableName} ))}

이전 ν™”λ©΄μ—μ„œ 전달받은 λ°μ΄ν„°μ˜ 원본 ν…Œμ΄λΈ” (예: item_info)

{/* μ €μž₯ λŒ€μƒ ν…Œμ΄λΈ” */}
ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. {filteredTables.map((table) => ( { handleChange("targetTable", currentValue); setTableSelectOpen(false); setTableSearchValue(""); if (onTargetTableChange) { onTargetTableChange(currentValue); } }} className="text-xs sm:text-sm" > {table.displayName || table.tableName} ))}

μ΅œμ’… 데이터λ₯Ό μ €μž₯ν•  ν…Œμ΄λΈ”

{/* ν‘œμ‹œν•  원본 데이터 컬럼 */}
{displayColumns.map((col, index) => (
))} μ‚¬μš© κ°€λŠ₯ν•œ 컬럼이 μ—†μŠ΅λ‹ˆλ‹€. {availableColumns.map((column) => ( addDisplayColumn(column.columnName, column.columnLabel || column.columnName)} className="text-xs sm:text-sm" >
{column.columnLabel || column.columnName}
{column.dataType &&
{column.dataType}
}
))}

전달받은 원본 데이터 쀑 화면에 ν‘œμ‹œν•  컬럼 (예: ν’ˆλͺ©μ½”λ“œ, ν’ˆλͺ©λͺ…)

{/* μΆ”κ°€ μž…λ ₯ ν•„λ“œ μ •μ˜ */}
{localFields.map((field, index) => (
ν•„λ“œ {index + 1}
setFieldPopoverOpen({ ...fieldPopoverOpen, [index]: open })} > μ‚¬μš© κ°€λŠ₯ν•œ 컬럼이 μ—†μŠ΅λ‹ˆλ‹€. {availableTargetColumns.map((column) => ( { updateField(index, { name: column.columnName, label: column.columnLabel || column.columnName, inputType: column.inputType || "text", // πŸ†• inputType 포함 codeCategory: column.codeCategory, // πŸ†• codeCategory 포함 }); setFieldPopoverOpen({ ...fieldPopoverOpen, [index]: false }); }} className="text-[10px] sm:text-xs" >
{column.columnLabel}
{column.columnName}
))}
updateFieldLocal(index, 'label', e.target.value)} onBlur={() => handleFieldBlur(index)} placeholder="ν•„λ“œ 라벨" className="h-6 w-full text-[10px] sm:h-7 sm:text-xs" />

ν…Œμ΄λΈ” νƒ€μž…κ΄€λ¦¬μ—μ„œ μžλ™ 섀정됨

updateFieldLocal(index, 'placeholder', e.target.value)} onBlur={() => handleFieldBlur(index)} placeholder="μž…λ ₯ μ•ˆλ‚΄" className="h-6 w-full text-[10px] sm:h-7 sm:text-xs" />
{/* πŸ†• 원본 데이터 μžλ™ μ±„μš°κΈ° */}
{/* ν…Œμ΄λΈ” 선택 λ“œλ‘­λ‹€μš΄ */} ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. { updateField(index, { autoFillFromTable: undefined, autoFillFrom: undefined }); setAutoFillTableColumns(prev => ({ ...prev, [index]: [] })); }} className="text-[10px] sm:text-xs" > 원본 ν…Œμ΄λΈ” ({config.sourceTable || "λ―Έμ„€μ •"}) {allTables.map((table) => ( { updateField(index, { autoFillFromTable: value, autoFillFrom: undefined }); loadAutoFillTableColumns(value, index); }} className="text-[10px] sm:text-xs" > {table.displayName || table.tableName} ))}

λ‹€λ₯Έ ν…Œμ΄λΈ”μ—μ„œ κ°€μ Έμ˜¬ 경우 ν…Œμ΄λΈ” 선택

{/* ν•„λ“œ 선택 */} {field.autoFillFromTable ? "μ»¬λŸΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€" : "원본 ν…Œμ΄λΈ”μ„ λ¨Όμ € μ„ νƒν•˜μ„Έμš”"} updateField(index, { autoFillFrom: undefined })} className="text-[10px] sm:text-xs" > 선택 μ•ˆ 함 {(() => { // μ„ νƒλœ ν…Œμ΄λΈ”μ˜ 컬럼 λ˜λŠ” κΈ°λ³Έ 원본 ν…Œμ΄λΈ” 컬럼 const columns = field.autoFillFromTable ? (autoFillTableColumns[index] || []) : (loadedSourceTableColumns.length > 0 ? loadedSourceTableColumns : sourceTableColumns); return columns.map((column) => ( updateField(index, { autoFillFrom: value })} className="text-[10px] sm:text-xs" >
{column.columnLabel || column.columnName}
{column.dataType &&
{column.dataType}
}
)); })()}

{field.autoFillFromTable ? `"${field.autoFillFromTable}" ν…Œμ΄λΈ”μ—μ„œ μžλ™ μ±„μš°κΈ°` : "μ£Ό 데이터 μ†ŒμŠ€μ—μ„œ μžλ™ μ±„μš°κΈ° (μˆ˜μ • κ°€λŠ₯)" }

{/* πŸ†• ν•„λ“œ κ·Έλ£Ή 선택 */} {localFieldGroups.length > 0 && (

같은 κ·Έλ£Ή IDλ₯Ό κ°€μ§„ ν•„λ“œλ“€μ€ 같은 μΉ΄λ“œμ— ν‘œμ‹œλ©λ‹ˆλ‹€

)}
updateField(index, { required: checked as boolean })} />
))}
{/* πŸ†• ν•„λ“œ κ·Έλ£Ή 관리 */}

μΆ”κ°€ μž…λ ₯ ν•„λ“œλ₯Ό μ—¬λŸ¬ μΉ΄λ“œλ‘œ λ‚˜λˆ μ„œ ν‘œμ‹œ (예: 거래처 정보, 단가 정보)

{localFieldGroups.map((group, index) => { const isGroupExpanded = expandedGroups[group.id] ?? true; return ( setExpandedGroups(prev => ({ ...prev, [group.id]: open }))} >
{/* κ·Έλ£Ή ID */}
updateGroupLocal(group.id, 'id', e.target.value)} onBlur={() => handleGroupBlur(group.id)} className="h-7 text-xs sm:h-8 sm:text-sm" placeholder="group_customer" />
{/* κ·Έλ£Ή 제λͺ© */}
updateGroupLocal(group.id, 'title', e.target.value)} onBlur={() => handleGroupBlur(group.id)} className="h-7 text-xs sm:h-8 sm:text-sm" placeholder="거래처 정보" />
{/* κ·Έλ£Ή μ„€λͺ… */}
updateGroupLocal(group.id, 'description', e.target.value)} onBlur={() => handleGroupBlur(group.id)} className="h-7 text-xs sm:h-8 sm:text-sm" placeholder="거래처 κ΄€λ ¨ 정보λ₯Ό μž…λ ₯ν•©λ‹ˆλ‹€" />
{/* ν‘œμ‹œ μˆœμ„œ */}
updateGroupLocal(group.id, 'order', parseInt(e.target.value) || 0)} onBlur={() => handleGroupBlur(group.id)} className="h-7 text-xs sm:h-8 sm:text-sm" min="0" />
{/* πŸ†• 이 그룹의 ν•­λͺ© ν‘œμ‹œ μ„€μ • */} setExpandedDisplayItems(prev => ({ ...prev, [group.id]: open }))} >
{/* μΆ”κ°€ λ²„νŠΌλ“€ */}

이 그룹의 μž…λ ₯ ν•­λͺ©μ΄ μΆ”κ°€λ˜λ©΄ μ–΄λ–»κ²Œ ν‘œμ‹œλ μ§€ μ„€μ •

{(!group.displayItems || group.displayItems.length === 0) ? (
λ―Έμ„€μ • (λͺ¨λ“  ν•„λ“œλ₯Ό " / "둜 κ΅¬λΆ„ν•˜μ—¬ ν‘œμ‹œ)
) : (
{group.displayItems.map((item, itemIndex) => (
{/* 헀더 */}
{item.type === "icon" && "🎨"} {item.type === "field" && "πŸ“"} {item.type === "text" && "πŸ’¬"} {item.type === "badge" && "🏷️"}
{/* μ•„μ΄μ½˜ μ„€μ • */} {item.type === "icon" && ( { const newValue = e.target.value; setLocalDisplayItemInputs(prev => ({ ...prev, [group.id]: { ...prev[group.id], [itemIndex]: { ...prev[group.id]?.[itemIndex], value: newValue } } })); }} onBlur={() => { const localValue = localDisplayItemInputs[group.id]?.[itemIndex]?.value; if (localValue !== undefined) { updateDisplayItemInGroup(group.id, itemIndex, { icon: localValue }); } }} placeholder="Building" className="h-6 text-[9px] sm:text-[10px]" /> )} {/* ν…μŠ€νŠΈ μ„€μ • */} {item.type === "text" && ( { const newValue = e.target.value; // 둜컬 μƒνƒœ μ¦‰μ‹œ μ—…λ°μ΄νŠΈ (포컀슀 μœ μ§€) setLocalDisplayItemInputs(prev => ({ ...prev, [group.id]: { ...prev[group.id], [itemIndex]: { ...prev[group.id]?.[itemIndex], value: newValue } } })); }} onBlur={() => { const localValue = localDisplayItemInputs[group.id]?.[itemIndex]?.value; if (localValue !== undefined) { updateDisplayItemInGroup(group.id, itemIndex, { value: localValue }); } }} placeholder="| , / , -" className="h-6 text-[9px] sm:text-[10px]" /> )} {/* ν•„λ“œ μ„€μ • */} {item.type === "field" && (
{/* ν•„λ“œ 선택 */} {/* 라벨 */} { const newValue = e.target.value; // 둜컬 μƒνƒœ μ¦‰μ‹œ μ—…λ°μ΄νŠΈ (포컀슀 μœ μ§€) setLocalDisplayItemInputs(prev => ({ ...prev, [group.id]: { ...prev[group.id], [itemIndex]: { ...prev[group.id]?.[itemIndex], label: newValue } } })); }} onBlur={() => { const localValue = localDisplayItemInputs[group.id]?.[itemIndex]?.label; if (localValue !== undefined) { updateDisplayItemInGroup(group.id, itemIndex, { label: localValue }); } }} placeholder="라벨 (예: 거래처:)" className="h-6 w-full text-[9px] sm:text-[10px]" /> {/* ν‘œμ‹œ ν˜•μ‹ */} {/* 빈 κ°’ 처리 */} {/* κΈ°λ³Έκ°’ */} {item.emptyBehavior === "default" && ( { const newValue = e.target.value; setLocalDisplayItemInputs(prev => ({ ...prev, [group.id]: { ...prev[group.id], [itemIndex]: { ...prev[group.id]?.[itemIndex], value: newValue } } })); }} onBlur={() => { const localValue = localDisplayItemInputs[group.id]?.[itemIndex]?.value; if (localValue !== undefined) { updateDisplayItemInGroup(group.id, itemIndex, { defaultValue: localValue }); } }} placeholder="λ―Έμž…λ ₯" className="h-6 w-full text-[9px] sm:text-[10px]" /> )}
)}
))}
)}
); })} {localFieldGroups.length > 0 && (

πŸ’‘ μΆ”κ°€ μž…λ ₯ ν•„λ“œμ˜ "ν•„λ“œ κ·Έλ£Ή ID"에 μœ„μ—μ„œ μ •μ˜ν•œ κ·Έλ£Ή IDλ₯Ό μž…λ ₯ν•˜μ„Έμš”

)}
{/* μž…λ ₯ λͺ¨λ“œ μ„€μ • */}

{config.inputMode === "modal" ? "μΆ”κ°€ λ²„νŠΌ 클릭 μ‹œ μž…λ ₯μ°½ ν‘œμ‹œ, μ™„λ£Œ ν›„ μž‘μ€ μΉ΄λ“œλ‘œ ν‘œμ‹œ" : "λͺ¨λ“  ν•­λͺ©μ˜ μž…λ ₯창을 항상 ν‘œμ‹œ"}

{/* λ ˆμ΄μ•„μ›ƒ μ„€μ • */}

{config.layout === "grid" ? "ν–‰ λ‹¨μœ„λ‘œ 데이터λ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€" : "각 ν•­λͺ©μ„ μΉ΄λ“œλ‘œ ν‘œμ‹œν•©λ‹ˆλ‹€"}

{/* μžλ™ 계산 μ„€μ • */}
{ if (checked) { handleChange("autoCalculation", { targetField: "", mode: "template", inputFields: { basePrice: "", discountType: "", discountValue: "", roundingType: "", roundingUnit: "", }, calculationType: "price", valueMapping: {}, calculationSteps: [], }); } else { handleChange("autoCalculation", undefined); } }} />
{config.autoCalculation && (
{/* 계산 λͺ¨λ“œ 선택 */}
{/* ν…œν”Œλ¦Ώ λͺ¨λ“œ */} {config.autoCalculation.mode === "template" && ( <> {/* 계산 ν•„λ“œ 선택 */}
{/* 계산 κ²°κ³Ό ν•„λ“œ */}
{/* κΈ°μ€€ 단가 ν•„λ“œ */}
{/* 할인 방식 ν•„λ“œ */}
{/* 할인값 ν•„λ“œ */}
{/* 반올림 방식 ν•„λ“œ */}
{/* 반올림 λ‹¨μœ„ ν•„λ“œ */}
{/* μΉ΄ν…Œκ³ λ¦¬ κ°’ λ§€ν•‘ */}
{/* 할인 방식 λ§€ν•‘ */} setExpandedCategoryMappings(prev => ({ ...prev, discountType: open }))} > {/* 1단계: 메뉴 선택 */}
{/* 2단계: μΉ΄ν…Œκ³ λ¦¬ 선택 */} {(() => { const hasSelectedMenu = !!(config.autoCalculation.valueMapping as any)?._selectedMenus?.discountType; const columns = categoryColumns.discountType || []; console.log("🎨 [λ Œλ”λ§] 2단계 μΉ΄ν…Œκ³ λ¦¬ 선택", { hasSelectedMenu, columns, columnsCount: columns.length, categoryColumnsState: categoryColumns }); return hasSelectedMenu ? (
) : null; })()} {/* 3단계: κ°’ λ§€ν•‘ */} {(config.autoCalculation.valueMapping as any)?._selectedCategories?.discountType && (
{["ν• μΈμ—†μŒ", "ν• μΈμœ¨(%)", "ν• μΈκΈˆμ•‘"].map((label, idx) => { const operations = ["none", "rate", "amount"]; return (
{label} β†’ {operations[idx]}
); })}
)}
{/* 반올림 방식 λ§€ν•‘ */} setExpandedCategoryMappings(prev => ({ ...prev, roundingType: open }))} > {/* 1단계: 메뉴 선택 */}
{/* 2단계: μΉ΄ν…Œκ³ λ¦¬ 선택 */} {(config.autoCalculation.valueMapping as any)?._selectedMenus?.roundingType && (
)} {/* 3단계: κ°’ λ§€ν•‘ */} {(config.autoCalculation.valueMapping as any)?._selectedCategories?.roundingType && (
{["λ°˜μ˜¬λ¦Όμ—†μŒ", "반올림", "μ ˆμ‚­", "올림"].map((label, idx) => { const operations = ["none", "round", "floor", "ceil"]; return (
{label} β†’ {operations[idx]}
); })}
)}
{/* 반올림 λ‹¨μœ„ λ§€ν•‘ */} setExpandedCategoryMappings(prev => ({ ...prev, roundingUnit: open }))} > {/* 1단계: 메뉴 선택 */}
{/* 2단계: μΉ΄ν…Œκ³ λ¦¬ 선택 */} {(config.autoCalculation.valueMapping as any)?._selectedMenus?.roundingUnit && (
)} {/* 3단계: κ°’ λ§€ν•‘ */} {(config.autoCalculation.valueMapping as any)?._selectedCategories?.roundingUnit && (
{["1원", "10원", "100원", "1,000원"].map((label) => { const unitValue = label === "1,000원" ? 1000 : parseInt(label); return (
{label} β†’ {unitValue}
); })}
)}

πŸ’‘ 1단계: 메뉴 선택 β†’ 2단계: μΉ΄ν…Œκ³ λ¦¬ 선택 β†’ 3단계: κ°’ λ§€ν•‘

)} {/* μ»€μŠ€ν…€ λͺ¨λ“œ (계산식 λΉŒλ”) */} {config.autoCalculation.mode === "custom" && (
{ handleChange("autoCalculation", { ...config.autoCalculation, calculationSteps: steps, }); }} />
)}
)}
{/* μ˜΅μ…˜ */}
handleChange("showIndex", checked as boolean)} />
handleChange("allowRemove", checked as boolean)} />
handleChange("disabled", checked as boolean)} />
{/* πŸ†• λΆ€λͺ¨ 데이터 λ§€ν•‘ */}

이전 ν™”λ©΄(거래처 선택 λ“±)μ—μ„œ λ„˜μ–΄μ˜¨ 데이터λ₯Ό μžλ™μœΌλ‘œ λ§€ν•‘ν•©λ‹ˆλ‹€.

{(config.parentDataMapping || []).map((mapping, index) => (
{/* μ†ŒμŠ€ ν…Œμ΄λΈ” 선택 */}
ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. {allTables.map((table) => ( { const updated = [...(config.parentDataMapping || [])]; updated[index] = { ...updated[index], sourceTable: currentValue, sourceField: "", // ν…Œμ΄λΈ” λ³€κ²½ μ‹œ ν•„λ“œ μ΄ˆκΈ°ν™” }; handleChange("parentDataMapping", updated); // ν…Œμ΄λΈ” 선택 μ‹œ 컬럼 λ‘œλ“œ if (currentValue) { loadMappingSourceColumns(currentValue, index); } }} className="text-xs" > {table.displayName || table.tableName} ))}

ν’ˆλͺ©, 거래처, μ‚¬μš©μž λ“± 데이터λ₯Ό κ°€μ Έμ˜¬ ν…Œμ΄λΈ”μ„ μ„ νƒν•˜μ„Έμš”

{/* 원본 ν•„λ“œ */}
{!mapping.sourceTable ? ( μ†ŒμŠ€ ν…Œμ΄λΈ”μ„ λ¨Όμ € μ„ νƒν•˜μ„Έμš” ) : !mappingSourceColumns[index] || mappingSourceColumns[index].length === 0 ? ( 컬럼 λ‘œλ”© 쀑... ) : ( <> μ»¬λŸΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. {mappingSourceColumns[index].map((col) => { const searchValue = `${col.columnLabel || col.columnName} ${col.columnName} ${col.dataType || ""}`.toLowerCase(); return ( { const updated = [...(config.parentDataMapping || [])]; updated[index] = { ...updated[index], sourceField: col.columnName }; handleChange("parentDataMapping", updated); }} className="text-xs" >
{col.columnLabel || col.columnName} {col.dataType && ( {col.dataType} )}
); })}
)}
{/* μ €μž₯ ν•„λ“œ (ν˜„μž¬ ν™”λ©΄ ν…Œμ΄λΈ” 컬럼) */}
{!config.targetTable ? ( μ €μž₯ λŒ€μƒ ν…Œμ΄λΈ”μ„ λ¨Όμ € μ„ νƒν•˜μ„Έμš” ) : loadedTargetTableColumns.length === 0 ? ( 컬럼 λ‘œλ”© 쀑... ) : ( <> μ»¬λŸΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. {loadedTargetTableColumns.map((col) => { const searchValue = `${col.columnLabel || col.columnName} ${col.columnName} ${col.dataType || ""}`.toLowerCase(); return ( { const updated = [...(config.parentDataMapping || [])]; updated[index] = { ...updated[index], targetField: col.columnName }; handleChange("parentDataMapping", updated); }} className="text-xs" >
{col.columnLabel || col.columnName} {col.dataType && ( {col.dataType} )}
); })}
)}

ν˜„μž¬ ν™”λ©΄μ˜ μ €μž₯ λŒ€μƒ ν…Œμ΄λΈ” ({config.targetTable || "미선택"})의 컬럼

{/* κΈ°λ³Έκ°’ (선택사항) */}
{ const newValue = e.target.value; setLocalMappingInputs(prev => ({ ...prev, [index]: newValue })); }} onBlur={() => { const currentValue = localMappingInputs[index]; if (currentValue !== undefined) { const updated = [...(config.parentDataMapping || [])]; updated[index] = { ...updated[index], defaultValue: currentValue || undefined }; handleChange("parentDataMapping", updated); } }} placeholder="값이 없을 λ•Œ μ‚¬μš©ν•  κΈ°λ³Έκ°’" className="h-7 text-xs" />
{/* μ‚­μ œ λ²„νŠΌ */}
))}
{/* μ‚¬μš© μ˜ˆμ‹œ */}

πŸ’‘ μ‚¬μš© μ˜ˆμ‹œ

  • β€’ ν’ˆλͺ© 선택 λͺ¨λ‹¬ β†’ λ‹€μŒ λ²„νŠΌ β†’ κ±°λž˜μ²˜λ³„ 가격 μž…λ ₯
  • β€’ μ‚¬μš©μž 선택 λͺ¨λ‹¬ β†’ λ‹€μŒ λ²„νŠΌ β†’ κΆŒν•œ 및 λΆ€μ„œ ν• λ‹Ή
  • β€’ μ œν’ˆ 선택 λͺ¨λ‹¬ β†’ λ‹€μŒ λ²„νŠΌ β†’ μˆ˜λŸ‰ 및 납기일 μž…λ ₯
); }; SelectedItemsDetailInputConfigPanel.displayName = "SelectedItemsDetailInputConfigPanel"; export default SelectedItemsDetailInputConfigPanel;