diff --git a/backend-node/src/controllers/tableCategoryValueController.ts b/backend-node/src/controllers/tableCategoryValueController.ts index 60a0af08..49fe6e72 100644 --- a/backend-node/src/controllers/tableCategoryValueController.ts +++ b/backend-node/src/controllers/tableCategoryValueController.ts @@ -62,24 +62,31 @@ export const getAllCategoryColumns = async (req: AuthenticatedRequest, res: Resp */ export const getCategoryValues = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = req.user!.companyCode; + const userCompanyCode = req.user!.companyCode; const { tableName, columnName } = req.params; const includeInactive = req.query.includeInactive === "true"; const menuObjid = req.query.menuObjid ? Number(req.query.menuObjid) : undefined; + const filterCompanyCode = req.query.filterCompanyCode as string | undefined; + + // 최고관리자가 특정 회사 기준 필터링을 요청한 경우 해당 회사 코드 사용 + const effectiveCompanyCode = (userCompanyCode === "*" && filterCompanyCode) + ? filterCompanyCode + : userCompanyCode; logger.info("카테고리 값 조회 요청", { tableName, columnName, menuObjid, - companyCode, + companyCode: effectiveCompanyCode, + filterCompanyCode, }); const values = await tableCategoryValueService.getCategoryValues( tableName, columnName, - companyCode, + effectiveCompanyCode, includeInactive, - menuObjid // ← menuObjid 전달 + menuObjid ); return res.json({ diff --git a/backend-node/src/services/tableCategoryValueService.ts b/backend-node/src/services/tableCategoryValueService.ts index 96efdfbb..a8b12605 100644 --- a/backend-node/src/services/tableCategoryValueService.ts +++ b/backend-node/src/services/tableCategoryValueService.ts @@ -217,12 +217,12 @@ class TableCategoryValueService { AND column_name = $2 `; - // category_values 테이블 사용 (menu_objid 없음) + // company_code 기반 필터링 if (companyCode === "*") { - // 최고 관리자: 모든 값 조회 - query = baseSelect; + // 최고 관리자: 공통(*) 카테고리만 조회 (모든 회사 카테고리 혼합 방지) + query = baseSelect + ` AND company_code = '*'`; params = [tableName, columnName]; - logger.info("최고 관리자 전체 카테고리 값 조회 (category_values)"); + logger.info("최고 관리자: 공통 카테고리만 조회 (category_values)"); } else { // 일반 회사: 자신의 회사 또는 공통(*) 카테고리 조회 query = baseSelect + ` AND (company_code = $3 OR company_code = '*')`; diff --git a/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx b/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx index 87bbb586..2accfe1e 100644 --- a/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx @@ -713,7 +713,7 @@ export const TableListComponent: React.FC = ({ const [categoryMappings, setCategoryMappings] = useState< Record> >({}); - const [categoryMappingsKey, setCategoryMappingsKey] = useState(0); // 강제 리렌더링용 + const [categoryMappingsKey, setCategoryMappingsKey] = useState(0); const [searchValues, setSearchValues] = useState>({}); const [selectedRows, setSelectedRows] = useState>(new Set()); const [columnWidths, setColumnWidths] = useState>({}); @@ -1047,9 +1047,14 @@ export const TableListComponent: React.FC = ({ const getColumnUniqueValues = async (columnName: string) => { const { apiClient } = await import("@/lib/api/client"); + // 최고관리자가 특정 회사 프리뷰 시 해당 회사 카테고리만 필터링 + const filterParam = companyCode && companyCode !== "*" + ? `?filterCompanyCode=${encodeURIComponent(companyCode)}` + : ""; + // 1단계: 카테고리 API 시도 (columnMeta 무관하게 항상 시도) try { - const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values`); + const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values${filterParam}`); if (response.data.success && response.data.data && response.data.data.length > 0) { return response.data.data.map((item: any) => ({ value: item.valueCode, @@ -1154,15 +1159,13 @@ export const TableListComponent: React.FC = ({ tableConfig.selectedTable, tableConfig.columns, columnLabels, - columnMeta, // columnMeta가 변경되면 재등록 (inputType 정보 필요) - categoryMappings, // 카테고리 매핑 변경 시 재등록 (필터 라벨 변환용) + columnMeta, + categoryMappings, columnWidths, tableLabel, - data, // 데이터 자체가 변경되면 재등록 (고유 값 조회용) - totalItems, // 전체 항목 수가 변경되면 재등록 + data, + totalItems, registerTable, - // unregisterTable은 의존성에서 제외 - 무한 루프 방지 - // unregisterTable 함수는 의존성이 없어 안정적임 ]); // 🎯 초기 로드 시 localStorage에서 정렬 상태 불러오기 (없으면 defaultSort 적용) @@ -1406,7 +1409,13 @@ export const TableListComponent: React.FC = ({ const mappings: Record> = {}; const apiClient = (await import("@/lib/api/client")).apiClient; + // 최고관리자가 특정 회사 프리뷰 시 해당 회사 카테고리만 필터링 + const filterCompanyParam = companyCode && companyCode !== "*" + ? `&filterCompanyCode=${encodeURIComponent(companyCode)}` + : ""; + // 트리 구조를 평탄화하는 헬퍼 함수 (메인 테이블 + 엔티티 조인 공통 사용) + // valueCode만 키로 사용 (valueId까지 넣으면 같은 라벨이 2번 나옴) const flattenTree = (items: any[], mapping: Record) => { items.forEach((item: any) => { if (item.valueCode) { @@ -1415,12 +1424,6 @@ export const TableListComponent: React.FC = ({ color: item.color, }; } - if (item.valueId !== undefined && item.valueId !== null) { - mapping[String(item.valueId)] = { - label: item.valueLabel, - color: item.color, - }; - } if (item.children && Array.isArray(item.children) && item.children.length > 0) { flattenTree(item.children, mapping); } @@ -1448,7 +1451,7 @@ export const TableListComponent: React.FC = ({ } // 비활성화된 카테고리도 라벨로 표시하기 위해 includeInactive=true - const response = await apiClient.get(`/table-categories/${targetTable}/${targetColumn}/values?includeInactive=true`); + const response = await apiClient.get(`/table-categories/${targetTable}/${targetColumn}/values?includeInactive=true${filterCompanyParam}`); if (response.data.success && response.data.data && Array.isArray(response.data.data)) { const mapping: Record = {}; @@ -1531,7 +1534,7 @@ export const TableListComponent: React.FC = ({ // inputType이 category인 경우 카테고리 매핑 로드 if (inputTypeInfo?.inputType === "category" && !mappings[col.columnName]) { try { - const response = await apiClient.get(`/table-categories/${joinedTable}/${col.actualColumn}/values?includeInactive=true`); + const response = await apiClient.get(`/table-categories/${joinedTable}/${col.actualColumn}/values?includeInactive=true${filterCompanyParam}`); if (response.data.success && response.data.data && Array.isArray(response.data.data)) { const mapping: Record = {}; @@ -1601,6 +1604,7 @@ export const TableListComponent: React.FC = ({ JSON.stringify(categoryColumns), JSON.stringify(tableConfig.columns), columnMeta, + companyCode, ]); // ========================================