diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 09a9691d..da7a3981 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -1461,6 +1461,40 @@ export class TableManagementService { }); } + // ๐Ÿ”ง ํŒŒ์ดํ”„๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด ์ฒ˜๋ฆฌ (๊ฐ์ฒด์—์„œ ์ถ”์ถœํ•œ actualValue๋„ ์ฒ˜๋ฆฌ) + if (typeof actualValue === "string" && actualValue.includes("|")) { + const columnInfo = await this.getColumnWebTypeInfo( + tableName, + columnName + ); + + // ๋‚ ์งœ ํƒ€์ž…์ด๋ฉด ๋‚ ์งœ ๋ฒ”์œ„๋กœ ์ฒ˜๋ฆฌ + if ( + columnInfo && + (columnInfo.webType === "date" || columnInfo.webType === "datetime") + ) { + return this.buildDateRangeCondition(columnName, actualValue, paramIndex); + } + + // ๊ทธ ์™ธ ํƒ€์ž…์ด๋ฉด ๋‹ค์ค‘์„ ํƒ(IN ์กฐ๊ฑด)์œผ๋กœ ์ฒ˜๋ฆฌ + const multiValues = actualValue + .split("|") + .filter((v: string) => v.trim() !== ""); + if (multiValues.length > 0) { + const placeholders = multiValues + .map((_: string, idx: number) => `$${paramIndex + idx}`) + .join(", "); + logger.info( + `๐Ÿ” ๋‹ค์ค‘์„ ํƒ ํ•„ํ„ฐ ์ ์šฉ (๊ฐ์ฒด์—์„œ ์ถ”์ถœ): ${columnName} IN (${multiValues.join(", ")})` + ); + return { + whereClause: `${columnName}::text IN (${placeholders})`, + values: multiValues, + paramCount: multiValues.length, + }; + } + } + // "__ALL__" ๊ฐ’์ด๊ฑฐ๋‚˜ ๋นˆ ๊ฐ’์ด๋ฉด ํ•„ํ„ฐ ์กฐ๊ฑด์„ ์ ์šฉํ•˜์ง€ ์•Š์Œ if ( actualValue === "__ALL__" || @@ -3428,15 +3462,37 @@ export class TableManagementService { // ๊ธฐ๋ณธ Entity ์กฐ์ธ ์ปฌ๋Ÿผ์ธ ๊ฒฝ์šฐ: ์กฐ์ธ๋œ ํ…Œ์ด๋ธ”์˜ ํ‘œ์‹œ ์ปฌ๋Ÿผ์—์„œ ๊ฒ€์ƒ‰ const aliasKey = `${joinConfig.referenceTable}:${joinConfig.sourceColumn}`; const alias = aliasMap.get(aliasKey); - whereConditions.push( - `${alias}.${joinConfig.displayColumn} ILIKE '%${safeValue}%'` - ); - entitySearchColumns.push( - `${key} (${joinConfig.referenceTable}.${joinConfig.displayColumn})` - ); - logger.info( - `๐ŸŽฏ Entity ์กฐ์ธ ๊ฒ€์ƒ‰: ${key} โ†’ ${joinConfig.referenceTable}.${joinConfig.displayColumn} LIKE '%${safeValue}%' (๋ณ„์นญ: ${alias})` - ); + + // ๐Ÿ”ง ํŒŒ์ดํ”„๋กœ ๊ตฌ๋ถ„๋œ ๋‹ค์ค‘ ์„ ํƒ๊ฐ’ ์ฒ˜๋ฆฌ + if (safeValue.includes("|")) { + const multiValues = safeValue + .split("|") + .filter((v: string) => v.trim() !== ""); + if (multiValues.length > 0) { + const inClause = multiValues + .map((v: string) => `'${v}'`) + .join(", "); + whereConditions.push( + `${alias}.${joinConfig.displayColumn}::text IN (${inClause})` + ); + entitySearchColumns.push( + `${key} (${joinConfig.referenceTable}.${joinConfig.displayColumn})` + ); + logger.info( + `๐ŸŽฏ Entity ์กฐ์ธ ๋‹ค์ค‘์„ ํƒ ๊ฒ€์ƒ‰: ${key} โ†’ ${joinConfig.referenceTable}.${joinConfig.displayColumn} IN (${multiValues.join(", ")}) (๋ณ„์นญ: ${alias})` + ); + } + } else { + whereConditions.push( + `${alias}.${joinConfig.displayColumn} ILIKE '%${safeValue}%'` + ); + entitySearchColumns.push( + `${key} (${joinConfig.referenceTable}.${joinConfig.displayColumn})` + ); + logger.info( + `๐ŸŽฏ Entity ์กฐ์ธ ๊ฒ€์ƒ‰: ${key} โ†’ ${joinConfig.referenceTable}.${joinConfig.displayColumn} LIKE '%${safeValue}%' (๋ณ„์นญ: ${alias})` + ); + } } else if (key === "writer_dept_code") { // writer_dept_code: user_info.dept_code์—์„œ ๊ฒ€์ƒ‰ const userAliasKey = Array.from(aliasMap.keys()).find((k) => @@ -3473,10 +3529,26 @@ export class TableManagementService { } } else { // ์ผ๋ฐ˜ ์ปฌ๋Ÿผ์ธ ๊ฒฝ์šฐ: ๋ฉ”์ธ ํ…Œ์ด๋ธ”์—์„œ ๊ฒ€์ƒ‰ - whereConditions.push(`main.${key} ILIKE '%${safeValue}%'`); - logger.info( - `๐Ÿ” ์ผ๋ฐ˜ ์ปฌ๋Ÿผ ๊ฒ€์ƒ‰: ${key} โ†’ main.${key} LIKE '%${safeValue}%'` - ); + // ๐Ÿ”ง ํŒŒ์ดํ”„๋กœ ๊ตฌ๋ถ„๋œ ๋‹ค์ค‘ ์„ ํƒ๊ฐ’ ์ฒ˜๋ฆฌ + if (safeValue.includes("|")) { + const multiValues = safeValue + .split("|") + .filter((v: string) => v.trim() !== ""); + if (multiValues.length > 0) { + const inClause = multiValues + .map((v: string) => `'${v}'`) + .join(", "); + whereConditions.push(`main.${key}::text IN (${inClause})`); + logger.info( + `๐Ÿ” ๋‹ค์ค‘์„ ํƒ ์ปฌ๋Ÿผ ๊ฒ€์ƒ‰: ${key} โ†’ main.${key} IN (${multiValues.join(", ")})` + ); + } + } else { + whereConditions.push(`main.${key} ILIKE '%${safeValue}%'`); + logger.info( + `๐Ÿ” ์ผ๋ฐ˜ ์ปฌ๋Ÿผ ๊ฒ€์ƒ‰: ${key} โ†’ main.${key} LIKE '%${safeValue}%'` + ); + } } } } diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx index b822aeee..6ea347c2 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx @@ -834,8 +834,10 @@ export const ButtonConfigPanel: React.FC = ({ {/* ์ด๋ฒคํŠธ ๋ฒ„์Šค */} ์ด๋ฒคํŠธ ๋ฐœ์†ก - {/* ๐Ÿ”’ ์ˆจ๊น€ ์ฒ˜๋ฆฌ - ๊ธฐ์กด ์‹œ์Šคํ…œ ํ˜ธํ™˜์„ฑ ์œ ์ง€, UI์—์„œ๋งŒ ์ˆจ๊น€ + {/* ๋ณต์‚ฌ */} ๋ณต์‚ฌ (ํ’ˆ๋ชฉ์ฝ”๋“œ ์ดˆ๊ธฐํ™”) + + {/* ๐Ÿ”’ ์ˆจ๊น€ ์ฒ˜๋ฆฌ - ๊ธฐ์กด ์‹œ์Šคํ…œ ํ˜ธํ™˜์„ฑ ์œ ์ง€, UI์—์„œ๋งŒ ์ˆจ๊น€ ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ๋ฒ„ํŠผ ๋ชจ๋‹ฌ ์—ด๊ธฐ (deprecated) ๋ฐ์ดํ„ฐ ์ „๋‹ฌ + ๋ชจ๋‹ฌ ์—ด๊ธฐ ํ…Œ์ด๋ธ” ์ด๋ ฅ ๋ณด๊ธฐ diff --git a/frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx index 83a7771d..918d7560 100644 --- a/frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx @@ -1324,7 +1324,31 @@ export const ButtonPrimaryComponent: React.FC = ({ ...userStyle, }; - const buttonContent = processedConfig.text !== undefined ? processedConfig.text : component.label || "๋ฒ„ํŠผ"; + // ๋ฒ„ํŠผ ํ…์ŠคํŠธ ๊ฒฐ์ • (๋‹ค์–‘ํ•œ ์†Œ์Šค์—์„œ ๊ฐ€์ ธ์˜ด) + // "๊ธฐ๋ณธ ๋ฒ„ํŠผ"์€ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ์‹œ ๊ธฐ๋ณธ๊ฐ’์ด๋ฏ€๋กœ ๋ฌด์‹œ + const labelValue = component.label === "๊ธฐ๋ณธ ๋ฒ„ํŠผ" ? undefined : component.label; + + // ์•ก์…˜ ํƒ€์ž…์— ๋”ฐ๋ฅธ ๊ธฐ๋ณธ ํ…์ŠคํŠธ (modal ์•ก์…˜๊ณผ ๋™์ผํ•˜๊ฒŒ) + const actionType = processedConfig.action?.type || component.componentConfig?.action?.type; + const actionDefaultText: Record = { + save: "์ €์žฅ", + delete: "์‚ญ์ œ", + modal: "๋“ฑ๋ก", + edit: "์ˆ˜์ •", + copy: "๋ณต์‚ฌ", + close: "๋‹ซ๊ธฐ", + cancel: "์ทจ์†Œ", + }; + + const buttonContent = + processedConfig.text || + component.webTypeConfig?.text || + component.componentConfig?.text || + component.config?.text || + component.style?.labelText || + labelValue || + actionDefaultText[actionType as string] || + "๋ฒ„ํŠผ"; return ( <> diff --git a/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx b/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx index c99f9876..02ef8643 100644 --- a/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx @@ -459,6 +459,9 @@ export const TableListComponent: React.FC = ({ // ๐Ÿ†• Filter Builder (๊ณ ๊ธ‰ ํ•„ํ„ฐ) ๊ด€๋ จ ์ƒํƒœ - filteredData๋ณด๋‹ค ๋จผ์ € ์ •์˜ํ•ด์•ผ ํ•จ const [filterGroups, setFilterGroups] = useState([]); + + // ๐Ÿ†• joinColumnMapping - filteredData์—์„œ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๋จผ์ € ์ •์˜ํ•ด์•ผ ํ•จ + const [joinColumnMapping, setJoinColumnMapping] = useState>({}); // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„์—์„œ ์šฐ์ธก์— ์ด๋ฏธ ์ถ”๊ฐ€๋œ ํ•ญ๋ชฉ ํ•„ํ„ฐ๋ง (์ขŒ์ธก ํ…Œ์ด๋ธ”์—๋งŒ ์ ์šฉ) + ํ—ค๋” ํ•„ํ„ฐ const filteredData = useMemo(() => { @@ -473,14 +476,17 @@ export const TableListComponent: React.FC = ({ }); } - // 2. ํ—ค๋” ํ•„ํ„ฐ ์ ์šฉ (joinColumnMapping ์‚ฌ์šฉ ์•ˆ ํ•จ - ์ง์ ‘ ์ปฌ๋Ÿผ๋ช… ์‚ฌ์šฉ) + // 2. ํ—ค๋” ํ•„ํ„ฐ ์ ์šฉ (joinColumnMapping ์‚ฌ์šฉ - ์กฐ์ธ๋œ ์ปฌ๋Ÿผ๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•จ) if (Object.keys(headerFilters).length > 0) { result = result.filter((row) => { return Object.entries(headerFilters).every(([columnName, values]) => { if (values.size === 0) return true; - // ์—ฌ๋Ÿฌ ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผ๋ช… ์‹œ๋„ - const cellValue = row[columnName] ?? row[columnName.toLowerCase()] ?? row[columnName.toUpperCase()]; + // joinColumnMapping์„ ์‚ฌ์šฉํ•˜์—ฌ ์กฐ์ธ๋œ ์ปฌ๋Ÿผ๋ช… ํ™•์ธ + const mappedColumnName = joinColumnMapping[columnName] || columnName; + + // ์—ฌ๋Ÿฌ ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผ๋ช… ์‹œ๋„ (mappedColumnName ์šฐ์„ ) + const cellValue = row[mappedColumnName] ?? row[columnName] ?? row[columnName.toLowerCase()] ?? row[columnName.toUpperCase()]; const cellStr = cellValue !== null && cellValue !== undefined ? String(cellValue) : ""; return values.has(cellStr); @@ -541,7 +547,7 @@ export const TableListComponent: React.FC = ({ } return result; - }, [data, splitPanelPosition, splitPanelContext?.addedItemIds, headerFilters, filterGroups]); + }, [data, splitPanelPosition, splitPanelContext?.addedItemIds, headerFilters, filterGroups, joinColumnMapping]); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(0); @@ -554,7 +560,6 @@ export const TableListComponent: React.FC = ({ const [tableLabel, setTableLabel] = useState(""); const [localPageSize, setLocalPageSize] = useState(tableConfig.pagination?.pageSize || 20); const [displayColumns, setDisplayColumns] = useState([]); - const [joinColumnMapping, setJoinColumnMapping] = useState>({}); const [columnMeta, setColumnMeta] = useState< Record >({});