feat: 화면 편집기에서 메뉴 기반 데이터 스코프 적용
- 백엔드: screenManagementService에 getMenuByScreen 함수 추가 - 백엔드: GET /api/screen-management/screens/:id/menu 엔드포인트 추가 - 프론트엔드: screenApi.getScreenMenu() 함수 추가 - ScreenDesigner: 화면 로드 시 menu_objid 자동 조회 - ScreenDesigner: menuObjid를 RealtimePreview와 UnifiedPropertiesPanel에 전달 - UnifiedPropertiesPanel: menuObjid를 DynamicComponentConfigPanel에 전달 이로써 화면 편집기에서 코드/카테고리/채번규칙이 해당 화면이 할당된 메뉴 기준으로 필터링됨
This commit is contained in:
parent
32d4575fb5
commit
6534d03ecd
|
|
@ -112,6 +112,17 @@ router.post("/", authenticateToken, async (req: AuthenticatedRequest, res: Respo
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const ruleConfig = req.body;
|
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 {
|
try {
|
||||||
if (!ruleConfig.ruleId || !ruleConfig.ruleName) {
|
if (!ruleConfig.ruleId || !ruleConfig.ruleName) {
|
||||||
return res.status(400).json({ success: false, error: "규칙 ID와 규칙명은 필수입니다" });
|
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);
|
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 });
|
return res.status(201).json({ success: true, data: newRule });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.code === "23505") {
|
if (error.code === "23505") {
|
||||||
return res.status(409).json({ success: false, error: "이미 존재하는 규칙 ID입니다" });
|
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 });
|
return res.status(500).json({ success: false, error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,29 @@ export const getScreen = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 화면에 할당된 메뉴 조회
|
||||||
|
export const getScreenMenu = async (
|
||||||
|
req: AuthenticatedRequest,
|
||||||
|
res: Response
|
||||||
|
): Promise<void> => {
|
||||||
|
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 (
|
export const createScreen = async (
|
||||||
req: AuthenticatedRequest,
|
req: AuthenticatedRequest,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { authenticateToken } from "../middleware/authMiddleware";
|
||||||
import {
|
import {
|
||||||
getScreens,
|
getScreens,
|
||||||
getScreen,
|
getScreen,
|
||||||
|
getScreenMenu,
|
||||||
createScreen,
|
createScreen,
|
||||||
updateScreen,
|
updateScreen,
|
||||||
updateScreenInfo,
|
updateScreenInfo,
|
||||||
|
|
@ -33,6 +34,7 @@ router.use(authenticateToken);
|
||||||
// 화면 관리
|
// 화면 관리
|
||||||
router.get("/screens", getScreens);
|
router.get("/screens", getScreens);
|
||||||
router.get("/screens/:id", getScreen);
|
router.get("/screens/:id", getScreen);
|
||||||
|
router.get("/screens/:id/menu", getScreenMenu); // 화면에 할당된 메뉴 조회
|
||||||
router.post("/screens", createScreen);
|
router.post("/screens", createScreen);
|
||||||
router.put("/screens/:id", updateScreen);
|
router.put("/screens/:id", updateScreen);
|
||||||
router.put("/screens/:id/info", updateScreenInfo); // 화면 정보만 수정
|
router.put("/screens/:id/info", updateScreenInfo); // 화면 정보만 수정
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,17 @@ export class CommonCodeService {
|
||||||
try {
|
try {
|
||||||
const { search, isActive, page = 1, size = 20 } = params;
|
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 whereConditions: string[] = ["code_category = $1"];
|
||||||
const values: any[] = [categoryCode];
|
const values: any[] = [categoryCode];
|
||||||
let paramIndex = 2;
|
let paramIndex = 2;
|
||||||
|
|
@ -169,7 +180,13 @@ export class CommonCodeService {
|
||||||
whereConditions.push(`menu_objid = ANY($${paramIndex})`);
|
whereConditions.push(`menu_objid = ANY($${paramIndex})`);
|
||||||
values.push(siblingMenuObjids);
|
values.push(siblingMenuObjids);
|
||||||
paramIndex++;
|
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;
|
const offset = (page - 1) * size;
|
||||||
|
|
||||||
|
logger.info(`📝 [getCodes] 실행할 쿼리:`, {
|
||||||
|
whereClause,
|
||||||
|
values,
|
||||||
|
whereConditions,
|
||||||
|
paramIndex,
|
||||||
|
});
|
||||||
|
|
||||||
// 코드 조회
|
// 코드 조회
|
||||||
const codes = await query<CodeInfo>(
|
const codes = await query<CodeInfo>(
|
||||||
`SELECT * FROM code_info
|
`SELECT * FROM code_info
|
||||||
|
|
@ -217,9 +241,20 @@ export class CommonCodeService {
|
||||||
const total = parseInt(countResult?.count || "0");
|
const total = parseInt(countResult?.count || "0");
|
||||||
|
|
||||||
logger.info(
|
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 };
|
return { data: codes, total };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`코드 조회 중 오류 (${categoryCode}):`, error);
|
logger.error(`코드 조회 중 오류 (${categoryCode}):`, error);
|
||||||
|
|
|
||||||
|
|
@ -301,16 +301,18 @@ class NumberingRuleService {
|
||||||
scope_type = 'global'
|
scope_type = 'global'
|
||||||
OR scope_type = 'table'
|
OR scope_type = 'table'
|
||||||
OR (scope_type = 'menu' AND menu_objid = ANY($1))
|
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
|
ORDER BY
|
||||||
CASE scope_type
|
CASE
|
||||||
WHEN 'menu' THEN 1
|
WHEN scope_type = 'menu' OR (scope_type = 'table' AND menu_objid = ANY($1)) THEN 1
|
||||||
WHEN 'table' THEN 2
|
WHEN scope_type = 'table' THEN 2
|
||||||
WHEN 'global' THEN 3
|
WHEN scope_type = 'global' THEN 3
|
||||||
END,
|
END,
|
||||||
created_at DESC
|
created_at DESC
|
||||||
`;
|
`;
|
||||||
params = [siblingObjids];
|
params = [siblingObjids];
|
||||||
logger.info("최고 관리자: 형제 메뉴 포함 채번 규칙 조회", { siblingObjids });
|
logger.info("최고 관리자: 형제 메뉴 포함 채번 규칙 조회 (기존 규칙 포함)", { siblingObjids });
|
||||||
} else {
|
} else {
|
||||||
// 일반 회사: 자신의 규칙만 조회 (형제 메뉴 포함)
|
// 일반 회사: 자신의 규칙만 조회 (형제 메뉴 포함)
|
||||||
query = `
|
query = `
|
||||||
|
|
@ -335,17 +337,19 @@ class NumberingRuleService {
|
||||||
scope_type = 'global'
|
scope_type = 'global'
|
||||||
OR scope_type = 'table'
|
OR scope_type = 'table'
|
||||||
OR (scope_type = 'menu' AND menu_objid = ANY($2))
|
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
|
ORDER BY
|
||||||
CASE scope_type
|
CASE
|
||||||
WHEN 'menu' THEN 1
|
WHEN scope_type = 'menu' OR (scope_type = 'table' AND menu_objid = ANY($2)) THEN 1
|
||||||
WHEN 'table' THEN 2
|
WHEN scope_type = 'table' THEN 2
|
||||||
WHEN 'global' THEN 3
|
WHEN scope_type = 'global' THEN 3
|
||||||
END,
|
END,
|
||||||
created_at DESC
|
created_at DESC
|
||||||
`;
|
`;
|
||||||
params = [companyCode, siblingObjids];
|
params = [companyCode, siblingObjids];
|
||||||
logger.info("회사별: 형제 메뉴 포함 채번 규칙 조회", { companyCode, siblingObjids });
|
logger.info("회사별: 형제 메뉴 포함 채번 규칙 조회 (기존 규칙 포함)", { companyCode, siblingObjids });
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("🔍 채번 규칙 쿼리 실행", {
|
logger.info("🔍 채번 규칙 쿼리 실행", {
|
||||||
|
|
|
||||||
|
|
@ -1547,6 +1547,39 @@ export class ScreenManagementService {
|
||||||
return screens.map((screen) => this.mapToScreenDefinition(screen));
|
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 전환 완료)
|
* 화면-메뉴 할당 해제 (✅ Raw Query 전환 완료)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { NumberingRuleConfig, NumberingRulePart } from "@/types/numbering-rule";
|
||||||
import { NumberingRuleCard } from "./NumberingRuleCard";
|
import { NumberingRuleCard } from "./NumberingRuleCard";
|
||||||
import { NumberingRulePreview } from "./NumberingRulePreview";
|
import { NumberingRulePreview } from "./NumberingRulePreview";
|
||||||
import {
|
import {
|
||||||
getNumberingRules,
|
getAvailableNumberingRules,
|
||||||
createNumberingRule,
|
createNumberingRule,
|
||||||
updateNumberingRule,
|
updateNumberingRule,
|
||||||
deleteNumberingRule,
|
deleteNumberingRule,
|
||||||
|
|
@ -55,7 +55,20 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
const loadRules = useCallback(async () => {
|
const loadRules = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
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) {
|
if (response.success && response.data) {
|
||||||
setSavedRules(response.data);
|
setSavedRules(response.data);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -135,17 +148,21 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
try {
|
try {
|
||||||
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
|
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
|
||||||
|
|
||||||
// 저장 전에 현재 화면의 테이블명 자동 설정
|
// 저장 전에 현재 화면의 테이블명과 menuObjid 자동 설정
|
||||||
const ruleToSave = {
|
const ruleToSave = {
|
||||||
...currentRule,
|
...currentRule,
|
||||||
scopeType: "table" as const, // 항상 table로 고정
|
scopeType: "table" as const, // ⚠️ 임시: DB 제약 조건 때문에 table 유지
|
||||||
tableName: currentTableName || currentRule.tableName || "", // 현재 테이블명 자동 설정
|
tableName: currentTableName || currentRule.tableName || "", // 현재 테이블명 자동 설정
|
||||||
|
menuObjid: menuObjid || currentRule.menuObjid || null, // 🆕 메뉴 OBJID 설정 (필터링용)
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("💾 채번 규칙 저장:", {
|
console.log("💾 채번 규칙 저장:", {
|
||||||
currentTableName,
|
currentTableName,
|
||||||
|
menuObjid,
|
||||||
"currentRule.tableName": currentRule.tableName,
|
"currentRule.tableName": currentRule.tableName,
|
||||||
|
"currentRule.menuObjid": currentRule.menuObjid,
|
||||||
"ruleToSave.tableName": ruleToSave.tableName,
|
"ruleToSave.tableName": ruleToSave.tableName,
|
||||||
|
"ruleToSave.menuObjid": ruleToSave.menuObjid,
|
||||||
"ruleToSave.scopeType": ruleToSave.scopeType,
|
"ruleToSave.scopeType": ruleToSave.scopeType,
|
||||||
ruleToSave,
|
ruleToSave,
|
||||||
});
|
});
|
||||||
|
|
@ -215,7 +232,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNewRule = useCallback(() => {
|
const handleNewRule = useCallback(() => {
|
||||||
console.log("📋 새 규칙 생성 - currentTableName:", currentTableName);
|
console.log("📋 새 규칙 생성:", { currentTableName, menuObjid });
|
||||||
|
|
||||||
const newRule: NumberingRuleConfig = {
|
const newRule: NumberingRuleConfig = {
|
||||||
ruleId: `rule-${Date.now()}`,
|
ruleId: `rule-${Date.now()}`,
|
||||||
|
|
@ -224,8 +241,9 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
separator: "-",
|
separator: "-",
|
||||||
resetPeriod: "none",
|
resetPeriod: "none",
|
||||||
currentSequence: 1,
|
currentSequence: 1,
|
||||||
scopeType: "table", // 기본값을 table로 설정
|
scopeType: "table", // ⚠️ 임시: DB 제약 조건 때문에 table 유지
|
||||||
tableName: currentTableName || "", // 현재 화면의 테이블명 자동 설정
|
tableName: currentTableName || "", // 현재 화면의 테이블명 자동 설정
|
||||||
|
menuObjid: menuObjid || null, // 🆕 메뉴 OBJID 설정 (필터링용)
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("📋 생성된 규칙 정보:", newRule);
|
console.log("📋 생성된 규칙 정보:", newRule);
|
||||||
|
|
@ -234,7 +252,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
setCurrentRule(newRule);
|
setCurrentRule(newRule);
|
||||||
|
|
||||||
toast.success("새 규칙이 생성되었습니다");
|
toast.success("새 규칙이 생성되었습니다");
|
||||||
}, [currentTableName]);
|
}, [currentTableName, menuObjid]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex h-full gap-4 ${className}`}>
|
<div className={`flex h-full gap-4 ${className}`}>
|
||||||
|
|
|
||||||
|
|
@ -1092,15 +1092,12 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
const widget = comp as WidgetComponent;
|
const widget = comp as WidgetComponent;
|
||||||
const config = widget.webTypeConfig as CodeTypeConfig | undefined;
|
const config = widget.webTypeConfig as CodeTypeConfig | undefined;
|
||||||
|
|
||||||
console.log("🔍 InteractiveScreenViewer - Code 위젯 (공통코드 선택):", {
|
console.log(`🔍 [InteractiveScreenViewer] Code 위젯 렌더링:`, {
|
||||||
componentId: widget.id,
|
componentId: widget.id,
|
||||||
widgetType: widget.widgetType,
|
|
||||||
columnName: widget.columnName,
|
columnName: widget.columnName,
|
||||||
fieldName,
|
|
||||||
currentValue,
|
|
||||||
formData,
|
|
||||||
config,
|
|
||||||
codeCategory: config?.codeCategory,
|
codeCategory: config?.codeCategory,
|
||||||
|
menuObjid,
|
||||||
|
hasMenuObjid: !!menuObjid,
|
||||||
});
|
});
|
||||||
|
|
||||||
// code 타입은 공통코드 선택박스로 처리
|
// code 타입은 공통코드 선택박스로 처리
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
});
|
});
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
// 🆕 화면에 할당된 메뉴 OBJID
|
||||||
|
const [menuObjid, setMenuObjid] = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
// 메뉴 할당 모달 상태
|
// 메뉴 할당 모달 상태
|
||||||
const [showMenuAssignmentModal, setShowMenuAssignmentModal] = useState(false);
|
const [showMenuAssignmentModal, setShowMenuAssignmentModal] = useState(false);
|
||||||
|
|
||||||
|
|
@ -880,6 +883,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
|
|
||||||
const loadLayout = async () => {
|
const loadLayout = async () => {
|
||||||
try {
|
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);
|
const response = await screenApi.getLayout(selectedScreen.screenId);
|
||||||
if (response) {
|
if (response) {
|
||||||
// 🔄 마이그레이션 필요 여부 확인
|
// 🔄 마이그레이션 필요 여부 확인
|
||||||
|
|
@ -4205,6 +4217,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
currentResolution={screenResolution}
|
currentResolution={screenResolution}
|
||||||
onResolutionChange={handleResolutionChange}
|
onResolutionChange={handleResolutionChange}
|
||||||
allComponents={layout.components} // 🆕 플로우 위젯 감지용
|
allComponents={layout.components} // 🆕 플로우 위젯 감지용
|
||||||
|
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
@ -4497,6 +4510,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
onDragStart={(e) => startComponentDrag(component, e)}
|
onDragStart={(e) => startComponentDrag(component, e)}
|
||||||
onDragEnd={endDrag}
|
onDragEnd={endDrag}
|
||||||
selectedScreen={selectedScreen}
|
selectedScreen={selectedScreen}
|
||||||
|
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||||
// onZoneComponentDrop 제거
|
// onZoneComponentDrop 제거
|
||||||
onZoneClick={handleZoneClick}
|
onZoneClick={handleZoneClick}
|
||||||
// 설정 변경 핸들러 (테이블 페이지 크기 등 설정을 상세설정에 반영)
|
// 설정 변경 핸들러 (테이블 페이지 크기 등 설정을 상세설정에 반영)
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,8 @@ interface UnifiedPropertiesPanelProps {
|
||||||
onResolutionChange?: (resolution: { name: string; width: number; height: number }) => void;
|
onResolutionChange?: (resolution: { name: string; width: number; height: number }) => void;
|
||||||
// 🆕 플로우 위젯 감지용
|
// 🆕 플로우 위젯 감지용
|
||||||
allComponents?: ComponentData[];
|
allComponents?: ComponentData[];
|
||||||
|
// 🆕 메뉴 OBJID (코드/카테고리 스코프용)
|
||||||
|
menuObjid?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
|
|
@ -98,6 +100,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
currentTableName,
|
currentTableName,
|
||||||
dragState,
|
dragState,
|
||||||
onStyleChange,
|
onStyleChange,
|
||||||
|
menuObjid,
|
||||||
currentResolution,
|
currentResolution,
|
||||||
onResolutionChange,
|
onResolutionChange,
|
||||||
allComponents = [], // 🆕 기본값 빈 배열
|
allComponents = [], // 🆕 기본값 빈 배열
|
||||||
|
|
@ -685,6 +688,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
screenTableName={selectedComponent.tableName || currentTable?.tableName || currentTableName}
|
screenTableName={selectedComponent.tableName || currentTable?.tableName || currentTableName}
|
||||||
tableColumns={currentTable?.columns || []}
|
tableColumns={currentTable?.columns || []}
|
||||||
tables={tables}
|
tables={tables}
|
||||||
|
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||||
onChange={(newConfig) => {
|
onChange={(newConfig) => {
|
||||||
console.log("🔄 DynamicComponentConfigPanel onChange:", newConfig);
|
console.log("🔄 DynamicComponentConfigPanel onChange:", newConfig);
|
||||||
// 개별 속성별로 업데이트하여 다른 속성과의 충돌 방지
|
// 개별 속성별로 업데이트하여 다른 속성과의 충돌 방지
|
||||||
|
|
@ -848,6 +852,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
screenTableName={widget.tableName || currentTable?.tableName || currentTableName}
|
screenTableName={widget.tableName || currentTable?.tableName || currentTableName}
|
||||||
tableColumns={currentTable?.columns || []}
|
tableColumns={currentTable?.columns || []}
|
||||||
tables={tables}
|
tables={tables}
|
||||||
|
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||||
onChange={(newConfig) => {
|
onChange={(newConfig) => {
|
||||||
console.log("🔄 DynamicComponentConfigPanel onChange (widget):", newConfig);
|
console.log("🔄 DynamicComponentConfigPanel onChange (widget):", newConfig);
|
||||||
// 전체 componentConfig를 업데이트
|
// 전체 componentConfig를 업데이트
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ export function useTableCodeCategory(tableName?: string, columnName?: string) {
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!tableName || !columnName) return null;
|
if (!tableName || !columnName) return null;
|
||||||
|
|
||||||
console.log(`🔍 [React Query] 테이블 코드 카테고리 조회: ${tableName}.${columnName}`);
|
|
||||||
const columns = await tableTypeApi.getColumns(tableName);
|
const columns = await tableTypeApi.getColumns(tableName);
|
||||||
const targetColumn = columns.find((col) => col.columnName === columnName);
|
const targetColumn = columns.find((col) => col.columnName === columnName);
|
||||||
|
|
||||||
|
|
@ -41,7 +40,6 @@ export function useTableCodeCategory(tableName?: string, columnName?: string) {
|
||||||
? targetColumn.codeCategory
|
? targetColumn.codeCategory
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
console.log(`✅ [React Query] 테이블 코드 카테고리 결과: ${tableName}.${columnName} -> ${codeCategory}`);
|
|
||||||
return codeCategory;
|
return codeCategory;
|
||||||
},
|
},
|
||||||
enabled: !!(tableName && columnName),
|
enabled: !!(tableName && columnName),
|
||||||
|
|
@ -59,12 +57,25 @@ export function useCodeOptions(codeCategory?: string, enabled: boolean = true, m
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!codeCategory || codeCategory === "none") return [];
|
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, {
|
const response = await commonCodeApi.codes.getList(codeCategory, {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
menuObjid
|
menuObjid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`📦 [useCodeOptions] API 응답:`, {
|
||||||
|
codeCategory,
|
||||||
|
menuObjid,
|
||||||
|
success: response.success,
|
||||||
|
dataCount: response.data?.length || 0,
|
||||||
|
rawData: response.data,
|
||||||
|
});
|
||||||
|
|
||||||
if (response.success && response.data) {
|
if (response.success && response.data) {
|
||||||
const options = response.data.map((code: any) => {
|
const options = response.data.map((code: any) => {
|
||||||
const actualValue = code.code || code.CODE || code.value || code.code_value || code.codeValue;
|
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;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,12 @@ export const screenApi = {
|
||||||
} as ScreenDefinition;
|
} 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<ScreenDefinition> => {
|
createScreen: async (screenData: CreateScreenRequest): Promise<ScreenDefinition> => {
|
||||||
const response = await apiClient.post("/screen-management/screens", screenData);
|
const response = await apiClient.post("/screen-management/screens", screenData);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ interface NumberingRuleWrapperProps {
|
||||||
onChange?: (config: NumberingRuleComponentConfig) => void;
|
onChange?: (config: NumberingRuleComponentConfig) => void;
|
||||||
isPreview?: boolean;
|
isPreview?: boolean;
|
||||||
tableName?: string; // 현재 화면의 테이블명
|
tableName?: string; // 현재 화면의 테이블명
|
||||||
|
menuObjid?: number; // 🆕 메뉴 OBJID
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
|
export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
|
||||||
|
|
@ -16,8 +17,14 @@ export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
|
||||||
onChange,
|
onChange,
|
||||||
isPreview = false,
|
isPreview = false,
|
||||||
tableName,
|
tableName,
|
||||||
|
menuObjid,
|
||||||
}) => {
|
}) => {
|
||||||
console.log("📋 NumberingRuleWrapper: 테이블명 전달", { tableName, config });
|
console.log("📋 NumberingRuleWrapper: 테이블명 + menuObjid 전달", {
|
||||||
|
tableName,
|
||||||
|
menuObjid,
|
||||||
|
hasMenuObjid: !!menuObjid,
|
||||||
|
config
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full">
|
||||||
|
|
@ -26,6 +33,7 @@ export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
|
||||||
isPreview={isPreview}
|
isPreview={isPreview}
|
||||||
className="h-full"
|
className="h-full"
|
||||||
currentTableName={tableName} // 테이블명 전달
|
currentTableName={tableName} // 테이블명 전달
|
||||||
|
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -50,17 +50,6 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
menuObjid, // 🆕 메뉴 OBJID
|
menuObjid, // 🆕 메뉴 OBJID
|
||||||
...props
|
...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);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
// webTypeConfig 또는 componentConfig 사용 (DynamicWebTypeRenderer 호환성)
|
// webTypeConfig 또는 componentConfig 사용 (DynamicWebTypeRenderer 호환성)
|
||||||
|
|
@ -79,30 +68,6 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
// autocomplete의 경우 검색어 관리
|
// autocomplete의 경우 검색어 관리
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
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<HTMLDivElement>(null);
|
const selectRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
|
@ -117,11 +82,6 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
// 코드 카테고리 결정: 동적 카테고리 > 설정 카테고리 (메모이제이션)
|
// 코드 카테고리 결정: 동적 카테고리 > 설정 카테고리 (메모이제이션)
|
||||||
const codeCategory = useMemo(() => {
|
const codeCategory = useMemo(() => {
|
||||||
const category = dynamicCodeCategory || staticCodeCategory;
|
const category = dynamicCodeCategory || staticCodeCategory;
|
||||||
console.log(`🔑 [${component.id}] 코드 카테고리 결정:`, {
|
|
||||||
dynamicCodeCategory,
|
|
||||||
staticCodeCategory,
|
|
||||||
finalCategory: category,
|
|
||||||
});
|
|
||||||
return category;
|
return category;
|
||||||
}, [dynamicCodeCategory, staticCodeCategory, component.id]);
|
}, [dynamicCodeCategory, staticCodeCategory, component.id]);
|
||||||
|
|
||||||
|
|
@ -136,32 +96,25 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
isFetching,
|
isFetching,
|
||||||
} = useCodeOptions(codeCategory, isCodeCategoryValid, menuObjid);
|
} = useCodeOptions(codeCategory, isCodeCategoryValid, menuObjid);
|
||||||
|
|
||||||
// React Query 상태 디버깅
|
// 디버깅: menuObjid가 제대로 전달되는지 확인
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(`🎯 [${component.id}] React Query 상태:`, {
|
if (codeCategory && codeCategory !== "none") {
|
||||||
|
console.log(`🎯 [SelectBasicComponent ${component.id}] 코드 옵션 로드:`, {
|
||||||
codeCategory,
|
codeCategory,
|
||||||
|
menuObjid,
|
||||||
|
hasMenuObjid: !!menuObjid,
|
||||||
isCodeCategoryValid,
|
isCodeCategoryValid,
|
||||||
codeOptionsLength: codeOptions.length,
|
codeOptionsCount: codeOptions.length,
|
||||||
isLoadingCodes,
|
isLoading: isLoadingCodes,
|
||||||
isFetching,
|
|
||||||
cacheStatus: isFetching ? "FETCHING" : "FROM_CACHE",
|
|
||||||
});
|
});
|
||||||
}, [component.id, codeCategory, isCodeCategoryValid, codeOptions.length, isLoadingCodes, isFetching]);
|
}
|
||||||
|
}, [component.id, codeCategory, menuObjid, codeOptions.length, isLoadingCodes, isCodeCategoryValid]);
|
||||||
|
|
||||||
// 외부 value prop 변경 시 selectedValue 업데이트
|
// 외부 value prop 변경 시 selectedValue 업데이트
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newValue = externalValue || config?.value || "";
|
const newValue = externalValue || config?.value || "";
|
||||||
// 값이 실제로 다른 경우에만 업데이트 (빈 문자열도 유효한 값으로 처리)
|
// 값이 실제로 다른 경우에만 업데이트 (빈 문자열도 유효한 값으로 처리)
|
||||||
if (newValue !== selectedValue) {
|
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);
|
setSelectedValue(newValue);
|
||||||
}
|
}
|
||||||
}, [externalValue, config?.value]);
|
}, [externalValue, config?.value]);
|
||||||
|
|
@ -190,23 +143,12 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
const labelMatch = options.find((option) => option.label === selectedValue);
|
const labelMatch = options.find((option) => option.label === selectedValue);
|
||||||
if (labelMatch) {
|
if (labelMatch) {
|
||||||
newLabel = labelMatch.label;
|
newLabel = labelMatch.label;
|
||||||
console.log(`🔍 [${component.id}] 코드명으로 매치 발견: "${selectedValue}" → "${newLabel}"`);
|
|
||||||
} else {
|
} else {
|
||||||
// 2) selectedValue가 코드값인 경우라면 원래 로직대로 라벨을 찾되, 없으면 원값 표시
|
// 2) selectedValue가 코드값인 경우라면 원래 로직대로 라벨을 찾되, 없으면 원값 표시
|
||||||
newLabel = selectedValue; // 코드값 그대로 표시 (예: "555")
|
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) {
|
if (newLabel !== selectedLabel) {
|
||||||
setSelectedLabel(newLabel);
|
setSelectedLabel(newLabel);
|
||||||
}
|
}
|
||||||
|
|
@ -216,15 +158,6 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
if (isDesignMode) return;
|
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가 자동으로 캐시 관리하므로 수동 새로고침 불필요
|
// React Query가 자동으로 캐시 관리하므로 수동 새로고침 불필요
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
};
|
};
|
||||||
|
|
@ -242,17 +175,8 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
|
|
||||||
// 인터랙티브 모드에서 폼 데이터 업데이트 (TextInputComponent와 동일한 로직)
|
// 인터랙티브 모드에서 폼 데이터 업데이트 (TextInputComponent와 동일한 로직)
|
||||||
if (isInteractive && onFormDataChange && component.columnName) {
|
if (isInteractive && onFormDataChange && component.columnName) {
|
||||||
console.log(`📤 SelectBasicComponent -> onFormDataChange 호출: ${component.columnName} = "${value}"`);
|
|
||||||
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<SelectBasicComponentProps> = ({
|
||||||
// 모든 옵션 가져오기
|
// 모든 옵션 가져오기
|
||||||
const getAllOptions = () => {
|
const getAllOptions = () => {
|
||||||
const configOptions = config.options || [];
|
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];
|
return [...codeOptions, ...configOptions];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue