feature/screen-management #199
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
req: AuthenticatedRequest,
|
||||
|
|
|
|||
|
|
@ -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); // 화면 정보만 수정
|
||||
|
|
|
|||
|
|
@ -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<CodeInfo>(
|
||||
`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);
|
||||
|
|
|
|||
|
|
@ -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("🔍 채번 규칙 쿼리 실행", {
|
||||
|
|
|
|||
|
|
@ -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 전환 완료)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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<NumberingRuleDesignerProps> = ({
|
|||
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<NumberingRuleDesignerProps> = ({
|
|||
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<NumberingRuleDesignerProps> = ({
|
|||
);
|
||||
|
||||
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<NumberingRuleDesignerProps> = ({
|
|||
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<NumberingRuleDesignerProps> = ({
|
|||
setCurrentRule(newRule);
|
||||
|
||||
toast.success("새 규칙이 생성되었습니다");
|
||||
}, [currentTableName]);
|
||||
}, [currentTableName, menuObjid]);
|
||||
|
||||
return (
|
||||
<div className={`flex h-full gap-4 ${className}`}>
|
||||
|
|
|
|||
|
|
@ -1092,15 +1092,12 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
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 타입은 공통코드 선택박스로 처리
|
||||
|
|
|
|||
|
|
@ -143,6 +143,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
});
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
// 🆕 화면에 할당된 메뉴 OBJID
|
||||
const [menuObjid, setMenuObjid] = useState<number | undefined>(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 전달
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
|
@ -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}
|
||||
// 설정 변경 핸들러 (테이블 페이지 크기 등 설정을 상세설정에 반영)
|
||||
|
|
|
|||
|
|
@ -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<UnifiedPropertiesPanelProps> = ({
|
||||
|
|
@ -98,6 +100,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
currentTableName,
|
||||
dragState,
|
||||
onStyleChange,
|
||||
menuObjid,
|
||||
currentResolution,
|
||||
onResolutionChange,
|
||||
allComponents = [], // 🆕 기본값 빈 배열
|
||||
|
|
@ -685,6 +688,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
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<UnifiedPropertiesPanelProps> = ({
|
|||
screenTableName={widget.tableName || currentTable?.tableName || currentTableName}
|
||||
tableColumns={currentTable?.columns || []}
|
||||
tables={tables}
|
||||
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||
onChange={(newConfig) => {
|
||||
console.log("🔄 DynamicComponentConfigPanel onChange (widget):", newConfig);
|
||||
// 전체 componentConfig를 업데이트
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ScreenDefinition> => {
|
||||
const response = await apiClient.post("/screen-management/screens", screenData);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ interface NumberingRuleWrapperProps {
|
|||
onChange?: (config: NumberingRuleComponentConfig) => void;
|
||||
isPreview?: boolean;
|
||||
tableName?: string; // 현재 화면의 테이블명
|
||||
menuObjid?: number; // 🆕 메뉴 OBJID
|
||||
}
|
||||
|
||||
export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
|
||||
|
|
@ -16,8 +17,14 @@ export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
|
|||
onChange,
|
||||
isPreview = false,
|
||||
tableName,
|
||||
menuObjid,
|
||||
}) => {
|
||||
console.log("📋 NumberingRuleWrapper: 테이블명 전달", { tableName, config });
|
||||
console.log("📋 NumberingRuleWrapper: 테이블명 + menuObjid 전달", {
|
||||
tableName,
|
||||
menuObjid,
|
||||
hasMenuObjid: !!menuObjid,
|
||||
config
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
|
|
@ -26,6 +33,7 @@ export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
|
|||
isPreview={isPreview}
|
||||
className="h-full"
|
||||
currentTableName={tableName} // 테이블명 전달
|
||||
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -50,17 +50,6 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
|||
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<SelectBasicComponentProps> = ({
|
|||
// 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<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -117,11 +82,6 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
|||
// 코드 카테고리 결정: 동적 카테고리 > 설정 카테고리 (메모이제이션)
|
||||
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<SelectBasicComponentProps> = ({
|
|||
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<SelectBasicComponentProps> = ({
|
|||
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<SelectBasicComponentProps> = ({
|
|||
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<SelectBasicComponentProps> = ({
|
|||
|
||||
// 인터랙티브 모드에서 폼 데이터 업데이트 (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<SelectBasicComponentProps> = ({
|
|||
// 모든 옵션 가져오기
|
||||
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];
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue