diff --git a/backend-node/src/controllers/commonCodeController.ts b/backend-node/src/controllers/commonCodeController.ts index 616e0c6c..b0db2059 100644 --- a/backend-node/src/controllers/commonCodeController.ts +++ b/backend-node/src/controllers/commonCodeController.ts @@ -20,8 +20,9 @@ export class CommonCodeController { */ async getCategories(req: AuthenticatedRequest, res: Response) { try { - const { search, isActive, page = "1", size = "20" } = req.query; + const { search, isActive, page = "1", size = "20", menuObjid } = req.query; const userCompanyCode = req.user?.companyCode; + const menuObjidNum = menuObjid ? Number(menuObjid) : undefined; const categories = await this.commonCodeService.getCategories( { @@ -35,7 +36,8 @@ export class CommonCodeController { page: parseInt(page as string), size: parseInt(size as string), }, - userCompanyCode + userCompanyCode, + menuObjidNum ); return res.json({ @@ -61,8 +63,9 @@ export class CommonCodeController { async getCodes(req: AuthenticatedRequest, res: Response) { try { const { categoryCode } = req.params; - const { search, isActive, page, size } = req.query; + const { search, isActive, page, size, menuObjid } = req.query; const userCompanyCode = req.user?.companyCode; + const menuObjidNum = menuObjid ? Number(menuObjid) : undefined; const result = await this.commonCodeService.getCodes( categoryCode, @@ -77,7 +80,8 @@ export class CommonCodeController { page: page ? parseInt(page as string) : undefined, size: size ? parseInt(size as string) : undefined, }, - userCompanyCode + userCompanyCode, + menuObjidNum ); // 프론트엔드가 기대하는 형식으로 데이터 변환 @@ -131,6 +135,7 @@ export class CommonCodeController { const categoryData: CreateCategoryData = req.body; const userId = req.user?.userId || "SYSTEM"; const companyCode = req.user?.companyCode || "*"; + const menuObjid = req.body.menuObjid; // 입력값 검증 if (!categoryData.categoryCode || !categoryData.categoryName) { @@ -140,10 +145,18 @@ export class CommonCodeController { }); } + if (!menuObjid) { + return res.status(400).json({ + success: false, + message: "메뉴 OBJID는 필수입니다.", + }); + } + const category = await this.commonCodeService.createCategory( categoryData, userId, - companyCode + companyCode, + Number(menuObjid) ); return res.status(201).json({ @@ -263,6 +276,7 @@ export class CommonCodeController { const codeData: CreateCodeData = req.body; const userId = req.user?.userId || "SYSTEM"; const companyCode = req.user?.companyCode || "*"; + const menuObjid = req.body.menuObjid; // 입력값 검증 if (!codeData.codeValue || !codeData.codeName) { @@ -272,11 +286,19 @@ export class CommonCodeController { }); } + if (!menuObjid) { + return res.status(400).json({ + success: false, + message: "메뉴 OBJID는 필수입니다.", + }); + } + const code = await this.commonCodeService.createCode( categoryCode, codeData, userId, - companyCode + companyCode, + Number(menuObjid) ); return res.status(201).json({ diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts index 8c02a60d..7bf40b7d 100644 --- a/backend-node/src/services/commonCodeService.ts +++ b/backend-node/src/services/commonCodeService.ts @@ -66,7 +66,7 @@ export class CommonCodeService { /** * 카테고리 목록 조회 */ - async getCategories(params: GetCategoriesParams, userCompanyCode?: string) { + async getCategories(params: GetCategoriesParams, userCompanyCode?: string, menuObjid?: number) { try { const { search, isActive, page = 1, size = 20 } = params; @@ -74,6 +74,16 @@ export class CommonCodeService { const values: any[] = []; let paramIndex = 1; + // 메뉴별 필터링 (형제 메뉴 포함) + if (menuObjid) { + const { getSiblingMenuObjids } = await import('./menuService'); + const siblingMenuObjids = await getSiblingMenuObjids(menuObjid); + whereConditions.push(`menu_objid = ANY($${paramIndex})`); + values.push(siblingMenuObjids); + paramIndex++; + logger.info(`메뉴별 코드 카테고리 필터링: ${menuObjid}, 형제 메뉴: ${siblingMenuObjids.join(', ')}`); + } + // 회사별 필터링 (최고 관리자가 아닌 경우) if (userCompanyCode && userCompanyCode !== "*") { whereConditions.push(`company_code = $${paramIndex}`); @@ -142,7 +152,8 @@ export class CommonCodeService { async getCodes( categoryCode: string, params: GetCodesParams, - userCompanyCode?: string + userCompanyCode?: string, + menuObjid?: number ) { try { const { search, isActive, page = 1, size = 20 } = params; @@ -151,6 +162,16 @@ export class CommonCodeService { const values: any[] = [categoryCode]; let paramIndex = 2; + // 메뉴별 필터링 (형제 메뉴 포함) + if (menuObjid) { + const { getSiblingMenuObjids } = await import('./menuService'); + const siblingMenuObjids = await getSiblingMenuObjids(menuObjid); + whereConditions.push(`menu_objid = ANY($${paramIndex})`); + values.push(siblingMenuObjids); + paramIndex++; + logger.info(`메뉴별 코드 필터링: ${menuObjid}, 형제 메뉴: ${siblingMenuObjids.join(', ')}`); + } + // 회사별 필터링 (최고 관리자가 아닌 경우) if (userCompanyCode && userCompanyCode !== "*") { whereConditions.push(`company_code = $${paramIndex}`); @@ -212,14 +233,15 @@ export class CommonCodeService { async createCategory( data: CreateCategoryData, createdBy: string, - companyCode: string + companyCode: string, + menuObjid: number ) { try { const category = await queryOne( `INSERT INTO code_category (category_code, category_name, category_name_eng, description, sort_order, - is_active, company_code, created_by, updated_by, created_date, updated_date) - VALUES ($1, $2, $3, $4, $5, 'Y', $6, $7, $8, NOW(), NOW()) + is_active, menu_objid, company_code, created_by, updated_by, created_date, updated_date) + VALUES ($1, $2, $3, $4, $5, 'Y', $6, $7, $8, $9, NOW(), NOW()) RETURNING *`, [ data.categoryCode, @@ -227,6 +249,7 @@ export class CommonCodeService { data.categoryNameEng || null, data.description || null, data.sortOrder || 0, + menuObjid, companyCode, createdBy, createdBy, @@ -234,7 +257,7 @@ export class CommonCodeService { ); logger.info( - `카테고리 생성 완료: ${data.categoryCode} (회사: ${companyCode})` + `카테고리 생성 완료: ${data.categoryCode} (메뉴: ${menuObjid}, 회사: ${companyCode})` ); return category; } catch (error) { @@ -352,14 +375,15 @@ export class CommonCodeService { categoryCode: string, data: CreateCodeData, createdBy: string, - companyCode: string + companyCode: string, + menuObjid: number ) { try { const code = await queryOne( `INSERT INTO code_info (code_category, code_value, code_name, code_name_eng, description, sort_order, - is_active, company_code, created_by, updated_by, created_date, updated_date) - VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, $9, NOW(), NOW()) + is_active, menu_objid, company_code, created_by, updated_by, created_date, updated_date) + VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, $9, $10, NOW(), NOW()) RETURNING *`, [ categoryCode, @@ -368,6 +392,7 @@ export class CommonCodeService { data.codeNameEng || null, data.description || null, data.sortOrder || 0, + menuObjid, companyCode, createdBy, createdBy, @@ -375,7 +400,7 @@ export class CommonCodeService { ); logger.info( - `코드 생성 완료: ${categoryCode}.${data.codeValue} (회사: ${companyCode})` + `코드 생성 완료: ${categoryCode}.${data.codeValue} (메뉴: ${menuObjid}, 회사: ${companyCode})` ); return code; } catch (error) { diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index 6fc7bab8..1ca88d51 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -403,6 +403,7 @@ export default function ScreenViewPage() { isSelected={false} isDesignMode={false} onClick={() => {}} + menuObjid={menuObjid} screenId={screenId} tableName={screen?.tableName} userId={user?.userId} @@ -468,6 +469,7 @@ export default function ScreenViewPage() { isSelected={false} isDesignMode={false} onClick={() => {}} + menuObjid={menuObjid} screenId={screenId} tableName={screen?.tableName} userId={user?.userId} diff --git a/frontend/components/screen/InteractiveScreenViewer.tsx b/frontend/components/screen/InteractiveScreenViewer.tsx index 472049ff..9eab7004 100644 --- a/frontend/components/screen/InteractiveScreenViewer.tsx +++ b/frontend/components/screen/InteractiveScreenViewer.tsx @@ -57,6 +57,7 @@ interface InteractiveScreenViewerProps { id: number; tableName?: string; }; + menuObjid?: number; // 🆕 메뉴 OBJID (코드 스코프용) // 새로운 검증 관련 옵션들 enableEnhancedValidation?: boolean; tableColumns?: ColumnInfo[]; @@ -76,6 +77,7 @@ export const InteractiveScreenViewer: React.FC = ( onFormDataChange, hideLabel = false, screenInfo, + menuObjid, // 🆕 메뉴 OBJID enableEnhancedValidation = false, tableColumns = [], showValidationPanel = false, @@ -1117,6 +1119,7 @@ export const InteractiveScreenViewer: React.FC = ( required: required, placeholder: config?.placeholder || "코드를 선택하세요...", className: "w-full h-full", + menuObjid: menuObjid, // 🆕 메뉴 OBJID 전달 }} config={{ ...config, diff --git a/frontend/components/screen/RealtimePreviewDynamic.tsx b/frontend/components/screen/RealtimePreviewDynamic.tsx index 1f220586..9ae2db82 100644 --- a/frontend/components/screen/RealtimePreviewDynamic.tsx +++ b/frontend/components/screen/RealtimePreviewDynamic.tsx @@ -41,7 +41,7 @@ interface RealtimePreviewProps { userId?: string; // 🆕 현재 사용자 ID userName?: string; // 🆕 현재 사용자 이름 companyCode?: string; // 🆕 현재 사용자의 회사 코드 - menuObjid?: number; // 🆕 현재 메뉴 OBJID (메뉴 스코프) + menuObjid?: number; // 🆕 메뉴 OBJID (코드/카테고리 스코프용) selectedRowsData?: any[]; onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void; flowSelectedData?: any[]; diff --git a/frontend/hooks/queries/useCodes.ts b/frontend/hooks/queries/useCodes.ts index 14c6ee5d..0b95f907 100644 --- a/frontend/hooks/queries/useCodes.ts +++ b/frontend/hooks/queries/useCodes.ts @@ -51,14 +51,19 @@ export function useTableCodeCategory(tableName?: string, columnName?: string) { } // 코드 옵션 조회 (select용) -export function useCodeOptions(codeCategory?: string, enabled: boolean = true) { +export function useCodeOptions(codeCategory?: string, enabled: boolean = true, menuObjid?: number) { const query = useQuery({ - queryKey: queryKeys.codes.options(codeCategory || ""), + queryKey: menuObjid + ? [...queryKeys.codes.options(codeCategory || ""), 'menu', menuObjid] + : queryKeys.codes.options(codeCategory || ""), queryFn: async () => { if (!codeCategory || codeCategory === "none") return []; - console.log(`🔍 [React Query] 코드 옵션 조회: ${codeCategory}`); - const response = await commonCodeApi.codes.getList(codeCategory, { isActive: true }); + console.log(`🔍 [React Query] 코드 옵션 조회: ${codeCategory} (menuObjid: ${menuObjid})`); + const response = await commonCodeApi.codes.getList(codeCategory, { + isActive: true, + menuObjid + }); if (response.success && response.data) { const options = response.data.map((code: any) => { @@ -73,7 +78,7 @@ export function useCodeOptions(codeCategory?: string, enabled: boolean = true) { }; }); - console.log(`✅ [React Query] 코드 옵션 결과: ${codeCategory} (${options.length}개)`); + console.log(`✅ [React Query] 코드 옵션 결과: ${codeCategory} (${options.length}개, menuObjid: ${menuObjid})`); return options; } diff --git a/frontend/lib/api/commonCode.ts b/frontend/lib/api/commonCode.ts index 2f465880..c51edb11 100644 --- a/frontend/lib/api/commonCode.ts +++ b/frontend/lib/api/commonCode.ts @@ -66,13 +66,14 @@ export const commonCodeApi = { /** * 카테고리별 코드 목록 조회 */ - async getList(categoryCode: string, params?: GetCodesQuery): Promise> { + async getList(categoryCode: string, params?: GetCodesQuery & { menuObjid?: number }): Promise> { const searchParams = new URLSearchParams(); if (params?.search) searchParams.append("search", params.search); if (params?.isActive !== undefined) searchParams.append("isActive", params.isActive.toString()); if (params?.page !== undefined) searchParams.append("page", params.page.toString()); if (params?.size !== undefined) searchParams.append("size", params.size.toString()); + if (params?.menuObjid !== undefined) searchParams.append("menuObjid", params.menuObjid.toString()); const queryString = searchParams.toString(); const url = `/common-codes/categories/${categoryCode}/codes${queryString ? `?${queryString}` : ""}`; diff --git a/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx b/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx index c105e5ec..14580ce8 100644 --- a/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx +++ b/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx @@ -23,6 +23,7 @@ export interface SelectBasicComponentProps { onDragStart?: () => void; onDragEnd?: () => void; value?: any; // 외부에서 전달받는 값 + menuObjid?: number; // 🆕 메뉴 OBJID (코드 스코프용) [key: string]: any; } @@ -46,6 +47,7 @@ const SelectBasicComponent: React.FC = ({ onDragStart, onDragEnd, value: externalValue, // 명시적으로 value prop 받기 + menuObjid, // 🆕 메뉴 OBJID ...props }) => { // 🚨 최우선 디버깅: 컴포넌트가 실행되는지 확인 @@ -132,7 +134,7 @@ const SelectBasicComponent: React.FC = ({ options: codeOptions, isLoading: isLoadingCodes, isFetching, - } = useCodeOptions(codeCategory, isCodeCategoryValid); + } = useCodeOptions(codeCategory, isCodeCategoryValid, menuObjid); // React Query 상태 디버깅 useEffect(() => {