diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index 3f599fa5..85159dc2 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -1617,10 +1617,11 @@ export async function getCategoryColumnsByMenu( logger.info("๐Ÿ“ฅ ๋ฉ”๋‰ด๋ณ„ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ ์š”์ฒญ", { menuObjid, companyCode }); if (!menuObjid) { - return res.status(400).json({ + res.status(400).json({ success: false, message: "๋ฉ”๋‰ด OBJID๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", }); + return; } // 1. ํ˜•์ œ ๋ฉ”๋‰ด ์กฐํšŒ @@ -1648,11 +1649,12 @@ export async function getCategoryColumnsByMenu( logger.info("โœ… ํ˜•์ œ ๋ฉ”๋‰ด ํ…Œ์ด๋ธ” ์กฐํšŒ ์™„๋ฃŒ", { tableNames, count: tableNames.length }); if (tableNames.length === 0) { - return res.json({ + res.json({ success: true, data: [], message: "ํ˜•์ œ ๋ฉ”๋‰ด์— ์—ฐ๊ฒฐ๋œ ํ…Œ์ด๋ธ”์ด ์—†์Šต๋‹ˆ๋‹ค.", }); + return; } // 3. ํ…Œ์ด๋ธ”๋“ค์˜ ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ปฌ๋Ÿผ ์กฐํšŒ (ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ํฌํ•จ) diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts index 40c05861..8cbd8a29 100644 --- a/backend-node/src/services/commonCodeService.ts +++ b/backend-node/src/services/commonCodeService.ts @@ -23,7 +23,8 @@ export interface CodeInfo { description?: string | null; sort_order: number; is_active: string; - company_code: string; // ์ถ”๊ฐ€ + company_code: string; + menu_objid?: number | null; // ๋ฉ”๋‰ด ๊ธฐ๋ฐ˜ ์ฝ”๋“œ ๊ด€๋ฆฌ์šฉ created_date?: Date | null; created_by?: string | null; updated_date?: Date | null; diff --git a/backend-node/src/services/ddlExecutionService.ts b/backend-node/src/services/ddlExecutionService.ts index 2ed01231..c7a611d3 100644 --- a/backend-node/src/services/ddlExecutionService.ts +++ b/backend-node/src/services/ddlExecutionService.ts @@ -104,7 +104,7 @@ export class DDLExecutionService { await this.saveTableMetadata(client, tableName, description); // 5-3. ์ปฌ๋Ÿผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ - await this.saveColumnMetadata(client, tableName, columns); + await this.saveColumnMetadata(client, tableName, columns, userCompanyCode); }); // 6. ์„ฑ๊ณต ๋กœ๊ทธ ๊ธฐ๋ก @@ -272,7 +272,7 @@ export class DDLExecutionService { await client.query(ddlQuery); // 6-2. ์ปฌ๋Ÿผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ - await this.saveColumnMetadata(client, tableName, [column]); + await this.saveColumnMetadata(client, tableName, [column], userCompanyCode); }); // 7. ์„ฑ๊ณต ๋กœ๊ทธ ๊ธฐ๋ก @@ -446,7 +446,8 @@ CREATE TABLE "${tableName}" (${baseColumns}, private async saveColumnMetadata( client: any, tableName: string, - columns: CreateColumnDefinition[] + columns: CreateColumnDefinition[], + companyCode: string ): Promise { // ๋จผ์ € table_labels์— ํ…Œ์ด๋ธ” ์ •๋ณด๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์—†์œผ๋ฉด ์ƒ์„ฑ await client.query( @@ -508,19 +509,19 @@ CREATE TABLE "${tableName}" (${baseColumns}, await client.query( ` INSERT INTO table_type_columns ( - table_name, column_name, input_type, detail_settings, + table_name, column_name, company_code, input_type, detail_settings, is_nullable, display_order, created_date, updated_date ) VALUES ( - $1, $2, $3, '{}', - 'Y', $4, now(), now() + $1, $2, $3, $4, '{}', + 'Y', $5, now(), now() ) - ON CONFLICT (table_name, column_name) + ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET - input_type = $3, - display_order = $4, + input_type = $4, + display_order = $5, updated_date = now() `, - [tableName, defaultCol.name, defaultCol.inputType, defaultCol.order] + [tableName, defaultCol.name, companyCode, defaultCol.inputType, defaultCol.order] ); } @@ -535,20 +536,20 @@ CREATE TABLE "${tableName}" (${baseColumns}, await client.query( ` INSERT INTO table_type_columns ( - table_name, column_name, input_type, detail_settings, + table_name, column_name, company_code, input_type, detail_settings, is_nullable, display_order, created_date, updated_date ) VALUES ( - $1, $2, $3, $4, - 'Y', $5, now(), now() + $1, $2, $3, $4, $5, + 'Y', $6, now(), now() ) - ON CONFLICT (table_name, column_name) + ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET - input_type = $3, - detail_settings = $4, - display_order = $5, + input_type = $4, + detail_settings = $5, + display_order = $6, updated_date = now() `, - [tableName, column.name, inputType, detailSettings, i] + [tableName, column.name, companyCode, inputType, detailSettings, i] ); } diff --git a/backend-node/src/services/menuService.ts b/backend-node/src/services/menuService.ts index b22beb88..86df579c 100644 --- a/backend-node/src/services/menuService.ts +++ b/backend-node/src/services/menuService.ts @@ -36,29 +36,61 @@ export async function getSiblingMenuObjids(menuObjid: number): Promise try { logger.debug("๋ฉ”๋‰ด ์Šค์ฝ”ํ”„ ์กฐํšŒ ์‹œ์ž‘", { menuObjid }); - // 1. ํ˜„์žฌ ๋ฉ”๋‰ด ์ž์‹ ์„ ํฌํ•จ - const menuObjids = [menuObjid]; + // 1. ํ˜„์žฌ ๋ฉ”๋‰ด ์ •๋ณด ์กฐํšŒ (๋ถ€๋ชจ ID ํ™•์ธ) + const currentMenuQuery = ` + SELECT parent_obj_id FROM menu_info + WHERE objid = $1 + `; + const currentMenuResult = await pool.query(currentMenuQuery, [menuObjid]); - // 2. ํ˜„์žฌ ๋ฉ”๋‰ด์˜ ์ž์‹ ๋ฉ”๋‰ด๋“ค ์กฐํšŒ - const childrenQuery = ` + if (currentMenuResult.rows.length === 0) { + logger.warn("๋ฉ”๋‰ด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ, ์ž๊ธฐ ์ž์‹ ๋งŒ ๋ฐ˜ํ™˜", { menuObjid }); + return [menuObjid]; + } + + const parentObjId = Number(currentMenuResult.rows[0].parent_obj_id); + + // 2. ์ตœ์ƒ์œ„ ๋ฉ”๋‰ด(parent_obj_id = 0)๋Š” ์ž๊ธฐ ์ž์‹ ๋งŒ ๋ฐ˜ํ™˜ + if (parentObjId === 0) { + logger.debug("์ตœ์ƒ์œ„ ๋ฉ”๋‰ด, ์ž๊ธฐ ์ž์‹ ๋งŒ ๋ฐ˜ํ™˜", { menuObjid }); + return [menuObjid]; + } + + // 3. ํ˜•์ œ ๋ฉ”๋‰ด๋“ค ์กฐํšŒ (๊ฐ™์€ ๋ถ€๋ชจ๋ฅผ ๊ฐ€์ง„ ๋ฉ”๋‰ด๋“ค) + const siblingsQuery = ` SELECT objid FROM menu_info WHERE parent_obj_id = $1 ORDER BY objid `; - const childrenResult = await pool.query(childrenQuery, [menuObjid]); + const siblingsResult = await pool.query(siblingsQuery, [parentObjId]); - const childObjids = childrenResult.rows.map((row) => Number(row.objid)); + const siblingObjids = siblingsResult.rows.map((row) => Number(row.objid)); - // 3. ์ž์‹  + ์ž์‹์„ ํ•ฉ์ณ์„œ ์ •๋ ฌ - const allObjids = Array.from(new Set([...menuObjids, ...childObjids])).sort((a, b) => a - b); + // 4. ๊ฐ ํ˜•์ œ ๋ฉ”๋‰ด(์ž๊ธฐ ์ž์‹  ํฌํ•จ)์˜ ์ž์‹ ๋ฉ”๋‰ด๋“ค๋„ ์กฐํšŒ + const allObjids = [...siblingObjids]; + + for (const siblingObjid of siblingObjids) { + const childrenQuery = ` + SELECT objid FROM menu_info + WHERE parent_obj_id = $1 + ORDER BY objid + `; + const childrenResult = await pool.query(childrenQuery, [siblingObjid]); + const childObjids = childrenResult.rows.map((row) => Number(row.objid)); + allObjids.push(...childObjids); + } + + // 5. ์ค‘๋ณต ์ œ๊ฑฐ ๋ฐ ์ •๋ ฌ + const uniqueObjids = Array.from(new Set(allObjids)).sort((a, b) => a - b); logger.debug("๋ฉ”๋‰ด ์Šค์ฝ”ํ”„ ์กฐํšŒ ์™„๋ฃŒ", { - menuObjid, - childCount: childObjids.length, - totalCount: allObjids.length + menuObjid, + parentObjId, + siblingCount: siblingObjids.length, + totalCount: uniqueObjids.length }); - return allObjids; + return uniqueObjids; } catch (error: any) { logger.error("๋ฉ”๋‰ด ์Šค์ฝ”ํ”„ ์กฐํšŒ ์‹คํŒจ", { menuObjid, diff --git a/backend-node/src/services/numberingRuleService.ts b/backend-node/src/services/numberingRuleService.ts index db76bbee..368559df 100644 --- a/backend-node/src/services/numberingRuleService.ts +++ b/backend-node/src/services/numberingRuleService.ts @@ -161,6 +161,8 @@ class NumberingRuleService { companyCode: string, menuObjid?: number ): Promise { + let siblingObjids: number[] = []; // catch ๋ธ”๋ก์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ์ˆ˜ ์ตœ์ƒ๋‹จ์— ์„ ์–ธ + try { logger.info("๋ฉ”๋‰ด๋ณ„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฑ„๋ฒˆ ๊ทœ์น™ ์กฐํšŒ ์‹œ์ž‘ (๋ฉ”๋‰ด ์Šค์ฝ”ํ”„)", { companyCode, @@ -170,7 +172,6 @@ class NumberingRuleService { const pool = getPool(); // 1. ํ˜•์ œ ๋ฉ”๋‰ด OBJID ์กฐํšŒ - let siblingObjids: number[] = []; if (menuObjid) { siblingObjids = await getSiblingMenuObjids(menuObjid); logger.info("ํ˜•์ œ ๋ฉ”๋‰ด OBJID ๋ชฉ๋ก", { menuObjid, siblingObjids }); diff --git a/backend-node/src/services/tableCategoryValueService.ts b/backend-node/src/services/tableCategoryValueService.ts index 29cad453..c5d51db5 100644 --- a/backend-node/src/services/tableCategoryValueService.ts +++ b/backend-node/src/services/tableCategoryValueService.ts @@ -179,7 +179,8 @@ class TableCategoryValueService { } else { // ์ผ๋ฐ˜ ํšŒ์‚ฌ: ์ž์‹ ์˜ ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’๋งŒ ์กฐํšŒ if (menuObjid && siblingObjids.length > 0) { - // ๋ฉ”๋‰ด ์Šค์ฝ”ํ”„ ์ ์šฉ + // ๋ฉ”๋‰ด ์Šค์ฝ”ํ”„ ์ ์šฉ + created_menu_objid ํ•„ํ„ฐ๋ง + // ํ˜„์žฌ ๋ฉ”๋‰ด ์Šค์ฝ”ํ”„(ํ˜•์ œ ๋ฉ”๋‰ด)์—์„œ ์ƒ์„ฑ๋œ ๊ฐ’๋งŒ ํ‘œ์‹œ query = ` SELECT value_id AS "valueId", @@ -197,6 +198,7 @@ class TableCategoryValueService { is_default AS "isDefault", company_code AS "companyCode", menu_objid AS "menuObjid", + created_menu_objid AS "createdMenuObjid", created_at AS "createdAt", updated_at AS "updatedAt", created_by AS "createdBy", @@ -206,6 +208,10 @@ class TableCategoryValueService { AND column_name = $2 AND menu_objid = ANY($3) AND company_code = $4 + AND ( + created_menu_objid = ANY($3) -- ํ˜•์ œ ๋ฉ”๋‰ด์—์„œ ์ƒ์„ฑ๋œ ๊ฐ’๋งŒ + OR created_menu_objid IS NULL -- ๋ ˆ๊ฑฐ์‹œ ๋ฐ์ดํ„ฐ (๋ชจ๋“  ๋ฉ”๋‰ด์—์„œ ๋ณด์ž„) + ) `; params = [tableName, columnName, siblingObjids, companyCode]; } else { @@ -331,8 +337,8 @@ class TableCategoryValueService { INSERT INTO table_column_category_values ( table_name, column_name, value_code, value_label, value_order, parent_value_id, depth, description, color, icon, - is_active, is_default, company_code, menu_objid, created_by - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) + is_active, is_default, company_code, menu_objid, created_menu_objid, created_by + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING value_id AS "valueId", table_name AS "tableName", @@ -349,6 +355,7 @@ class TableCategoryValueService { is_default AS "isDefault", company_code AS "companyCode", menu_objid AS "menuObjid", + created_menu_objid AS "createdMenuObjid", created_at AS "createdAt", created_by AS "createdBy" `; @@ -368,6 +375,7 @@ class TableCategoryValueService { value.isDefault || false, companyCode, menuObjid, // โ† ๋ฉ”๋‰ด OBJID ์ €์žฅ + menuObjid, // โ† ๐Ÿ†• ์ƒ์„ฑ ๋ฉ”๋‰ด OBJID ์ €์žฅ (๊ฐ™์€ ๊ฐ’) userId, ]); diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index fd2e82a7..ac8b62fd 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -1069,12 +1069,28 @@ export class TableManagementService { paramCount: number; } | null> { try { + // ๐Ÿ”ง {value, operator} ํ˜•ํƒœ์˜ ํ•„ํ„ฐ ๊ฐ์ฒด ์ฒ˜๋ฆฌ + let actualValue = value; + let operator = "contains"; // ๊ธฐ๋ณธ๊ฐ’ + + if (typeof value === "object" && value !== null && "value" in value) { + actualValue = value.value; + operator = value.operator || "contains"; + + logger.info("๐Ÿ” ํ•„ํ„ฐ ๊ฐ์ฒด ์ฒ˜๋ฆฌ:", { + columnName, + originalValue: value, + actualValue, + operator, + }); + } + // "__ALL__" ๊ฐ’์ด๊ฑฐ๋‚˜ ๋นˆ ๊ฐ’์ด๋ฉด ํ•„ํ„ฐ ์กฐ๊ฑด์„ ์ ์šฉํ•˜์ง€ ์•Š์Œ if ( - value === "__ALL__" || - value === "" || - value === null || - value === undefined + actualValue === "__ALL__" || + actualValue === "" || + actualValue === null || + actualValue === undefined ) { return null; } @@ -1083,12 +1099,22 @@ export class TableManagementService { const columnInfo = await this.getColumnWebTypeInfo(tableName, columnName); if (!columnInfo) { - // ์ปฌ๋Ÿผ ์ •๋ณด๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋ณธ ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰ - return { - whereClause: `${columnName}::text ILIKE $${paramIndex}`, - values: [`%${value}%`], - paramCount: 1, - }; + // ์ปฌ๋Ÿผ ์ •๋ณด๊ฐ€ ์—†์œผ๋ฉด operator์— ๋”ฐ๋ฅธ ๊ธฐ๋ณธ ๊ฒ€์ƒ‰ + switch (operator) { + case "equals": + return { + whereClause: `${columnName}::text = $${paramIndex}`, + values: [actualValue], + paramCount: 1, + }; + case "contains": + default: + return { + whereClause: `${columnName}::text ILIKE $${paramIndex}`, + values: [`%${actualValue}%`], + paramCount: 1, + }; + } } const webType = columnInfo.webType; @@ -1097,17 +1123,17 @@ export class TableManagementService { switch (webType) { case "date": case "datetime": - return this.buildDateRangeCondition(columnName, value, paramIndex); + return this.buildDateRangeCondition(columnName, actualValue, paramIndex); case "number": case "decimal": - return this.buildNumberRangeCondition(columnName, value, paramIndex); + return this.buildNumberRangeCondition(columnName, actualValue, paramIndex); case "code": return await this.buildCodeSearchCondition( tableName, columnName, - value, + actualValue, paramIndex ); @@ -1115,15 +1141,15 @@ export class TableManagementService { return await this.buildEntitySearchCondition( tableName, columnName, - value, + actualValue, paramIndex ); default: - // ๊ธฐ๋ณธ ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰ + // ๊ธฐ๋ณธ ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰ (actualValue ์‚ฌ์šฉ) return { whereClause: `${columnName}::text ILIKE $${paramIndex}`, - values: [`%${value}%`], + values: [`%${actualValue}%`], paramCount: 1, }; } @@ -1133,9 +1159,14 @@ export class TableManagementService { error ); // ์˜ค๋ฅ˜ ์‹œ ๊ธฐ๋ณธ ๊ฒ€์ƒ‰์œผ๋กœ ํด๋ฐฑ + let fallbackValue = value; + if (typeof value === "object" && value !== null && "value" in value) { + fallbackValue = value.value; + } + return { whereClause: `${columnName}::text ILIKE $${paramIndex}`, - values: [`%${value}%`], + values: [`%${fallbackValue}%`], paramCount: 1, }; } @@ -2057,6 +2088,7 @@ export class TableManagementService { sortBy?: string; sortOrder?: string; enableEntityJoin?: boolean; + companyCode?: string; // ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ•„ํ„ฐ์šฉ additionalJoinColumns?: Array<{ sourceTable: string; sourceColumn: string; diff --git a/frontend/components/screen/table-options/ColumnVisibilityPanel.tsx b/frontend/components/screen/table-options/ColumnVisibilityPanel.tsx index 2373aa0a..c03dac58 100644 --- a/frontend/components/screen/table-options/ColumnVisibilityPanel.tsx +++ b/frontend/components/screen/table-options/ColumnVisibilityPanel.tsx @@ -85,6 +85,15 @@ export const ColumnVisibilityPanel: React.FC = ({ const handleApply = () => { table?.onColumnVisibilityChange(localColumns); + + // ์ปฌ๋Ÿผ ์ˆœ์„œ ๋ณ€๊ฒฝ ์ฝœ๋ฐฑ ํ˜ธ์ถœ + if (table?.onColumnOrderChange) { + const newOrder = localColumns + .map((col) => col.columnName) + .filter((name) => name !== "__checkbox__"); + table.onColumnOrderChange(newOrder); + } + onClose(); }; diff --git a/frontend/components/screen/widgets/CategoryWidget.tsx b/frontend/components/screen/widgets/CategoryWidget.tsx index 2974ed60..a4e93256 100644 --- a/frontend/components/screen/widgets/CategoryWidget.tsx +++ b/frontend/components/screen/widgets/CategoryWidget.tsx @@ -49,6 +49,7 @@ export function CategoryWidget({ widgetId, tableName, menuObjid, component, ...p const effectiveMenuObjid = menuObjid || props.menuObjid; const [selectedColumn, setSelectedColumn] = useState<{ + uniqueKey: string; // ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช… ํ˜•์‹ columnName: string; columnLabel: string; tableName: string; @@ -98,10 +99,12 @@ export function CategoryWidget({ widgetId, tableName, menuObjid, component, ...p
- setSelectedColumn({ columnName, columnLabel, tableName }) - } + selectedColumn={selectedColumn?.uniqueKey || null} + onColumnSelect={(uniqueKey, columnLabel, tableName) => { + // uniqueKey๋Š” "ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช…" ํ˜•์‹ + const columnName = uniqueKey.split('.')[1]; + setSelectedColumn({ uniqueKey, columnName, columnLabel, tableName }); + }} menuObjid={effectiveMenuObjid} />
@@ -118,6 +121,7 @@ export function CategoryWidget({ widgetId, tableName, menuObjid, component, ...p
{selectedColumn ? ( {columns.map((column) => { const uniqueKey = `${column.tableName}.${column.columnName}`; + const isSelected = selectedColumn === uniqueKey; // ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช…์œผ๋กœ ๋น„๊ต return (
onColumnSelect(column.columnName, column.columnLabel || column.columnName, column.tableName)} + onClick={() => onColumnSelect(uniqueKey, column.columnLabel || column.columnName, column.tableName)} className={`cursor-pointer rounded-lg border px-4 py-2 transition-all ${ - selectedColumn === column.columnName ? "border-primary bg-primary/10 shadow-sm" : "hover:bg-muted/50" + isSelected ? "border-primary bg-primary/10 shadow-sm" : "hover:bg-muted/50" }`} >

{column.columnLabel || column.columnName}

diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 483fc393..91947094 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useCallback, useEffect } from "react"; +import React, { useState, useCallback, useEffect, useMemo } from "react"; import { ComponentRendererProps } from "../../types"; import { SplitPanelLayoutConfig } from "./types"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -8,12 +8,14 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp, Save, ChevronRight, Pencil, Trash2 } from "lucide-react"; import { dataApi } from "@/lib/api/data"; +import { entityJoinApi } from "@/lib/api/entityJoin"; import { useToast } from "@/hooks/use-toast"; import { tableTypeApi } from "@/lib/api/screen"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { useTableOptions } from "@/contexts/TableOptionsContext"; import { TableFilter, ColumnVisibility } from "@/types/table-options"; +import { useAuth } from "@/hooks/useAuth"; export interface SplitPanelLayoutComponentProps extends ComponentRendererProps { // ์ถ”๊ฐ€ props @@ -44,6 +46,7 @@ export const SplitPanelLayoutComponent: React.FC const [leftFilters, setLeftFilters] = useState([]); const [leftGrouping, setLeftGrouping] = useState([]); const [leftColumnVisibility, setLeftColumnVisibility] = useState([]); + const [leftColumnOrder, setLeftColumnOrder] = useState([]); // ๐Ÿ”ง ์ปฌ๋Ÿผ ์ˆœ์„œ const [rightFilters, setRightFilters] = useState([]); const [rightGrouping, setRightGrouping] = useState([]); const [rightColumnVisibility, setRightColumnVisibility] = useState([]); @@ -160,6 +163,84 @@ export const SplitPanelLayoutComponent: React.FC return rootItems; }, [componentConfig.leftPanel?.itemAddConfig]); + // ๐Ÿ”ง ์‚ฌ์šฉ์ž ID ๊ฐ€์ ธ์˜ค๊ธฐ + const { userId: currentUserId } = useAuth(); + + // ๐Ÿ”„ ํ•„ํ„ฐ๋ฅผ searchValues ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ + const searchValues = useMemo(() => { + if (!leftFilters || leftFilters.length === 0) return {}; + + const values: Record = {}; + leftFilters.forEach(filter => { + if (filter.value !== undefined && filter.value !== null && filter.value !== '') { + values[filter.columnName] = { + value: filter.value, + operator: filter.operator || 'contains', + }; + } + }); + return values; + }, [leftFilters]); + + // ๐Ÿ”„ ์ปฌ๋Ÿผ ๊ฐ€์‹œ์„ฑ ๋ฐ ์ˆœ์„œ ์ฒ˜๋ฆฌ + const visibleLeftColumns = useMemo(() => { + const displayColumns = componentConfig.leftPanel?.columns || []; + + if (displayColumns.length === 0) return []; + + let columns = displayColumns; + + // columnVisibility๊ฐ€ ์žˆ์œผ๋ฉด ๊ฐ€์‹œ์„ฑ ์ ์šฉ + if (leftColumnVisibility.length > 0) { + const visibilityMap = new Map(leftColumnVisibility.map(cv => [cv.columnName, cv.visible])); + columns = columns.filter((col: any) => { + const colName = typeof col === 'string' ? col : (col.name || col.columnName); + return visibilityMap.get(colName) !== false; + }); + } + + // ๐Ÿ”ง ์ปฌ๋Ÿผ ์ˆœ์„œ ์ ์šฉ + if (leftColumnOrder.length > 0) { + const orderMap = new Map(leftColumnOrder.map((name, index) => [name, index])); + columns = [...columns].sort((a, b) => { + const aName = typeof a === 'string' ? a : (a.name || a.columnName); + const bName = typeof b === 'string' ? b : (b.name || b.columnName); + const aIndex = orderMap.get(aName) ?? 999; + const bIndex = orderMap.get(bName) ?? 999; + return aIndex - bIndex; + }); + } + + return columns; + }, [componentConfig.leftPanel?.columns, leftColumnVisibility, leftColumnOrder]); + + // ๐Ÿ”„ ๋ฐ์ดํ„ฐ ๊ทธ๋ฃนํ™” + const groupedLeftData = useMemo(() => { + if (!leftGrouping || leftGrouping.length === 0 || leftData.length === 0) return []; + + const grouped = new Map(); + + leftData.forEach((item) => { + // ๊ฐ ๊ทธ๋ฃน ์ปฌ๋Ÿผ์˜ ๊ฐ’์„ ์กฐํ•ฉํ•˜์—ฌ ๊ทธ๋ฃน ํ‚ค ์ƒ์„ฑ + const groupKey = leftGrouping.map(col => { + const value = item[col]; + // null/undefined ์ฒ˜๋ฆฌ + return value === null || value === undefined ? "(๋น„์–ด์žˆ์Œ)" : String(value); + }).join(" > "); + + if (!grouped.has(groupKey)) { + grouped.set(groupKey, []); + } + grouped.get(groupKey)!.push(item); + }); + + return Array.from(grouped.entries()).map(([key, items]) => ({ + groupKey: key, + items, + count: items.length, + })); + }, [leftData, leftGrouping]); + // ์ขŒ์ธก ๋ฐ์ดํ„ฐ ๋กœ๋“œ const loadLeftData = useCallback(async () => { const leftTableName = componentConfig.leftPanel?.tableName; @@ -167,12 +248,18 @@ export const SplitPanelLayoutComponent: React.FC setIsLoadingLeft(true); try { - const result = await dataApi.getTableData(leftTableName, { + // ๐ŸŽฏ ํ•„ํ„ฐ ์กฐ๊ฑด์„ API์— ์ „๋‹ฌ (entityJoinApi ์‚ฌ์šฉ) + const filters = Object.keys(searchValues).length > 0 ? searchValues : undefined; + + + const result = await entityJoinApi.getTableDataWithJoins(leftTableName, { page: 1, size: 100, - // searchTerm ์ œ๊ฑฐ - ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ํ•„ํ„ฐ๋ง + search: filters, // ํ•„ํ„ฐ ์กฐ๊ฑด ์ „๋‹ฌ + enableEntityJoin: true, // ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ํ™œ์„ฑํ™” }); + // ๊ฐ€๋‚˜๋‹ค์ˆœ ์ •๋ ฌ (์ขŒ์ธก ํŒจ๋„์˜ ํ‘œ์‹œ ์ปฌ๋Ÿผ ๊ธฐ์ค€) const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; if (leftColumn && result.data.length > 0) { @@ -196,7 +283,7 @@ export const SplitPanelLayoutComponent: React.FC } finally { setIsLoadingLeft(false); } - }, [componentConfig.leftPanel?.tableName, componentConfig.rightPanel?.relation?.leftColumn, isDesignMode, toast, buildHierarchy]); + }, [componentConfig.leftPanel?.tableName, componentConfig.rightPanel?.relation?.leftColumn, isDesignMode, toast, buildHierarchy, searchValues]); // ์šฐ์ธก ๋ฐ์ดํ„ฐ ๋กœ๋“œ const loadRightData = useCallback( @@ -283,67 +370,101 @@ export const SplitPanelLayoutComponent: React.FC [rightTableColumns], ); + // ๐Ÿ”ง ์ปฌ๋Ÿผ์˜ ๊ณ ์œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ ํ•จ์ˆ˜ + const getLeftColumnUniqueValues = useCallback(async (columnName: string) => { + const leftTableName = componentConfig.leftPanel?.tableName; + if (!leftTableName || leftData.length === 0) return []; + + // ํ˜„์žฌ ๋กœ๋“œ๋œ ๋ฐ์ดํ„ฐ์—์„œ ๊ณ ์œ ๊ฐ’ ์ถ”์ถœ + const uniqueValues = new Set(); + + leftData.forEach((item) => { + const value = item[columnName]; + if (value !== null && value !== undefined && value !== '') { + // _name ํ•„๋“œ ์šฐ์„  ์‚ฌ์šฉ (category/entity type) + const displayValue = item[`${columnName}_name`] || value; + uniqueValues.add(String(displayValue)); + } + }); + + return Array.from(uniqueValues).map(value => ({ + value: value, + label: value, + })); + }, [componentConfig.leftPanel?.tableName, leftData]); + // ์ขŒ์ธก ํ…Œ์ด๋ธ” ๋“ฑ๋ก (Context์— ๋“ฑ๋ก) useEffect(() => { const leftTableName = componentConfig.leftPanel?.tableName; if (!leftTableName || isDesignMode) return; const leftTableId = `split-panel-left-${component.id}`; - const leftColumns = componentConfig.leftPanel?.displayColumns || []; + // ๐Ÿ”ง ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š” ์ปฌ๋Ÿผ ์‚ฌ์šฉ (columns ์†์„ฑ) + const configuredColumns = componentConfig.leftPanel?.columns || []; + const displayColumns = configuredColumns.map((col: any) => { + if (typeof col === 'string') return col; + return col.columnName || col.name || col; + }).filter(Boolean); + + // ํ™”๋ฉด์— ์„ค์ •๋œ ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ๋“ฑ๋กํ•˜์ง€ ์•Š์Œ + if (displayColumns.length === 0) return; - if (leftColumns.length > 0) { - registerTable({ - tableId: leftTableId, - label: `${component.title || "๋ถ„ํ•  ํŒจ๋„"} (์ขŒ์ธก)`, - tableName: leftTableName, - columns: leftColumns.map((col: string) => ({ - columnName: col, - columnLabel: leftColumnLabels[col] || col, - inputType: "text", - visible: true, - width: 150, - sortable: true, - filterable: true, - })), - onFilterChange: setLeftFilters, - onGroupChange: setLeftGrouping, - onColumnVisibilityChange: setLeftColumnVisibility, - }); + // ํ…Œ์ด๋ธ”๋ช…์ด ์žˆ์œผ๋ฉด ๋“ฑ๋ก + registerTable({ + tableId: leftTableId, + label: `${component.title || "๋ถ„ํ•  ํŒจ๋„"} (์ขŒ์ธก)`, + tableName: leftTableName, + columns: displayColumns.map((col: string) => ({ + columnName: col, + columnLabel: leftColumnLabels[col] || col, + inputType: "text", + visible: true, + width: 150, + sortable: true, + filterable: true, + })), + onFilterChange: setLeftFilters, + onGroupChange: setLeftGrouping, + onColumnVisibilityChange: setLeftColumnVisibility, + onColumnOrderChange: setLeftColumnOrder, // ๐Ÿ”ง ์ปฌ๋Ÿผ ์ˆœ์„œ ๋ณ€๊ฒฝ ์ฝœ๋ฐฑ ์ถ”๊ฐ€ + getColumnUniqueValues: getLeftColumnUniqueValues, // ๐Ÿ”ง ๊ณ ์œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ ํ•จ์ˆ˜ ์ถ”๊ฐ€ + }); - return () => unregisterTable(leftTableId); - } - }, [component.id, componentConfig.leftPanel?.tableName, componentConfig.leftPanel?.displayColumns, leftColumnLabels, component.title, isDesignMode]); + return () => unregisterTable(leftTableId); + }, [component.id, componentConfig.leftPanel?.tableName, componentConfig.leftPanel?.columns, leftColumnLabels, component.title, isDesignMode, getLeftColumnUniqueValues]); - // ์šฐ์ธก ํ…Œ์ด๋ธ” ๋“ฑ๋ก (Context์— ๋“ฑ๋ก) - useEffect(() => { - const rightTableName = componentConfig.rightPanel?.tableName; - if (!rightTableName || isDesignMode) return; - - const rightTableId = `split-panel-right-${component.id}`; - const rightColumns = rightTableColumns.map((col: any) => col.columnName || col.column_name).filter(Boolean); - - if (rightColumns.length > 0) { - registerTable({ - tableId: rightTableId, - label: `${component.title || "๋ถ„ํ•  ํŒจ๋„"} (์šฐ์ธก)`, - tableName: rightTableName, - columns: rightColumns.map((col: string) => ({ - columnName: col, - columnLabel: rightColumnLabels[col] || col, - inputType: "text", - visible: true, - width: 150, - sortable: true, - filterable: true, - })), - onFilterChange: setRightFilters, - onGroupChange: setRightGrouping, - onColumnVisibilityChange: setRightColumnVisibility, - }); - - return () => unregisterTable(rightTableId); - } - }, [component.id, componentConfig.rightPanel?.tableName, rightTableColumns, rightColumnLabels, component.title, isDesignMode]); + // ์šฐ์ธก ํ…Œ์ด๋ธ”์€ ๊ฒ€์ƒ‰ ์ปดํฌ๋„ŒํŠธ ๋“ฑ๋ก ์ œ์™ธ (์ขŒ์ธก ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”๋งŒ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ) + // useEffect(() => { + // const rightTableName = componentConfig.rightPanel?.tableName; + // if (!rightTableName || isDesignMode) return; + // + // const rightTableId = `split-panel-right-${component.id}`; + // // ๐Ÿ”ง ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š” ์ปฌ๋Ÿผ๋งŒ ๋“ฑ๋ก (displayColumns ๋˜๋Š” columns) + // const displayColumns = componentConfig.rightPanel?.columns || []; + // const rightColumns = displayColumns.map((col: any) => col.columnName || col.name || col).filter(Boolean); + // + // if (rightColumns.length > 0) { + // registerTable({ + // tableId: rightTableId, + // label: `${component.title || "๋ถ„ํ•  ํŒจ๋„"} (์šฐ์ธก)`, + // tableName: rightTableName, + // columns: rightColumns.map((col: string) => ({ + // columnName: col, + // columnLabel: rightColumnLabels[col] || col, + // inputType: "text", + // visible: true, + // width: 150, + // sortable: true, + // filterable: true, + // })), + // onFilterChange: setRightFilters, + // onGroupChange: setRightGrouping, + // onColumnVisibilityChange: setRightColumnVisibility, + // }); + // + // return () => unregisterTable(rightTableId); + // } + // }, [component.id, componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.columns, rightColumnLabels, component.title, isDesignMode]); // ์ขŒ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ useEffect(() => { @@ -786,6 +907,43 @@ export const SplitPanelLayoutComponent: React.FC } }, [addModalPanel, componentConfig, addModalFormData, toast, selectedLeftItem, loadLeftData, loadRightData]); + // ๐Ÿ”ง ์ขŒ์ธก ์ปฌ๋Ÿผ ๊ฐ€์‹œ์„ฑ ์„ค์ • ์ €์žฅ ๋ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ + useEffect(() => { + const leftTableName = componentConfig.leftPanel?.tableName; + if (leftTableName && currentUserId) { + // localStorage์—์„œ ์ €์žฅ๋œ ์„ค์ • ๋ถˆ๋Ÿฌ์˜ค๊ธฐ + const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`; + const savedSettings = localStorage.getItem(storageKey); + + if (savedSettings) { + try { + const parsed = JSON.parse(savedSettings) as ColumnVisibility[]; + setLeftColumnVisibility(parsed); + } catch (error) { + console.error("์ €์žฅ๋œ ์ปฌ๋Ÿผ ์„ค์ • ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ:", error); + } + } + } + }, [componentConfig.leftPanel?.tableName, currentUserId]); + + // ๐Ÿ”ง ์ปฌ๋Ÿผ ๊ฐ€์‹œ์„ฑ ๋ณ€๊ฒฝ ์‹œ localStorage์— ์ €์žฅ ๋ฐ ์ˆœ์„œ ์—…๋ฐ์ดํŠธ + useEffect(() => { + const leftTableName = componentConfig.leftPanel?.tableName; + + if (leftColumnVisibility.length > 0 && leftTableName && currentUserId) { + // ์ˆœ์„œ ์—…๋ฐ์ดํŠธ + const newOrder = leftColumnVisibility + .map((cv) => cv.columnName) + .filter((name) => name !== "__checkbox__"); // ์ฒดํฌ๋ฐ•์Šค ์ œ์™ธ + + setLeftColumnOrder(newOrder); + + // localStorage์— ์ €์žฅ + const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`; + localStorage.setItem(storageKey, JSON.stringify(leftColumnVisibility)); + } + }, [leftColumnVisibility, componentConfig.leftPanel?.tableName, currentUserId]); + // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ useEffect(() => { if (!isDesignMode && componentConfig.autoLoad !== false) { @@ -794,6 +952,14 @@ export const SplitPanelLayoutComponent: React.FC // eslint-disable-next-line react-hooks/exhaustive-deps }, [isDesignMode, componentConfig.autoLoad]); + // ๐Ÿ”„ ํ•„ํ„ฐ ๋ณ€๊ฒฝ ์‹œ ๋ฐ์ดํ„ฐ ๋‹ค์‹œ ๋กœ๋“œ + useEffect(() => { + if (!isDesignMode && componentConfig.autoLoad !== false) { + loadLeftData(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [leftFilters]); + // ๋ฆฌ์‚ฌ์ด์ € ๋“œ๋ž˜๊ทธ ํ•ธ๋“ค๋Ÿฌ const handleMouseDown = (e: React.MouseEvent) => { if (!resizable) return; @@ -933,6 +1099,7 @@ export const SplitPanelLayoutComponent: React.FC
) : ( (() => { + // ๐Ÿ”ง ๋กœ์ปฌ ๊ฒ€์ƒ‰ ํ•„ํ„ฐ ์ ์šฉ const filteredData = leftSearchQuery ? leftData.filter((item) => { const searchLower = leftSearchQuery.toLowerCase(); @@ -943,12 +1110,17 @@ export const SplitPanelLayoutComponent: React.FC }) : leftData; - const displayColumns = componentConfig.leftPanel?.columns || []; - const columnsToShow = displayColumns.length > 0 - ? displayColumns.map(col => ({ - ...col, - label: leftColumnLabels[col.name] || col.label || col.name - })) + // ๐Ÿ”ง ๊ฐ€์‹œ์„ฑ ์ฒ˜๋ฆฌ๋œ ์ปฌ๋Ÿผ ์‚ฌ์šฉ + const columnsToShow = visibleLeftColumns.length > 0 + ? visibleLeftColumns.map((col: any) => { + const colName = typeof col === 'string' ? col : (col.name || col.columnName); + return { + name: colName, + label: leftColumnLabels[colName] || (typeof col === 'object' ? col.label : null) || colName, + width: typeof col === 'object' ? col.width : 150, + align: (typeof col === 'object' ? col.align : "left") as "left" | "center" | "right" + }; + }) : Object.keys(filteredData[0] || {}).filter(key => key !== 'children' && key !== 'level').slice(0, 5).map(key => ({ name: key, label: leftColumnLabels[key] || key, @@ -956,6 +1128,66 @@ export const SplitPanelLayoutComponent: React.FC align: "left" as const })); + // ๐Ÿ”ง ๊ทธ๋ฃนํ™”๋œ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง + if (groupedLeftData.length > 0) { + return ( +
+ {groupedLeftData.map((group, groupIdx) => ( +
+
+ {group.groupKey} ({group.count}๊ฐœ) +
+ + + + {columnsToShow.map((col, idx) => ( + + ))} + + + + {group.items.map((item, idx) => { + const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || 'id'; + const itemId = item[sourceColumn] || item.id || item.ID || idx; + const isSelected = selectedLeftItem && (selectedLeftItem[sourceColumn] === itemId || selectedLeftItem === item); + + return ( + handleLeftItemSelect(item)} + className={`hover:bg-accent cursor-pointer transition-colors ${ + isSelected ? "bg-primary/10" : "" + }`} + > + {columnsToShow.map((col, colIdx) => ( + + ))} + + ); + })} + +
+ {col.label} +
+ {item[col.name] !== null && item[col.name] !== undefined + ? String(item[col.name]) + : "-"} +
+
+ ))} +
+ ); + } + + // ๐Ÿ”ง ์ผ๋ฐ˜ ํ…Œ์ด๋ธ” ๋ Œ๋”๋ง (๊ทธ๋ฃนํ™” ์—†์Œ) return (
diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 6344f3e8..f5fecd34 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -274,6 +274,11 @@ export const TableListComponent: React.FC = ({ setCurrentPage(1); // ํ•„ํ„ฐ ๋ณ€๊ฒฝ ์‹œ ์ฒซ ํŽ˜์ด์ง€๋กœ }, [filters]); + // grouping์ด ๋ณ€๊ฒฝ๋˜๋ฉด groupByColumns ์—…๋ฐ์ดํŠธ + useEffect(() => { + setGroupByColumns(grouping); + }, [grouping]); + // ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ localStorage์—์„œ ์ €์žฅ๋œ ์„ค์ • ๋ถˆ๋Ÿฌ์˜ค๊ธฐ useEffect(() => { if (tableConfig.selectedTable && currentUserId) { @@ -1652,9 +1657,20 @@ export const TableListComponent: React.FC = ({ data.forEach((item) => { // ๊ทธ๋ฃน ํ‚ค ์ƒ์„ฑ: "ํ†ตํ™”:KRW > ๋‹จ์œ„:EA" const keyParts = groupByColumns.map((col) => { - const value = item[col]; + // ์นดํ…Œ๊ณ ๋ฆฌ/์—”ํ‹ฐํ‹ฐ ํƒ€์ž…์ธ ๊ฒฝ์šฐ _name ํ•„๋“œ ์‚ฌ์šฉ + const inputType = columnMeta?.[col]?.inputType; + let displayValue = item[col]; + + if (inputType === 'category' || inputType === 'entity' || inputType === 'code') { + // _name ํ•„๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์šฉ (์˜ˆ: division_name, writer_name) + const nameField = `${col}_name`; + if (item[nameField] !== undefined && item[nameField] !== null) { + displayValue = item[nameField]; + } + } + const label = columnLabels[col] || col; - return `${label}:${value !== null && value !== undefined ? value : "-"}`; + return `${label}:${displayValue !== null && displayValue !== undefined ? displayValue : "-"}`; }); const groupKey = keyParts.join(" > "); @@ -1677,7 +1693,7 @@ export const TableListComponent: React.FC = ({ count: items.length, }; }); - }, [data, groupByColumns, columnLabels]); + }, [data, groupByColumns, columnLabels, columnMeta]); // ์ €์žฅ๋œ ๊ทธ๋ฃน ์„ค์ • ๋ถˆ๋Ÿฌ์˜ค๊ธฐ useEffect(() => { @@ -1860,124 +1876,7 @@ export const TableListComponent: React.FC = ({ if (tableConfig.stickyHeader && !isDesignMode) { return (
- {tableConfig.filter?.enabled && ( -
-
-
- -
-
- {/* ์ „์ฒด ๊ฐœ์ˆ˜ */} -
- ์ „์ฒด {totalItems.toLocaleString()}๊ฐœ -
- - - - - - - - -
-
-

๊ทธ๋ฃน ์„ค์ •

-

- ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋ฃนํ™”ํ•  ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜์„ธ์š” -

-
- - {/* ์ปฌ๋Ÿผ ๋ชฉ๋ก */} -
- {visibleColumns - .filter((col) => col.columnName !== "__checkbox__") - .map((col) => ( -
- toggleGroupColumn(col.columnName)} - /> - -
- ))} -
- - {/* ์„ ํƒ๋œ ๊ทธ๋ฃน ์•ˆ๋‚ด */} - {groupByColumns.length > 0 && ( -
- - {groupByColumns.map((col) => columnLabels[col] || col).join(" โ†’ ")} - -
- )} - - {/* ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ */} - {groupByColumns.length > 0 && ( - - )} -
-
-
-
-
-
- )} + {/* ํ•„ํ„ฐ ํ—ค๋”๋Š” TableSearchWidget์œผ๋กœ ์ด๋™ */} {/* ๊ทธ๋ฃน ํ‘œ์‹œ ๋ฐฐ์ง€ */} {groupByColumns.length > 0 && ( @@ -2040,125 +1939,7 @@ export const TableListComponent: React.FC = ({ return ( <>
- {/* ํ•„ํ„ฐ */} - {tableConfig.filter?.enabled && ( -
-
-
- -
-
- {/* ์ „์ฒด ๊ฐœ์ˆ˜ */} -
- ์ „์ฒด {totalItems.toLocaleString()}๊ฐœ -
- - - - - - - - -
-
-

๊ทธ๋ฃน ์„ค์ •

-

- ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋ฃนํ™”ํ•  ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜์„ธ์š” -

-
- - {/* ์ปฌ๋Ÿผ ๋ชฉ๋ก */} -
- {visibleColumns - .filter((col) => col.columnName !== "__checkbox__") - .map((col) => ( -
- toggleGroupColumn(col.columnName)} - /> - -
- ))} -
- - {/* ์„ ํƒ๋œ ๊ทธ๋ฃน ์•ˆ๋‚ด */} - {groupByColumns.length > 0 && ( -
- - {groupByColumns.map((col) => columnLabels[col] || col).join(" โ†’ ")} - -
- )} - - {/* ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ */} - {groupByColumns.length > 0 && ( - - )} -
-
-
-
-
-
- )} + {/* ํ•„ํ„ฐ ํ—ค๋”๋Š” TableSearchWidget์œผ๋กœ ์ด๋™ */} {/* ๊ทธ๋ฃน ํ‘œ์‹œ ๋ฐฐ์ง€ */} {groupByColumns.length > 0 && ( diff --git a/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx b/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx index 2b37e2d6..34b3044c 100644 --- a/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx +++ b/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx @@ -76,7 +76,15 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table useEffect(() => { const tables = Array.from(registeredTables.values()); + console.log("๐Ÿ” [TableSearchWidget] ํ…Œ์ด๋ธ” ๊ฐ์ง€:", { + tablesCount: tables.length, + tableIds: tables.map(t => t.tableId), + selectedTableId, + autoSelectFirstTable, + }); + if (autoSelectFirstTable && tables.length > 0 && !selectedTableId) { + console.log("โœ… [TableSearchWidget] ์ฒซ ๋ฒˆ์งธ ํ…Œ์ด๋ธ” ์ž๋™ ์„ ํƒ:", tables[0].tableId); setSelectedTableId(tables[0].tableId); } }, [registeredTables, selectedTableId, autoSelectFirstTable, setSelectedTableId]);