From 8317af92cdef5e7266f6b21176b64d63c96b71d9 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 3 Dec 2025 10:24:07 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=8B=9C=20=EB=B0=94=EB=A1=9C=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/screenManagementService.ts | 134 +++++++++++++++++- .../src/services/tableManagementService.ts | 132 +++++++++++++++++ 2 files changed, 265 insertions(+), 1 deletion(-) diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index 007a39e7..646fc8d6 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -1517,11 +1517,23 @@ export class ScreenManagementService { }; } + // ๐Ÿ”ฅ ์ตœ์‹  inputType ์ •๋ณด ์กฐํšŒ (table_type_columns์—์„œ) + const inputTypeMap = await this.getLatestInputTypes(componentLayouts, companyCode); + const components: ComponentData[] = componentLayouts.map((layout) => { const properties = layout.properties as any; + + // ๐Ÿ”ฅ ์ตœ์‹  inputType์œผ๋กœ widgetType ๋ฐ componentType ์—…๋ฐ์ดํŠธ + const tableName = properties?.tableName; + const columnName = properties?.columnName; + const latestTypeInfo = tableName && columnName + ? inputTypeMap.get(`${tableName}.${columnName}`) + : null; + const component = { id: layout.component_id, - type: layout.component_type as any, + // ๐Ÿ”ฅ ์ตœ์‹  componentType์ด ์žˆ์œผ๋ฉด type ๋ฎ์–ด์“ฐ๊ธฐ + type: latestTypeInfo?.componentType || layout.component_type as any, position: { x: layout.position_x, y: layout.position_y, @@ -1530,6 +1542,17 @@ export class ScreenManagementService { size: { width: layout.width, height: layout.height }, parentId: layout.parent_id, ...properties, + // ๐Ÿ”ฅ ์ตœ์‹  inputType์ด ์žˆ์œผ๋ฉด widgetType, componentType ๋ฎ์–ด์“ฐ๊ธฐ + ...(latestTypeInfo && { + widgetType: latestTypeInfo.inputType, + inputType: latestTypeInfo.inputType, + componentType: latestTypeInfo.componentType, + componentConfig: { + ...properties?.componentConfig, + type: latestTypeInfo.componentType, + inputType: latestTypeInfo.inputType, + }, + }), }; console.log(`๋กœ๋“œ๋œ ์ปดํฌ๋„ŒํŠธ:`, { @@ -1539,6 +1562,9 @@ export class ScreenManagementService { size: component.size, parentId: component.parentId, title: (component as any).title, + widgetType: (component as any).widgetType, + componentType: (component as any).componentType, + latestTypeInfo, }); return component; @@ -1558,6 +1584,112 @@ export class ScreenManagementService { }; } + /** + * ์ž…๋ ฅ ํƒ€์ž…์— ํ•ด๋‹นํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ID ๋ฐ˜ํ™˜ + * (ํ”„๋ก ํŠธ์—”๋“œ webTypeMapping.ts์™€ ๋™์ผํ•œ ๋งคํ•‘) + */ + private getComponentIdFromInputType(inputType: string): string { + const mapping: Record = { + // ํ…์ŠคํŠธ ์ž…๋ ฅ + text: "text-input", + email: "text-input", + password: "text-input", + tel: "text-input", + // ์ˆซ์ž ์ž…๋ ฅ + number: "number-input", + decimal: "number-input", + // ๋‚ ์งœ/์‹œ๊ฐ„ + date: "date-input", + datetime: "date-input", + time: "date-input", + // ํ…์ŠคํŠธ ์˜์—ญ + textarea: "textarea-basic", + // ์„ ํƒ + select: "select-basic", + dropdown: "select-basic", + // ์ฒดํฌ๋ฐ•์Šค/๋ผ๋””์˜ค + checkbox: "checkbox-basic", + radio: "radio-basic", + boolean: "toggle-switch", + // ํŒŒ์ผ + file: "file-upload", + // ์ด๋ฏธ์ง€ + image: "image-widget", + img: "image-widget", + picture: "image-widget", + photo: "image-widget", + // ๋ฒ„ํŠผ + button: "button-primary", + // ๊ธฐํƒ€ + label: "text-display", + code: "select-basic", + entity: "select-basic", + category: "select-basic", + }; + + return mapping[inputType] || "text-input"; + } + + /** + * ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์ตœ์‹  inputType ์ •๋ณด ์กฐํšŒ + * @param layouts - ๋ ˆ์ด์•„์›ƒ ๋ชฉ๋ก + * @param companyCode - ํšŒ์‚ฌ ์ฝ”๋“œ + * @returns Map<"tableName.columnName", { inputType, componentType }> + */ + private async getLatestInputTypes( + layouts: any[], + companyCode: string + ): Promise> { + const inputTypeMap = new Map(); + + // tableName๊ณผ columnName์ด ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๊ณ ์œ  ์กฐํ•ฉ ์ถ”์ถœ + const tableColumnPairs = new Set(); + for (const layout of layouts) { + const properties = layout.properties as any; + if (properties?.tableName && properties?.columnName) { + tableColumnPairs.add(`${properties.tableName}|${properties.columnName}`); + } + } + + if (tableColumnPairs.size === 0) { + return inputTypeMap; + } + + // ๊ฐ ํ…Œ์ด๋ธ”-์ปฌ๋Ÿผ ์กฐํ•ฉ์— ๋Œ€ํ•ด ์ตœ์‹  inputType ์กฐํšŒ + const pairs = Array.from(tableColumnPairs).map(pair => { + const [tableName, columnName] = pair.split('|'); + return { tableName, columnName }; + }); + + // ๋ฐฐ์น˜ ์ฟผ๋ฆฌ๋กœ ํ•œ ๋ฒˆ์— ์กฐํšŒ + const placeholders = pairs.map((_, i) => `($${i * 2 + 1}, $${i * 2 + 2})`).join(', '); + const params = pairs.flatMap(p => [p.tableName, p.columnName]); + + try { + const results = await query<{ table_name: string; column_name: string; input_type: string }>( + `SELECT table_name, column_name, input_type + FROM table_type_columns + WHERE (table_name, column_name) IN (${placeholders}) + AND company_code = $${params.length + 1}`, + [...params, companyCode] + ); + + for (const row of results) { + const componentType = this.getComponentIdFromInputType(row.input_type); + inputTypeMap.set(`${row.table_name}.${row.column_name}`, { + inputType: row.input_type, + componentType: componentType, + }); + } + + console.log(`์ตœ์‹  inputType ์กฐํšŒ ์™„๋ฃŒ: ${results.length}๊ฐœ`); + } catch (error) { + console.warn(`์ตœ์‹  inputType ์กฐํšŒ ์‹คํŒจ (๋ฌด์‹œ๋จ):`, error); + } + + return inputTypeMap; + } + // ======================================== // ํ…œํ”Œ๋ฆฟ ๊ด€๋ฆฌ // ======================================== diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 64eb44c8..8e01903b 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -797,6 +797,9 @@ export class TableManagementService { ] ); + // ๐Ÿ”ฅ ํ•ด๋‹น ์ปฌ๋Ÿผ์„ ์‚ฌ์šฉํ•˜๋Š” ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ์˜ widgetType๋„ ์—…๋ฐ์ดํŠธ + await this.syncScreenLayoutsInputType(tableName, columnName, inputType, companyCode); + // ๐Ÿ”ฅ ์บ์‹œ ๋ฌดํšจํ™”: ํ•ด๋‹น ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ์บ์‹œ ์‚ญ์ œ const cacheKeyPattern = `${CacheKeys.TABLE_COLUMNS(tableName, 1, 1000)}_${companyCode}`; cache.delete(cacheKeyPattern); @@ -816,6 +819,135 @@ export class TableManagementService { } } + /** + * ์ž…๋ ฅ ํƒ€์ž…์— ํ•ด๋‹นํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ID ๋ฐ˜ํ™˜ + * (ํ”„๋ก ํŠธ์—”๋“œ webTypeMapping.ts์™€ ๋™์ผํ•œ ๋งคํ•‘) + */ + private getComponentIdFromInputType(inputType: string): string { + const mapping: Record = { + // ํ…์ŠคํŠธ ์ž…๋ ฅ + text: "text-input", + email: "text-input", + password: "text-input", + tel: "text-input", + // ์ˆซ์ž ์ž…๋ ฅ + number: "number-input", + decimal: "number-input", + // ๋‚ ์งœ/์‹œ๊ฐ„ + date: "date-input", + datetime: "date-input", + time: "date-input", + // ํ…์ŠคํŠธ ์˜์—ญ + textarea: "textarea-basic", + // ์„ ํƒ + select: "select-basic", + dropdown: "select-basic", + // ์ฒดํฌ๋ฐ•์Šค/๋ผ๋””์˜ค + checkbox: "checkbox-basic", + radio: "radio-basic", + boolean: "toggle-switch", + // ํŒŒ์ผ + file: "file-upload", + // ์ด๋ฏธ์ง€ + image: "image-widget", + img: "image-widget", + picture: "image-widget", + photo: "image-widget", + // ๋ฒ„ํŠผ + button: "button-primary", + // ๊ธฐํƒ€ + label: "text-display", + code: "select-basic", + entity: "select-basic", + category: "select-basic", + }; + + return mapping[inputType] || "text-input"; + } + + /** + * ์ปฌ๋Ÿผ ์ž…๋ ฅ ํƒ€์ž… ๋ณ€๊ฒฝ ์‹œ ํ•ด๋‹น ์ปฌ๋Ÿผ์„ ์‚ฌ์šฉํ•˜๋Š” ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ์˜ widgetType ๋ฐ componentType ๋™๊ธฐํ™” + * @param tableName - ํ…Œ์ด๋ธ”๋ช… + * @param columnName - ์ปฌ๋Ÿผ๋ช… + * @param inputType - ์ƒˆ๋กœ์šด ์ž…๋ ฅ ํƒ€์ž… + * @param companyCode - ํšŒ์‚ฌ ์ฝ”๋“œ + */ + private async syncScreenLayoutsInputType( + tableName: string, + columnName: string, + inputType: string, + companyCode: string + ): Promise { + try { + // ํ•ด๋‹น ์ปฌ๋Ÿผ์„ ์‚ฌ์šฉํ•˜๋Š” ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ + const affectedLayouts = await query<{ + layout_id: number; + screen_id: number; + component_id: string; + component_type: string; + properties: any; + }>( + `SELECT sl.layout_id, sl.screen_id, sl.component_id, sl.component_type, sl.properties + FROM screen_layouts sl + JOIN screen_definitions sd ON sl.screen_id = sd.screen_id + WHERE sl.properties->>'tableName' = $1 + AND sl.properties->>'columnName' = $2 + AND (sd.company_code = $3 OR $3 = '*')`, + [tableName, columnName, companyCode] + ); + + if (affectedLayouts.length === 0) { + logger.info( + `ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ๋™๊ธฐํ™”: ${tableName}.${columnName}์„ ์‚ฌ์šฉํ•˜๋Š” ํ™”๋ฉด ์—†์Œ` + ); + return; + } + + logger.info( + `ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ๋™๊ธฐํ™” ์‹œ์ž‘: ${affectedLayouts.length}๊ฐœ ์ปดํฌ๋„ŒํŠธ ๋ฐœ๊ฒฌ` + ); + + // ์ƒˆ๋กœ์šด componentType ๊ณ„์‚ฐ + const newComponentType = this.getComponentIdFromInputType(inputType); + + // ๊ฐ ๋ ˆ์ด์•„์›ƒ์˜ widgetType, componentType ์—…๋ฐ์ดํŠธ + for (const layout of affectedLayouts) { + const updatedProperties = { + ...layout.properties, + widgetType: inputType, + inputType: inputType, + // componentConfig ๋‚ด๋ถ€์˜ type๋„ ์—…๋ฐ์ดํŠธ + componentConfig: { + ...layout.properties?.componentConfig, + type: newComponentType, + inputType: inputType, + }, + }; + + await query( + `UPDATE screen_layouts + SET properties = $1, component_type = $2 + WHERE layout_id = $3`, + [JSON.stringify(updatedProperties), newComponentType, layout.layout_id] + ); + + logger.info( + `ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์—…๋ฐ์ดํŠธ: screen_id=${layout.screen_id}, component_id=${layout.component_id}, widgetType=${inputType}, componentType=${newComponentType}` + ); + } + + logger.info( + `ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ๋™๊ธฐํ™” ์™„๋ฃŒ: ${affectedLayouts.length}๊ฐœ ์ปดํฌ๋„ŒํŠธ ์—…๋ฐ์ดํŠธ๋จ` + ); + } catch (error) { + // ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ๋™๊ธฐํ™” ์‹คํŒจ๋Š” ์น˜๋ช…์ ์ด์ง€ ์•Š์œผ๋ฏ€๋กœ ๋กœ๊ทธ๋งŒ ๋‚จ๊ธฐ๊ณ  ๊ณ„์† ์ง„ํ–‰ + logger.warn( + `ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ๋™๊ธฐํ™” ์‹คํŒจ (๋ฌด์‹œ๋จ): ${tableName}.${columnName}`, + error + ); + } + } + /** * ์ž…๋ ฅ ํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ ์ƒ์„ธ ์„ค์ • ์ƒ์„ฑ */