diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 2118bca3..3a440f07 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -2239,10 +2239,27 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD calculatedWidth: `${Math.round(widthPercent * 100) / 100}%`, }); + // ๐Ÿ†• ๋ผ๋ฒจ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ธฐ๋ณธ columnName ์ƒ์„ฑ (ํ•œ๊ธ€ โ†’ ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค) + // ์˜ˆ: "์ฐฝ๊ณ ์ฝ”๋“œ" โ†’ "warehouse_code" ๋˜๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€ + const generateDefaultColumnName = (label: string): string => { + // ํ•œ๊ธ€ ๋ผ๋ฒจ์˜ ๊ฒฝ์šฐ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ (๋‚˜์ค‘์— ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜์ • ๊ฐ€๋Šฅ) + // ์˜๋ฌธ์˜ ๊ฒฝ์šฐ ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค๋กœ ๋ณ€ํ™˜ + if (/[๊ฐ€-ํžฃ]/.test(label)) { + // ํ•œ๊ธ€์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ: ๊ณต๋ฐฑ์„ ์–ธ๋”์Šค์ฝ”์–ด๋กœ, ์†Œ๋ฌธ์ž๋กœ ๋ณ€ํ™˜ + return label.replace(/\s+/g, "_").toLowerCase(); + } + // ์˜๋ฌธ์˜ ๊ฒฝ์šฐ: ์นด๋ฉœ์ผ€์ด์Šค/ํŒŒ์Šค์นผ์ผ€์ด์Šค๋ฅผ ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค๋กœ ๋ณ€ํ™˜ + return label + .replace(/([a-z])([A-Z])/g, "$1_$2") + .replace(/\s+/g, "_") + .toLowerCase(); + }; + const newComponent: ComponentData = { id: generateComponentId(), type: "component", // โœ… ์ƒˆ ์ปดํฌ๋„ŒํŠธ ์‹œ์Šคํ…œ ์‚ฌ์šฉ label: component.name, + columnName: generateDefaultColumnName(component.name), // ๐Ÿ†• ๊ธฐ๋ณธ columnName ์ž๋™ ์ƒ์„ฑ widgetType: component.webType, componentType: component.id, // ์ƒˆ ์ปดํฌ๋„ŒํŠธ ์‹œ์Šคํ…œ์˜ ID (DynamicComponentRenderer์šฉ) position: snappedPosition, diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx index 36f420fd..39f32a73 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx @@ -91,6 +91,14 @@ export const ButtonConfigPanel: React.FC = ({ const [mappingSourceSearch, setMappingSourceSearch] = useState>({}); const [mappingTargetSearch, setMappingTargetSearch] = useState>({}); + // ๐Ÿ†• openModalWithData ์ „์šฉ ํ•„๋“œ ๋งคํ•‘ ์ƒํƒœ + const [modalSourceColumns, setModalSourceColumns] = useState>([]); + const [modalTargetColumns, setModalTargetColumns] = useState>([]); + const [modalSourcePopoverOpen, setModalSourcePopoverOpen] = useState>({}); + const [modalTargetPopoverOpen, setModalTargetPopoverOpen] = useState>({}); + const [modalSourceSearch, setModalSourceSearch] = useState>({}); + const [modalTargetSearch, setModalTargetSearch] = useState>({}); + // ๐ŸŽฏ ํ”Œ๋กœ์šฐ ์œ„์ ฏ์ด ํ™”๋ฉด์— ์žˆ๋Š”์ง€ ํ™•์ธ const hasFlowWidget = useMemo(() => { const found = allComponents.some((comp: any) => { @@ -318,6 +326,88 @@ export const ButtonConfigPanel: React.FC = ({ loadColumns(); }, [config.action?.dataTransfer?.sourceTable, config.action?.dataTransfer?.targetTable]); + // ๐Ÿ†• openModalWithData ์†Œ์Šค/ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ + useEffect(() => { + const actionType = config.action?.type; + if (actionType !== "openModalWithData") return; + + const loadModalMappingColumns = async () => { + // ์†Œ์Šค ํ…Œ์ด๋ธ”: ํ˜„์žฌ ํ™”๋ฉด์˜ ๋ถ„ํ•  ํŒจ๋„ ๋˜๋Š” ํ…Œ์ด๋ธ”์—์„œ ๊ฐ์ง€ + // allComponents์—์„œ split-panel-layout ๋˜๋Š” table-list ์ฐพ๊ธฐ + let sourceTableName: string | null = null; + + for (const comp of allComponents) { + const compType = comp.componentType || (comp as any).componentConfig?.type; + if (compType === "split-panel-layout" || compType === "screen-split-panel") { + // ๋ถ„ํ•  ํŒจ๋„์˜ ์ขŒ์ธก ํ…Œ์ด๋ธ”๋ช… + sourceTableName = (comp as any).componentConfig?.leftPanel?.tableName || + (comp as any).componentConfig?.leftTableName; + break; + } + if (compType === "table-list") { + sourceTableName = (comp as any).componentConfig?.tableName; + break; + } + } + + // ์†Œ์Šค ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ + if (sourceTableName) { + try { + const response = await apiClient.get(`/table-management/tables/${sourceTableName}/columns`); + if (response.data.success) { + let columnData = response.data.data; + if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; + if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; + + if (Array.isArray(columnData)) { + const columns = columnData.map((col: any) => ({ + name: col.name || col.columnName, + label: col.displayName || col.label || col.columnLabel || col.name || col.columnName, + })); + setModalSourceColumns(columns); + console.log(`โœ… [openModalWithData] ์†Œ์Šค ํ…Œ์ด๋ธ”(${sourceTableName}) ์ปฌ๋Ÿผ ๋กœ๋“œ:`, columns.length); + } + } + } catch (error) { + console.error("์†Œ์Šค ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ ์‹คํŒจ:", error); + } + } + + // ํƒ€๊ฒŸ ํ™”๋ฉด์˜ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ + const targetScreenId = config.action?.targetScreenId; + if (targetScreenId) { + try { + // ํƒ€๊ฒŸ ํ™”๋ฉด ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ + const screenResponse = await apiClient.get(`/screen-management/screens/${targetScreenId}`); + if (screenResponse.data.success && screenResponse.data.data) { + const targetTableName = screenResponse.data.data.tableName; + if (targetTableName) { + const columnResponse = await apiClient.get(`/table-management/tables/${targetTableName}/columns`); + if (columnResponse.data.success) { + let columnData = columnResponse.data.data; + if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; + if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; + + if (Array.isArray(columnData)) { + const columns = columnData.map((col: any) => ({ + name: col.name || col.columnName, + label: col.displayName || col.label || col.columnLabel || col.name || col.columnName, + })); + setModalTargetColumns(columns); + console.log(`โœ… [openModalWithData] ํƒ€๊ฒŸ ํ…Œ์ด๋ธ”(${targetTableName}) ์ปฌ๋Ÿผ ๋กœ๋“œ:`, columns.length); + } + } + } + } + } catch (error) { + console.error("ํƒ€๊ฒŸ ํ™”๋ฉด ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ ์‹คํŒจ:", error); + } + } + }; + + loadModalMappingColumns(); + }, [config.action?.type, config.action?.targetScreenId, allComponents]); + // ํ™”๋ฉด ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ (ํ˜„์žฌ ํŽธ์ง‘ ์ค‘์ธ ํ™”๋ฉด์˜ ํšŒ์‚ฌ ์ฝ”๋“œ ๊ธฐ์ค€) useEffect(() => { const fetchScreens = async () => { @@ -1024,6 +1114,194 @@ export const ButtonConfigPanel: React.FC = ({ SelectedItemsDetailInput ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋Š” ํ™”๋ฉด์„ ์„ ํƒํ•˜์„ธ์š”

+ + {/* ๐Ÿ†• ํ•„๋“œ ๋งคํ•‘ ์„ค์ • (์†Œ์Šค ์ปฌ๋Ÿผ โ†’ ํƒ€๊ฒŸ ์ปฌ๋Ÿผ) */} +
+
+ + +
+

+ ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋ช…์ด ํƒ€๊ฒŸ ํ™”๋ฉด์˜ ์ž…๋ ฅ ํ•„๋“œ ์ปฌ๋Ÿผ๋ช…๊ณผ ๋‹ค๋ฅผ ๋•Œ ๋งคํ•‘์„ ์„ค์ •ํ•˜์„ธ์š”. +
+ ์˜ˆ: warehouse_code โ†’ warehouse_id (๋ถ„ํ•  ํŒจ๋„์˜ ์ฐฝ๊ณ ์ฝ”๋“œ๋ฅผ ๋ชจ๋‹ฌ์˜ ์ฐฝ๊ณ ID์— ๋งคํ•‘) +

+ + {/* ์ปฌ๋Ÿผ ๋กœ๋“œ ์ƒํƒœ ํ‘œ์‹œ */} + {modalSourceColumns.length > 0 || modalTargetColumns.length > 0 ? ( +
+ ์†Œ์Šค ์ปฌ๋Ÿผ: {modalSourceColumns.length}๊ฐœ / ํƒ€๊ฒŸ ์ปฌ๋Ÿผ: {modalTargetColumns.length}๊ฐœ +
+ ) : ( +
+ ๋ถ„ํ•  ํŒจ๋„ ๋˜๋Š” ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ์™€ ๋Œ€์ƒ ํ™”๋ฉด์„ ์„ค์ •ํ•˜๋ฉด ์ปฌ๋Ÿผ ๋ชฉ๋ก์ด ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. +
+ )} + + {(config.action?.fieldMappings || []).length === 0 ? ( +
+

+ ๋งคํ•‘์ด ์—†์œผ๋ฉด ๊ฐ™์€ ์ด๋ฆ„์˜ ์ปฌ๋Ÿผ๋ผ๋ฆฌ ์ž๋™์œผ๋กœ ๋งคํ•‘๋ฉ๋‹ˆ๋‹ค. +

+
+ ) : ( +
+ {(config.action?.fieldMappings || []).map((mapping: any, index: number) => ( +
+ {/* ์†Œ์Šค ํ•„๋“œ ์„ ํƒ (Combobox) */} +
+ setModalSourcePopoverOpen((prev) => ({ ...prev, [index]: open }))} + > + + + + + + setModalSourceSearch((prev) => ({ ...prev, [index]: value }))} + /> + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + + {modalSourceColumns.map((col) => ( + { + const mappings = [...(config.action?.fieldMappings || [])]; + mappings[index] = { ...mappings[index], sourceField: col.name }; + onUpdateProperty("componentConfig.action.fieldMappings", mappings); + setModalSourcePopoverOpen((prev) => ({ ...prev, [index]: false })); + }} + className="text-xs" + > + + {col.label} + {col.label !== col.name && ( + ({col.name}) + )} + + ))} + + + + + +
+ + โ†’ + + {/* ํƒ€๊ฒŸ ํ•„๋“œ ์„ ํƒ (Combobox) */} +
+ setModalTargetPopoverOpen((prev) => ({ ...prev, [index]: open }))} + > + + + + + + setModalTargetSearch((prev) => ({ ...prev, [index]: value }))} + /> + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + + {modalTargetColumns.map((col) => ( + { + const mappings = [...(config.action?.fieldMappings || [])]; + mappings[index] = { ...mappings[index], targetField: col.name }; + onUpdateProperty("componentConfig.action.fieldMappings", mappings); + setModalTargetPopoverOpen((prev) => ({ ...prev, [index]: false })); + }} + className="text-xs" + > + + {col.label} + {col.label !== col.name && ( + ({col.name}) + )} + + ))} + + + + + +
+ + {/* ์‚ญ์ œ ๋ฒ„ํŠผ */} + +
+ ))} +
+ )} +
)} diff --git a/frontend/components/screen/panels/PropertiesPanel.tsx b/frontend/components/screen/panels/PropertiesPanel.tsx index ff21ac3e..bb663c74 100644 --- a/frontend/components/screen/panels/PropertiesPanel.tsx +++ b/frontend/components/screen/panels/PropertiesPanel.tsx @@ -584,20 +584,23 @@ const PropertiesPanelComponent: React.FC = ({
- {selectedComponent.type === "widget" && ( + {(selectedComponent.type === "widget" || selectedComponent.type === "component") && ( <>
onUpdateProperty("columnName", e.target.value)} + placeholder="formData์—์„œ ์‚ฌ์šฉํ•  ํ•„๋“œ๋ช…" + className="h-8" + title="๋ถ„ํ•  ํŒจ๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›์„ ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ํ•„๋“œ๋ช…์ž…๋‹ˆ๋‹ค" /> +

+ ๋ถ„ํ•  ํŒจ๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›์„ ๋•Œ ๋งคํ•‘๋˜๋Š” ํ•„๋“œ๋ช… +

diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 7c1545d8..275efbb5 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -59,6 +59,7 @@ export interface ButtonActionConfig { popupWidth?: number; popupHeight?: number; dataSourceId?: string; // ๐Ÿ†• modalDataStore์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ID (openModalWithData์šฉ) + fieldMappings?: Array<{ sourceField: string; targetField: string }>; // ๐Ÿ†• ํ•„๋“œ ๋งคํ•‘ (openModalWithData์šฉ) // ํ™•์ธ ๋ฉ”์‹œ์ง€ confirmMessage?: string; @@ -1548,10 +1549,27 @@ export class ButtonActionExecutor { } // ๐Ÿ†• ๋ถ€๋ชจ ํ™”๋ฉด์˜ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ (excludeFilter์—์„œ ์‚ฌ์šฉ) - const parentData = dataRegistry[dataSourceId]?.[0]?.originalData || dataRegistry[dataSourceId]?.[0] || {}; + const rawParentData = dataRegistry[dataSourceId]?.[0]?.originalData || dataRegistry[dataSourceId]?.[0] || {}; + + // ๐Ÿ†• ํ•„๋“œ ๋งคํ•‘ ์ ์šฉ (์†Œ์Šค ์ปฌ๋Ÿผ โ†’ ํƒ€๊ฒŸ ์ปฌ๋Ÿผ) + let parentData = { ...rawParentData }; + if (config.fieldMappings && Array.isArray(config.fieldMappings) && config.fieldMappings.length > 0) { + console.log("๐Ÿ”„ [openModalWithData] ํ•„๋“œ ๋งคํ•‘ ์ ์šฉ:", config.fieldMappings); + + config.fieldMappings.forEach((mapping: { sourceField: string; targetField: string }) => { + if (mapping.sourceField && mapping.targetField && rawParentData[mapping.sourceField] !== undefined) { + // ํƒ€๊ฒŸ ํ•„๋“œ์— ์†Œ์Šค ํ•„๋“œ ๊ฐ’ ๋ณต์‚ฌ + parentData[mapping.targetField] = rawParentData[mapping.sourceField]; + console.log(` โœ… ${mapping.sourceField} โ†’ ${mapping.targetField}: ${rawParentData[mapping.sourceField]}`); + } + }); + } + console.log("๐Ÿ“ฆ [openModalWithData] ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ:", { dataSourceId, - parentData, + rawParentData, + mappedParentData: parentData, + fieldMappings: config.fieldMappings, }); // ๐Ÿ†• ์ „์—ญ ๋ชจ๋‹ฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (URL ํŒŒ๋ผ๋ฏธํ„ฐ ํฌํ•จ)