From 6534d03ecd7a2d7667b71f095d52dcfe8b33652d Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 11 Nov 2025 16:28:17 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=99=94=EB=A9=B4=20=ED=8E=B8=EC=A7=91?= =?UTF-8?q?=EA=B8=B0=EC=97=90=EC=84=9C=20=EB=A9=94=EB=89=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=8A=A4=EC=BD=94?= =?UTF-8?q?=ED=94=84=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 백엔드: screenManagementService에 getMenuByScreen 함수 추가 - 백엔드: GET /api/screen-management/screens/:id/menu 엔드포인트 추가 - 프론트엔드: screenApi.getScreenMenu() 함수 추가 - ScreenDesigner: 화면 로드 시 menu_objid 자동 조회 - ScreenDesigner: menuObjid를 RealtimePreview와 UnifiedPropertiesPanel에 전달 - UnifiedPropertiesPanel: menuObjid를 DynamicComponentConfigPanel에 전달 이로써 화면 편집기에서 코드/카테고리/채번규칙이 해당 화면이 할당된 메뉴 기준으로 필터링됨 --- .../controllers/numberingRuleController.ts | 23 +++- .../controllers/screenManagementController.ts | 23 ++++ .../src/routes/screenManagementRoutes.ts | 2 + .../src/services/commonCodeService.ts | 39 ++++++- .../src/services/numberingRuleService.ts | 24 ++-- .../src/services/screenManagementService.ts | 33 ++++++ .../numbering-rule/NumberingRuleDesigner.tsx | 32 ++++-- .../screen/InteractiveScreenViewer.tsx | 9 +- frontend/components/screen/ScreenDesigner.tsx | 14 +++ .../screen/panels/UnifiedPropertiesPanel.tsx | 5 + frontend/hooks/queries/useCodes.ts | 25 ++++- frontend/lib/api/screen.ts | 6 + .../numbering-rule/NumberingRuleComponent.tsx | 10 +- .../select-basic/SelectBasicComponent.tsx | 106 ++---------------- 14 files changed, 226 insertions(+), 125 deletions(-) diff --git a/backend-node/src/controllers/numberingRuleController.ts b/backend-node/src/controllers/numberingRuleController.ts index 1b2e2197..d00db2c4 100644 --- a/backend-node/src/controllers/numberingRuleController.ts +++ b/backend-node/src/controllers/numberingRuleController.ts @@ -112,6 +112,17 @@ router.post("/", authenticateToken, async (req: AuthenticatedRequest, res: Respo const userId = req.user!.userId; const ruleConfig = req.body; + logger.info("🔍 [POST /numbering-rules] 채번 규칙 생성 요청:", { + companyCode, + userId, + ruleId: ruleConfig.ruleId, + ruleName: ruleConfig.ruleName, + scopeType: ruleConfig.scopeType, + menuObjid: ruleConfig.menuObjid, + tableName: ruleConfig.tableName, + partsCount: ruleConfig.parts?.length, + }); + try { if (!ruleConfig.ruleId || !ruleConfig.ruleName) { return res.status(400).json({ success: false, error: "규칙 ID와 규칙명은 필수입니다" }); @@ -122,12 +133,22 @@ router.post("/", authenticateToken, async (req: AuthenticatedRequest, res: Respo } const newRule = await numberingRuleService.createRule(ruleConfig, companyCode, userId); + + logger.info("✅ [POST /numbering-rules] 채번 규칙 생성 성공:", { + ruleId: newRule.ruleId, + menuObjid: newRule.menuObjid, + }); + return res.status(201).json({ success: true, data: newRule }); } catch (error: any) { if (error.code === "23505") { return res.status(409).json({ success: false, error: "이미 존재하는 규칙 ID입니다" }); } - logger.error("규칙 생성 실패", { error: error.message }); + logger.error("❌ [POST /numbering-rules] 규칙 생성 실패:", { + error: error.message, + stack: error.stack, + code: error.code, + }); return res.status(500).json({ success: false, error: error.message }); } }); diff --git a/backend-node/src/controllers/screenManagementController.ts b/backend-node/src/controllers/screenManagementController.ts index f7900b94..95277664 100644 --- a/backend-node/src/controllers/screenManagementController.ts +++ b/backend-node/src/controllers/screenManagementController.ts @@ -60,6 +60,29 @@ export const getScreen = async ( } }; +// 화면에 할당된 메뉴 조회 +export const getScreenMenu = async ( + req: AuthenticatedRequest, + res: Response +): Promise => { + try { + const { id } = req.params; + const { companyCode } = req.user as any; + + const menuInfo = await screenManagementService.getMenuByScreen( + parseInt(id), + companyCode + ); + + res.json({ success: true, data: menuInfo }); + } catch (error) { + console.error("화면 메뉴 조회 실패:", error); + res + .status(500) + .json({ success: false, message: "화면 메뉴 조회에 실패했습니다." }); + } +}; + // 화면 생성 export const createScreen = async ( req: AuthenticatedRequest, diff --git a/backend-node/src/routes/screenManagementRoutes.ts b/backend-node/src/routes/screenManagementRoutes.ts index 3fed9129..e307ccc5 100644 --- a/backend-node/src/routes/screenManagementRoutes.ts +++ b/backend-node/src/routes/screenManagementRoutes.ts @@ -3,6 +3,7 @@ import { authenticateToken } from "../middleware/authMiddleware"; import { getScreens, getScreen, + getScreenMenu, createScreen, updateScreen, updateScreenInfo, @@ -33,6 +34,7 @@ router.use(authenticateToken); // 화면 관리 router.get("/screens", getScreens); router.get("/screens/:id", getScreen); +router.get("/screens/:id/menu", getScreenMenu); // 화면에 할당된 메뉴 조회 router.post("/screens", createScreen); router.put("/screens/:id", updateScreen); router.put("/screens/:id/info", updateScreenInfo); // 화면 정보만 수정 diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts index 7bf40b7d..40c05861 100644 --- a/backend-node/src/services/commonCodeService.ts +++ b/backend-node/src/services/commonCodeService.ts @@ -158,6 +158,17 @@ export class CommonCodeService { try { const { search, isActive, page = 1, size = 20 } = params; + logger.info(`🔍 [getCodes] 코드 조회 시작:`, { + categoryCode, + menuObjid, + hasMenuObjid: !!menuObjid, + userCompanyCode, + search, + isActive, + page, + size, + }); + const whereConditions: string[] = ["code_category = $1"]; const values: any[] = [categoryCode]; let paramIndex = 2; @@ -169,7 +180,13 @@ export class CommonCodeService { whereConditions.push(`menu_objid = ANY($${paramIndex})`); values.push(siblingMenuObjids); paramIndex++; - logger.info(`메뉴별 코드 필터링: ${menuObjid}, 형제 메뉴: ${siblingMenuObjids.join(', ')}`); + logger.info(`📋 [getCodes] 메뉴별 코드 필터링:`, { + menuObjid, + siblingMenuObjids, + siblingCount: siblingMenuObjids.length, + }); + } else { + logger.warn(`⚠️ [getCodes] menuObjid 없음 - 전역 코드 조회`); } // 회사별 필터링 (최고 관리자가 아닌 경우) @@ -199,6 +216,13 @@ export class CommonCodeService { const offset = (page - 1) * size; + logger.info(`📝 [getCodes] 실행할 쿼리:`, { + whereClause, + values, + whereConditions, + paramIndex, + }); + // 코드 조회 const codes = await query( `SELECT * FROM code_info @@ -217,9 +241,20 @@ export class CommonCodeService { const total = parseInt(countResult?.count || "0"); logger.info( - `코드 조회 완료: ${categoryCode} - ${codes.length}개, 전체: ${total}개 (회사: ${userCompanyCode || "전체"})` + `✅ [getCodes] 코드 조회 완료: ${categoryCode} - ${codes.length}개, 전체: ${total}개 (회사: ${userCompanyCode || "전체"}, menuObjid: ${menuObjid || "없음"})` ); + logger.info(`📊 [getCodes] 조회된 코드 상세:`, { + categoryCode, + menuObjid, + codes: codes.map((c) => ({ + code_value: c.code_value, + code_name: c.code_name, + menu_objid: c.menu_objid, + company_code: c.company_code, + })), + }); + return { data: codes, total }; } catch (error) { logger.error(`코드 조회 중 오류 (${categoryCode}):`, error); diff --git a/backend-node/src/services/numberingRuleService.ts b/backend-node/src/services/numberingRuleService.ts index 2c89f188..4fb27e52 100644 --- a/backend-node/src/services/numberingRuleService.ts +++ b/backend-node/src/services/numberingRuleService.ts @@ -301,16 +301,18 @@ class NumberingRuleService { scope_type = 'global' OR scope_type = 'table' OR (scope_type = 'menu' AND menu_objid = ANY($1)) + OR (scope_type = 'table' AND menu_objid = ANY($1)) -- ⚠️ 임시: table 스코프도 menu_objid로 필터링 + OR (scope_type = 'table' AND menu_objid IS NULL) -- ⚠️ 임시: 기존 규칙(menu_objid NULL) 포함 ORDER BY - CASE scope_type - WHEN 'menu' THEN 1 - WHEN 'table' THEN 2 - WHEN 'global' THEN 3 + CASE + WHEN scope_type = 'menu' OR (scope_type = 'table' AND menu_objid = ANY($1)) THEN 1 + WHEN scope_type = 'table' THEN 2 + WHEN scope_type = 'global' THEN 3 END, created_at DESC `; params = [siblingObjids]; - logger.info("최고 관리자: 형제 메뉴 포함 채번 규칙 조회", { siblingObjids }); + logger.info("최고 관리자: 형제 메뉴 포함 채번 규칙 조회 (기존 규칙 포함)", { siblingObjids }); } else { // 일반 회사: 자신의 규칙만 조회 (형제 메뉴 포함) query = ` @@ -335,17 +337,19 @@ class NumberingRuleService { scope_type = 'global' OR scope_type = 'table' OR (scope_type = 'menu' AND menu_objid = ANY($2)) + OR (scope_type = 'table' AND menu_objid = ANY($2)) -- ⚠️ 임시: table 스코프도 menu_objid로 필터링 + OR (scope_type = 'table' AND menu_objid IS NULL) -- ⚠️ 임시: 기존 규칙(menu_objid NULL) 포함 ) ORDER BY - CASE scope_type - WHEN 'menu' THEN 1 - WHEN 'table' THEN 2 - WHEN 'global' THEN 3 + CASE + WHEN scope_type = 'menu' OR (scope_type = 'table' AND menu_objid = ANY($2)) THEN 1 + WHEN scope_type = 'table' THEN 2 + WHEN scope_type = 'global' THEN 3 END, created_at DESC `; params = [companyCode, siblingObjids]; - logger.info("회사별: 형제 메뉴 포함 채번 규칙 조회", { companyCode, siblingObjids }); + logger.info("회사별: 형제 메뉴 포함 채번 규칙 조회 (기존 규칙 포함)", { companyCode, siblingObjids }); } logger.info("🔍 채번 규칙 쿼리 실행", { diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index f3c3d133..9c125578 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -1547,6 +1547,39 @@ export class ScreenManagementService { return screens.map((screen) => this.mapToScreenDefinition(screen)); } + /** + * 화면에 할당된 메뉴 조회 (첫 번째 할당만 반환) + * 화면 편집기에서 menuObjid를 가져오기 위해 사용 + */ + async getMenuByScreen( + screenId: number, + companyCode: string + ): Promise<{ menuObjid: number; menuName?: string } | null> { + const result = await queryOne<{ + menu_objid: string; + menu_name_kor?: string; + }>( + `SELECT sma.menu_objid, mi.menu_name_kor + FROM screen_menu_assignments sma + LEFT JOIN menu_info mi ON sma.menu_objid = mi.objid + WHERE sma.screen_id = $1 + AND sma.company_code = $2 + AND sma.is_active = 'Y' + ORDER BY sma.created_at ASC + LIMIT 1`, + [screenId, companyCode] + ); + + if (!result) { + return null; + } + + return { + menuObjid: parseInt(result.menu_objid), + menuName: result.menu_name_kor, + }; + } + /** * 화면-메뉴 할당 해제 (✅ Raw Query 전환 완료) */ diff --git a/frontend/components/numbering-rule/NumberingRuleDesigner.tsx b/frontend/components/numbering-rule/NumberingRuleDesigner.tsx index 252f5403..0bd49982 100644 --- a/frontend/components/numbering-rule/NumberingRuleDesigner.tsx +++ b/frontend/components/numbering-rule/NumberingRuleDesigner.tsx @@ -12,7 +12,7 @@ import { NumberingRuleConfig, NumberingRulePart } from "@/types/numbering-rule"; import { NumberingRuleCard } from "./NumberingRuleCard"; import { NumberingRulePreview } from "./NumberingRulePreview"; import { - getNumberingRules, + getAvailableNumberingRules, createNumberingRule, updateNumberingRule, deleteNumberingRule, @@ -55,7 +55,20 @@ export const NumberingRuleDesigner: React.FC = ({ const loadRules = useCallback(async () => { setLoading(true); try { - const response = await getNumberingRules(menuObjid); + console.log("🔍 [NumberingRuleDesigner] 채번 규칙 목록 로드 시작:", { + menuObjid, + hasMenuObjid: !!menuObjid, + }); + + const response = await getAvailableNumberingRules(menuObjid); + + console.log("📦 [NumberingRuleDesigner] 채번 규칙 API 응답:", { + menuObjid, + success: response.success, + rulesCount: response.data?.length || 0, + rules: response.data, + }); + if (response.success && response.data) { setSavedRules(response.data); } else { @@ -135,17 +148,21 @@ export const NumberingRuleDesigner: React.FC = ({ try { const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId); - // 저장 전에 현재 화면의 테이블명 자동 설정 + // 저장 전에 현재 화면의 테이블명과 menuObjid 자동 설정 const ruleToSave = { ...currentRule, - scopeType: "table" as const, // 항상 table로 고정 + scopeType: "table" as const, // ⚠️ 임시: DB 제약 조건 때문에 table 유지 tableName: currentTableName || currentRule.tableName || "", // 현재 테이블명 자동 설정 + menuObjid: menuObjid || currentRule.menuObjid || null, // 🆕 메뉴 OBJID 설정 (필터링용) }; console.log("💾 채번 규칙 저장:", { currentTableName, + menuObjid, "currentRule.tableName": currentRule.tableName, + "currentRule.menuObjid": currentRule.menuObjid, "ruleToSave.tableName": ruleToSave.tableName, + "ruleToSave.menuObjid": ruleToSave.menuObjid, "ruleToSave.scopeType": ruleToSave.scopeType, ruleToSave, }); @@ -215,7 +232,7 @@ export const NumberingRuleDesigner: React.FC = ({ ); const handleNewRule = useCallback(() => { - console.log("📋 새 규칙 생성 - currentTableName:", currentTableName); + console.log("📋 새 규칙 생성:", { currentTableName, menuObjid }); const newRule: NumberingRuleConfig = { ruleId: `rule-${Date.now()}`, @@ -224,8 +241,9 @@ export const NumberingRuleDesigner: React.FC = ({ separator: "-", resetPeriod: "none", currentSequence: 1, - scopeType: "table", // 기본값을 table로 설정 + scopeType: "table", // ⚠️ 임시: DB 제약 조건 때문에 table 유지 tableName: currentTableName || "", // 현재 화면의 테이블명 자동 설정 + menuObjid: menuObjid || null, // 🆕 메뉴 OBJID 설정 (필터링용) }; console.log("📋 생성된 규칙 정보:", newRule); @@ -234,7 +252,7 @@ export const NumberingRuleDesigner: React.FC = ({ setCurrentRule(newRule); toast.success("새 규칙이 생성되었습니다"); - }, [currentTableName]); + }, [currentTableName, menuObjid]); return (
diff --git a/frontend/components/screen/InteractiveScreenViewer.tsx b/frontend/components/screen/InteractiveScreenViewer.tsx index 9eab7004..d408fc93 100644 --- a/frontend/components/screen/InteractiveScreenViewer.tsx +++ b/frontend/components/screen/InteractiveScreenViewer.tsx @@ -1092,15 +1092,12 @@ export const InteractiveScreenViewer: React.FC = ( const widget = comp as WidgetComponent; const config = widget.webTypeConfig as CodeTypeConfig | undefined; - console.log("🔍 InteractiveScreenViewer - Code 위젯 (공통코드 선택):", { + console.log(`🔍 [InteractiveScreenViewer] Code 위젯 렌더링:`, { componentId: widget.id, - widgetType: widget.widgetType, columnName: widget.columnName, - fieldName, - currentValue, - formData, - config, codeCategory: config?.codeCategory, + menuObjid, + hasMenuObjid: !!menuObjid, }); // code 타입은 공통코드 선택박스로 처리 diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 54f26a8d..fc412291 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -143,6 +143,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD }); const [isSaving, setIsSaving] = useState(false); + // 🆕 화면에 할당된 메뉴 OBJID + const [menuObjid, setMenuObjid] = useState(undefined); + // 메뉴 할당 모달 상태 const [showMenuAssignmentModal, setShowMenuAssignmentModal] = useState(false); @@ -880,6 +883,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD const loadLayout = async () => { try { + // 🆕 화면에 할당된 메뉴 조회 + const menuInfo = await screenApi.getScreenMenu(selectedScreen.screenId); + if (menuInfo) { + setMenuObjid(menuInfo.menuObjid); + console.log("🔗 화면에 할당된 메뉴:", menuInfo); + } else { + console.warn("⚠️ 화면에 할당된 메뉴가 없습니다"); + } + const response = await screenApi.getLayout(selectedScreen.screenId); if (response) { // 🔄 마이그레이션 필요 여부 확인 @@ -4205,6 +4217,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD currentResolution={screenResolution} onResolutionChange={handleResolutionChange} allComponents={layout.components} // 🆕 플로우 위젯 감지용 + menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 /> @@ -4497,6 +4510,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD onDragStart={(e) => startComponentDrag(component, e)} onDragEnd={endDrag} selectedScreen={selectedScreen} + menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 // onZoneComponentDrop 제거 onZoneClick={handleZoneClick} // 설정 변경 핸들러 (테이블 페이지 크기 등 설정을 상세설정에 반영) diff --git a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx index 84297aa7..aa63e451 100644 --- a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx +++ b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx @@ -84,6 +84,8 @@ interface UnifiedPropertiesPanelProps { onResolutionChange?: (resolution: { name: string; width: number; height: number }) => void; // 🆕 플로우 위젯 감지용 allComponents?: ComponentData[]; + // 🆕 메뉴 OBJID (코드/카테고리 스코프용) + menuObjid?: number; } export const UnifiedPropertiesPanel: React.FC = ({ @@ -98,6 +100,7 @@ export const UnifiedPropertiesPanel: React.FC = ({ currentTableName, dragState, onStyleChange, + menuObjid, currentResolution, onResolutionChange, allComponents = [], // 🆕 기본값 빈 배열 @@ -685,6 +688,7 @@ export const UnifiedPropertiesPanel: React.FC = ({ screenTableName={selectedComponent.tableName || currentTable?.tableName || currentTableName} tableColumns={currentTable?.columns || []} tables={tables} + menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 onChange={(newConfig) => { console.log("🔄 DynamicComponentConfigPanel onChange:", newConfig); // 개별 속성별로 업데이트하여 다른 속성과의 충돌 방지 @@ -848,6 +852,7 @@ export const UnifiedPropertiesPanel: React.FC = ({ screenTableName={widget.tableName || currentTable?.tableName || currentTableName} tableColumns={currentTable?.columns || []} tables={tables} + menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 onChange={(newConfig) => { console.log("🔄 DynamicComponentConfigPanel onChange (widget):", newConfig); // 전체 componentConfig를 업데이트 diff --git a/frontend/hooks/queries/useCodes.ts b/frontend/hooks/queries/useCodes.ts index 0b95f907..470a4a60 100644 --- a/frontend/hooks/queries/useCodes.ts +++ b/frontend/hooks/queries/useCodes.ts @@ -33,7 +33,6 @@ export function useTableCodeCategory(tableName?: string, columnName?: string) { queryFn: async () => { if (!tableName || !columnName) return null; - console.log(`🔍 [React Query] 테이블 코드 카테고리 조회: ${tableName}.${columnName}`); const columns = await tableTypeApi.getColumns(tableName); const targetColumn = columns.find((col) => col.columnName === columnName); @@ -41,7 +40,6 @@ export function useTableCodeCategory(tableName?: string, columnName?: string) { ? targetColumn.codeCategory : null; - console.log(`✅ [React Query] 테이블 코드 카테고리 결과: ${tableName}.${columnName} -> ${codeCategory}`); return codeCategory; }, enabled: !!(tableName && columnName), @@ -59,12 +57,25 @@ export function useCodeOptions(codeCategory?: string, enabled: boolean = true, m queryFn: async () => { if (!codeCategory || codeCategory === "none") return []; - console.log(`🔍 [React Query] 코드 옵션 조회: ${codeCategory} (menuObjid: ${menuObjid})`); + console.log(`🔍 [useCodeOptions] 코드 옵션 조회 시작:`, { + codeCategory, + menuObjid, + hasMenuObjid: !!menuObjid, + }); + const response = await commonCodeApi.codes.getList(codeCategory, { isActive: true, menuObjid }); + console.log(`📦 [useCodeOptions] API 응답:`, { + codeCategory, + menuObjid, + success: response.success, + dataCount: response.data?.length || 0, + rawData: response.data, + }); + if (response.success && response.data) { const options = response.data.map((code: any) => { const actualValue = code.code || code.CODE || code.value || code.code_value || code.codeValue; @@ -78,7 +89,13 @@ export function useCodeOptions(codeCategory?: string, enabled: boolean = true, m }; }); - console.log(`✅ [React Query] 코드 옵션 결과: ${codeCategory} (${options.length}개, menuObjid: ${menuObjid})`); + console.log(`✅ [useCodeOptions] 옵션 변환 완료:`, { + codeCategory, + menuObjid, + optionsCount: options.length, + options, + }); + return options; } diff --git a/frontend/lib/api/screen.ts b/frontend/lib/api/screen.ts index d1d07d96..736e61b0 100644 --- a/frontend/lib/api/screen.ts +++ b/frontend/lib/api/screen.ts @@ -46,6 +46,12 @@ export const screenApi = { } as ScreenDefinition; }, + // 화면에 할당된 메뉴 조회 + getScreenMenu: async (screenId: number): Promise<{ menuObjid: number; menuName?: string } | null> => { + const response = await apiClient.get(`/screen-management/screens/${screenId}/menu`); + return response.data?.data || null; + }, + // 화면 생성 createScreen: async (screenData: CreateScreenRequest): Promise => { const response = await apiClient.post("/screen-management/screens", screenData); diff --git a/frontend/lib/registry/components/numbering-rule/NumberingRuleComponent.tsx b/frontend/lib/registry/components/numbering-rule/NumberingRuleComponent.tsx index 0c2e795c..6f1048f9 100644 --- a/frontend/lib/registry/components/numbering-rule/NumberingRuleComponent.tsx +++ b/frontend/lib/registry/components/numbering-rule/NumberingRuleComponent.tsx @@ -9,6 +9,7 @@ interface NumberingRuleWrapperProps { onChange?: (config: NumberingRuleComponentConfig) => void; isPreview?: boolean; tableName?: string; // 현재 화면의 테이블명 + menuObjid?: number; // 🆕 메뉴 OBJID } export const NumberingRuleWrapper: React.FC = ({ @@ -16,8 +17,14 @@ export const NumberingRuleWrapper: React.FC = ({ onChange, isPreview = false, tableName, + menuObjid, }) => { - console.log("📋 NumberingRuleWrapper: 테이블명 전달", { tableName, config }); + console.log("📋 NumberingRuleWrapper: 테이블명 + menuObjid 전달", { + tableName, + menuObjid, + hasMenuObjid: !!menuObjid, + config + }); return (
@@ -26,6 +33,7 @@ export const NumberingRuleWrapper: React.FC = ({ isPreview={isPreview} className="h-full" currentTableName={tableName} // 테이블명 전달 + menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 />
); diff --git a/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx b/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx index 14580ce8..2597a143 100644 --- a/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx +++ b/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx @@ -50,17 +50,6 @@ const SelectBasicComponent: React.FC = ({ menuObjid, // 🆕 메뉴 OBJID ...props }) => { - // 🚨 최우선 디버깅: 컴포넌트가 실행되는지 확인 - console.log("🚨🚨🚨 SelectBasicComponent 실행됨!!!", { - componentId: component?.id, - componentType: component?.type, - webType: component?.webType, - tableName: component?.tableName, - columnName: component?.columnName, - screenId, - timestamp: new Date().toISOString(), - }); - const [isOpen, setIsOpen] = useState(false); // webTypeConfig 또는 componentConfig 사용 (DynamicWebTypeRenderer 호환성) @@ -79,30 +68,6 @@ const SelectBasicComponent: React.FC = ({ // autocomplete의 경우 검색어 관리 const [searchQuery, setSearchQuery] = useState(""); - console.log("🔍 SelectBasicComponent 초기화 (React Query):", { - componentId: component.id, - externalValue, - componentConfigValue: componentConfig?.value, - webTypeConfigValue: (props as any).webTypeConfig?.value, - configValue: config?.value, - finalSelectedValue: externalValue || config?.value || "", - tableName: component.tableName, - columnName: component.columnName, - staticCodeCategory: config?.codeCategory, - // React Query 디버깅 정보 - timestamp: new Date().toISOString(), - mountCount: ++(window as any).selectMountCount || ((window as any).selectMountCount = 1), - }); - - // 언마운트 시 로깅 - useEffect(() => { - const componentId = component.id; - console.log(`🔍 [${componentId}] SelectBasicComponent 마운트됨`); - - return () => { - console.log(`🔍 [${componentId}] SelectBasicComponent 언마운트됨`); - }; - }, [component.id]); const selectRef = useRef(null); @@ -117,11 +82,6 @@ const SelectBasicComponent: React.FC = ({ // 코드 카테고리 결정: 동적 카테고리 > 설정 카테고리 (메모이제이션) const codeCategory = useMemo(() => { const category = dynamicCodeCategory || staticCodeCategory; - console.log(`🔑 [${component.id}] 코드 카테고리 결정:`, { - dynamicCodeCategory, - staticCodeCategory, - finalCategory: category, - }); return category; }, [dynamicCodeCategory, staticCodeCategory, component.id]); @@ -136,32 +96,25 @@ const SelectBasicComponent: React.FC = ({ isFetching, } = useCodeOptions(codeCategory, isCodeCategoryValid, menuObjid); - // React Query 상태 디버깅 + // 디버깅: menuObjid가 제대로 전달되는지 확인 useEffect(() => { - console.log(`🎯 [${component.id}] React Query 상태:`, { - codeCategory, - isCodeCategoryValid, - codeOptionsLength: codeOptions.length, - isLoadingCodes, - isFetching, - cacheStatus: isFetching ? "FETCHING" : "FROM_CACHE", - }); - }, [component.id, codeCategory, isCodeCategoryValid, codeOptions.length, isLoadingCodes, isFetching]); + if (codeCategory && codeCategory !== "none") { + console.log(`🎯 [SelectBasicComponent ${component.id}] 코드 옵션 로드:`, { + codeCategory, + menuObjid, + hasMenuObjid: !!menuObjid, + isCodeCategoryValid, + codeOptionsCount: codeOptions.length, + isLoading: isLoadingCodes, + }); + } + }, [component.id, codeCategory, menuObjid, codeOptions.length, isLoadingCodes, isCodeCategoryValid]); // 외부 value prop 변경 시 selectedValue 업데이트 useEffect(() => { const newValue = externalValue || config?.value || ""; // 값이 실제로 다른 경우에만 업데이트 (빈 문자열도 유효한 값으로 처리) if (newValue !== selectedValue) { - console.log(`🔄 SelectBasicComponent value 업데이트: "${selectedValue}" → "${newValue}"`); - console.log("🔍 업데이트 조건 분석:", { - externalValue, - componentConfigValue: componentConfig?.value, - configValue: config?.value, - newValue, - selectedValue, - shouldUpdate: newValue !== selectedValue, - }); setSelectedValue(newValue); } }, [externalValue, config?.value]); @@ -190,23 +143,12 @@ const SelectBasicComponent: React.FC = ({ const labelMatch = options.find((option) => option.label === selectedValue); if (labelMatch) { newLabel = labelMatch.label; - console.log(`🔍 [${component.id}] 코드명으로 매치 발견: "${selectedValue}" → "${newLabel}"`); } else { // 2) selectedValue가 코드값인 경우라면 원래 로직대로 라벨을 찾되, 없으면 원값 표시 newLabel = selectedValue; // 코드값 그대로 표시 (예: "555") - console.log(`🔍 [${component.id}] 코드값 원본 유지: "${selectedValue}"`); } } - console.log(`🏷️ [${component.id}] 라벨 업데이트:`, { - selectedValue, - selectedOption: selectedOption ? { value: selectedOption.value, label: selectedOption.label } : null, - newLabel, - optionsCount: options.length, - allOptionsValues: options.map((o) => o.value), - allOptionsLabels: options.map((o) => o.label), - }); - if (newLabel !== selectedLabel) { setSelectedLabel(newLabel); } @@ -216,15 +158,6 @@ const SelectBasicComponent: React.FC = ({ const handleToggle = () => { if (isDesignMode) return; - console.log(`🖱️ [${component.id}] 드롭다운 토글 (React Query): ${isOpen} → ${!isOpen}`); - console.log(`📊 [${component.id}] 현재 상태:`, { - codeCategory, - isLoadingCodes, - codeOptionsLength: codeOptions.length, - tableName: component.tableName, - columnName: component.columnName, - }); - // React Query가 자동으로 캐시 관리하므로 수동 새로고침 불필요 setIsOpen(!isOpen); }; @@ -242,17 +175,8 @@ const SelectBasicComponent: React.FC = ({ // 인터랙티브 모드에서 폼 데이터 업데이트 (TextInputComponent와 동일한 로직) if (isInteractive && onFormDataChange && component.columnName) { - console.log(`📤 SelectBasicComponent -> onFormDataChange 호출: ${component.columnName} = "${value}"`); onFormDataChange(component.columnName, value); - } else { - console.log("❌ SelectBasicComponent onFormDataChange 조건 미충족:", { - isInteractive, - hasOnFormDataChange: !!onFormDataChange, - hasColumnName: !!component.columnName, - }); } - - console.log(`✅ [${component.id}] 옵션 선택:`, { value, label }); }; // 외부 클릭 시 드롭다운 닫기 @@ -280,12 +204,6 @@ const SelectBasicComponent: React.FC = ({ // 모든 옵션 가져오기 const getAllOptions = () => { const configOptions = config.options || []; - console.log(`🔧 [${component.id}] 옵션 병합:`, { - codeOptionsLength: codeOptions.length, - codeOptions: codeOptions.map((o: Option) => ({ value: o.value, label: o.label })), - configOptionsLength: configOptions.length, - configOptions: configOptions.map((o: Option) => ({ value: o.value, label: o.label })), - }); return [...codeOptions, ...configOptions]; };