feature/screen-management #199

Merged
kjs merged 16 commits from feature/screen-management into main 2025-11-11 18:33:41 +09:00
14 changed files with 226 additions and 125 deletions
Showing only changes of commit 6534d03ecd - Show all commits

View File

@ -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 });
}
});

View File

@ -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,

View File

@ -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); // 화면 정보만 수정

View File

@ -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);

View File

@ -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("🔍 채번 규칙 쿼리 실행", {

View File

@ -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 )
*/

View File

@ -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}`}>

View File

@ -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 타입은 공통코드 선택박스로 처리

View File

@ -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}
// 설정 변경 핸들러 (테이블 페이지 크기 등 설정을 상세설정에 반영)

View File

@ -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를 업데이트

View File

@ -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;
}

View File

@ -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);

View File

@ -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>
);

View File

@ -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];
};