From 9e7253a29349ac59b18770fb6925ab648434058d Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 12 Jan 2026 10:32:41 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EB=B3=B4=EC=9D=B4=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/tableCategoryValueService.ts | 117 +++++++++--------- .../screen/InteractiveDataTable.tsx | 21 +++- frontend/contexts/ScreenContext.tsx | 5 + 3 files changed, 79 insertions(+), 64 deletions(-) diff --git a/backend-node/src/services/tableCategoryValueService.ts b/backend-node/src/services/tableCategoryValueService.ts index edeb55b2..9cbbc521 100644 --- a/backend-node/src/services/tableCategoryValueService.ts +++ b/backend-node/src/services/tableCategoryValueService.ts @@ -187,71 +187,68 @@ class TableCategoryValueService { logger.info("형제 메뉴 OBJID 목록", { menuObjid, siblingObjids }); } - // 2. 카테고리 값 조회 (형제 메뉴 포함) + // 2. 카테고리 값 조회 (메뉴 스코프 또는 형제 메뉴 포함) let query: string; let params: any[]; + const baseSelect = ` + SELECT + value_id AS "valueId", + table_name AS "tableName", + column_name AS "columnName", + value_code AS "valueCode", + value_label AS "valueLabel", + value_order AS "valueOrder", + parent_value_id AS "parentValueId", + depth, + description, + color, + icon, + is_active AS "isActive", + is_default AS "isDefault", + company_code AS "companyCode", + menu_objid AS "menuObjid", + created_at AS "createdAt", + updated_at AS "updatedAt", + created_by AS "createdBy", + updated_by AS "updatedBy" + FROM table_column_category_values + WHERE table_name = $1 + AND column_name = $2 + `; + if (companyCode === "*") { - // 최고 관리자: 모든 카테고리 값 조회 - // 메뉴 스코프 제거: 같은 테이블.컬럼 조합은 모든 메뉴에서 공유 - query = ` - SELECT - value_id AS "valueId", - table_name AS "tableName", - column_name AS "columnName", - value_code AS "valueCode", - value_label AS "valueLabel", - value_order AS "valueOrder", - parent_value_id AS "parentValueId", - depth, - description, - color, - icon, - is_active AS "isActive", - is_default AS "isDefault", - company_code AS "companyCode", - menu_objid AS "menuObjid", - created_at AS "createdAt", - updated_at AS "updatedAt", - created_by AS "createdBy", - updated_by AS "updatedBy" - FROM table_column_category_values - WHERE table_name = $1 - AND column_name = $2 - `; - params = [tableName, columnName]; - logger.info("최고 관리자 카테고리 값 조회"); + // 최고 관리자: menuObjid가 있으면 해당 메뉴(및 형제 메뉴)의 값만 조회 + if (menuObjid && siblingObjids.length > 0) { + query = baseSelect + ` AND menu_objid = ANY($3::numeric[])`; + params = [tableName, columnName, siblingObjids]; + logger.info("최고 관리자 메뉴 스코프 카테고리 값 조회", { menuObjid, siblingObjids }); + } else if (menuObjid) { + query = baseSelect + ` AND menu_objid = $3`; + params = [tableName, columnName, menuObjid]; + logger.info("최고 관리자 단일 메뉴 카테고리 값 조회", { menuObjid }); + } else { + // menuObjid 없으면 모든 값 조회 (중복 가능) + query = baseSelect; + params = [tableName, columnName]; + logger.info("최고 관리자 전체 카테고리 값 조회 (menuObjid 없음)"); + } } else { - // 일반 회사: 자신의 카테고리 값만 조회 - // 메뉴 스코프 제거: 같은 테이블.컬럼 조합은 모든 메뉴에서 공유 - query = ` - SELECT - value_id AS "valueId", - table_name AS "tableName", - column_name AS "columnName", - value_code AS "valueCode", - value_label AS "valueLabel", - value_order AS "valueOrder", - parent_value_id AS "parentValueId", - depth, - description, - color, - icon, - is_active AS "isActive", - is_default AS "isDefault", - company_code AS "companyCode", - menu_objid AS "menuObjid", - created_at AS "createdAt", - updated_at AS "updatedAt", - created_by AS "createdBy", - updated_by AS "updatedBy" - FROM table_column_category_values - WHERE table_name = $1 - AND column_name = $2 - AND company_code = $3 - `; - params = [tableName, columnName, companyCode]; - logger.info("회사별 카테고리 값 조회", { companyCode }); + // 일반 회사: 자신의 회사 + menuObjid로 필터링 + if (menuObjid && siblingObjids.length > 0) { + query = baseSelect + ` AND company_code = $3 AND menu_objid = ANY($4::numeric[])`; + params = [tableName, columnName, companyCode, siblingObjids]; + logger.info("회사별 메뉴 스코프 카테고리 값 조회", { companyCode, menuObjid, siblingObjids }); + } else if (menuObjid) { + query = baseSelect + ` AND company_code = $3 AND menu_objid = $4`; + params = [tableName, columnName, companyCode, menuObjid]; + logger.info("회사별 단일 메뉴 카테고리 값 조회", { companyCode, menuObjid }); + } else { + // menuObjid 없으면 회사 전체 조회 (중복 가능하지만 회사별로 제한) + query = baseSelect + ` AND company_code = $3`; + params = [tableName, columnName, companyCode]; + logger.info("회사별 카테고리 값 조회 (menuObjid 없음)", { companyCode }); + } } if (!includeInactive) { diff --git a/frontend/components/screen/InteractiveDataTable.tsx b/frontend/components/screen/InteractiveDataTable.tsx index 5e4bda2e..7fcc61ed 100644 --- a/frontend/components/screen/InteractiveDataTable.tsx +++ b/frontend/components/screen/InteractiveDataTable.tsx @@ -1,6 +1,7 @@ "use client"; -import React, { useState, useEffect, useCallback, useRef } from "react"; +import React, { useState, useEffect, useCallback, useRef, useMemo } from "react"; +import { useSearchParams } from "next/navigation"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; @@ -188,6 +189,16 @@ export const InteractiveDataTable: React.FC = ({ const screenContext = useScreenContextOptional(); // 화면 컨텍스트 (좌측/우측 위치 확인용) const splitPanelPosition = screenContext?.splitPanelPosition; // 분할 패널 내 위치 + // URL에서 menuObjid 가져오기 (카테고리 값 조회 시 필요) + const searchParams = useSearchParams(); + const menuObjid = useMemo(() => { + // 1. ScreenContext에서 가져오기 + if (screenContext?.menuObjid) return screenContext.menuObjid; + // 2. URL 쿼리에서 가져오기 + const urlMenuObjid = searchParams.get("menuObjid"); + return urlMenuObjid ? parseInt(urlMenuObjid) : undefined; + }, [screenContext?.menuObjid, searchParams]); + const [data, setData] = useState[]>([]); const [loading, setLoading] = useState(false); const [searchValues, setSearchValues] = useState>({}); @@ -365,8 +376,10 @@ export const InteractiveDataTable: React.FC = ({ for (const col of categoryColumns) { try { + // menuObjid가 있으면 쿼리 파라미터로 전달 (메뉴별 카테고리 색상 적용) + const queryParams = menuObjid ? `?menuObjid=${menuObjid}` : ""; const response = await apiClient.get( - `/table-categories/${component.tableName}/${col.columnName}/values` + `/table-categories/${component.tableName}/${col.columnName}/values${queryParams}` ); if (response.data.success && response.data.data) { @@ -379,7 +392,7 @@ export const InteractiveDataTable: React.FC = ({ }; }); mappings[col.columnName] = mapping; - console.log(`✅ 카테고리 매핑 로드 성공 [${col.columnName}]:`, mapping); + console.log(`✅ 카테고리 매핑 로드 성공 [${col.columnName}]:`, mapping, { menuObjid }); } } catch (error) { console.error(`❌ 카테고리 값 로드 실패 [${col.columnName}]:`, error); @@ -394,7 +407,7 @@ export const InteractiveDataTable: React.FC = ({ }; loadCategoryMappings(); - }, [component.tableName, component.columns, getColumnWebType]); + }, [component.tableName, component.columns, getColumnWebType, menuObjid]); // 파일 상태 확인 함수 const checkFileStatus = useCallback( diff --git a/frontend/contexts/ScreenContext.tsx b/frontend/contexts/ScreenContext.tsx index 0bb6a32c..5e9bb2f1 100644 --- a/frontend/contexts/ScreenContext.tsx +++ b/frontend/contexts/ScreenContext.tsx @@ -13,6 +13,7 @@ import type { SplitPanelPosition } from "@/contexts/SplitPanelContext"; interface ScreenContextValue { screenId?: number; tableName?: string; + menuObjid?: number; // 메뉴 OBJID (카테고리 값 조회 시 필요) splitPanelPosition?: SplitPanelPosition; // 🆕 분할 패널 위치 (left/right) // 🆕 폼 데이터 (RepeaterFieldGroup 등 컴포넌트 데이터 저장) @@ -39,6 +40,7 @@ const ScreenContext = createContext(null); interface ScreenContextProviderProps { screenId?: number; tableName?: string; + menuObjid?: number; // 메뉴 OBJID splitPanelPosition?: SplitPanelPosition; // 🆕 분할 패널 위치 children: React.ReactNode; } @@ -49,6 +51,7 @@ interface ScreenContextProviderProps { export function ScreenContextProvider({ screenId, tableName, + menuObjid, splitPanelPosition, children, }: ScreenContextProviderProps) { @@ -112,6 +115,7 @@ export function ScreenContextProvider({ () => ({ screenId, tableName, + menuObjid, splitPanelPosition, formData, updateFormData, @@ -127,6 +131,7 @@ export function ScreenContextProvider({ [ screenId, tableName, + menuObjid, splitPanelPosition, formData, updateFormData,