우측화면 데이터 필터링 수정

This commit is contained in:
kjs 2025-12-16 11:49:10 +09:00
parent 4e74c7b5ba
commit d8329d31e4
12 changed files with 128 additions and 378 deletions

View File

@ -76,7 +76,6 @@ export const EmbeddedScreen = forwardRef<EmbeddedScreenHandle, EmbeddedScreenPro
// 필드 값 변경 핸들러 // 필드 값 변경 핸들러
const handleFieldChange = useCallback((fieldName: string, value: any) => { const handleFieldChange = useCallback((fieldName: string, value: any) => {
console.log("📝 [EmbeddedScreen] 필드 값 변경:", { fieldName, value });
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
[fieldName]: value, [fieldName]: value,
@ -88,10 +87,9 @@ export const EmbeddedScreen = forwardRef<EmbeddedScreenHandle, EmbeddedScreenPro
loadScreenData(); loadScreenData();
}, [embedding.childScreenId]); }, [embedding.childScreenId]);
// 🆕 initialFormData 변경 시 formData 업데이트 (수정 모드) // initialFormData 변경 시 formData 업데이트 (수정 모드)
useEffect(() => { useEffect(() => {
if (initialFormData && Object.keys(initialFormData).length > 0) { if (initialFormData && Object.keys(initialFormData).length > 0) {
console.log("📝 [EmbeddedScreen] 초기 폼 데이터 설정:", initialFormData);
setFormData(initialFormData); setFormData(initialFormData);
} }
}, [initialFormData]); }, [initialFormData]);
@ -135,12 +133,6 @@ export const EmbeddedScreen = forwardRef<EmbeddedScreenHandle, EmbeddedScreenPro
}); });
} }
console.log("🔗 [EmbeddedScreen] 우측 폼 데이터 교체:", {
allColumnNames,
selectedLeftDataKeys: selectedLeftData ? Object.keys(selectedLeftData) : [],
initializedFormDataKeys: Object.keys(initializedFormData),
});
setFormData(initializedFormData); setFormData(initializedFormData);
setFormDataVersion((v) => v + 1); // 🆕 버전 증가로 컴포넌트 강제 리렌더링 setFormDataVersion((v) => v + 1); // 🆕 버전 증가로 컴포넌트 강제 리렌더링
}, [position, splitPanelContext, selectedLeftData, layout]); }, [position, splitPanelContext, selectedLeftData, layout]);
@ -160,13 +152,6 @@ export const EmbeddedScreen = forwardRef<EmbeddedScreenHandle, EmbeddedScreenPro
// 화면 정보 로드 (screenApi.getScreen은 직접 ScreenDefinition 객체를 반환) // 화면 정보 로드 (screenApi.getScreen은 직접 ScreenDefinition 객체를 반환)
const screenData = await screenApi.getScreen(embedding.childScreenId); const screenData = await screenApi.getScreen(embedding.childScreenId);
console.log("📋 [EmbeddedScreen] 화면 정보 API 응답:", {
screenId: embedding.childScreenId,
hasData: !!screenData,
tableName: screenData?.tableName,
screenName: screenData?.name || screenData?.screenName,
position,
});
if (screenData) { if (screenData) {
setScreenInfo(screenData); setScreenInfo(screenData);
} else { } else {

View File

@ -27,29 +27,12 @@ export function ScreenSplitPanel({ screenId, config, initialFormData }: ScreenSp
// config에서 splitRatio 추출 (기본값 50) // config에서 splitRatio 추출 (기본값 50)
const configSplitRatio = config?.splitRatio ?? 50; const configSplitRatio = config?.splitRatio ?? 50;
console.log("🎯 [ScreenSplitPanel] 렌더링됨!", {
screenId,
config,
leftScreenId: config?.leftScreenId,
rightScreenId: config?.rightScreenId,
configSplitRatio,
parentDataMapping: config?.parentDataMapping,
configKeys: config ? Object.keys(config) : [],
});
// 🆕 initialFormData 별도 로그 (명확한 확인)
console.log("📝 [ScreenSplitPanel] initialFormData 확인:", {
hasInitialFormData: !!initialFormData,
initialFormDataKeys: initialFormData ? Object.keys(initialFormData) : [],
initialFormData: initialFormData,
});
// 드래그로 조절 가능한 splitRatio 상태 // 드래그로 조절 가능한 splitRatio 상태
const [splitRatio, setSplitRatio] = useState(configSplitRatio); const [splitRatio, setSplitRatio] = useState(configSplitRatio);
// config.splitRatio가 변경되면 동기화 (설정 패널에서 변경 시) // config.splitRatio가 변경되면 동기화 (설정 패널에서 변경 시)
React.useEffect(() => { React.useEffect(() => {
console.log("📐 [ScreenSplitPanel] splitRatio 동기화:", { configSplitRatio, currentSplitRatio: splitRatio });
setSplitRatio(configSplitRatio); setSplitRatio(configSplitRatio);
}, [configSplitRatio]); }, [configSplitRatio]);

View File

@ -25,12 +25,6 @@ export function TabsWidget({ component, className, style, menuObjid }: TabsWidge
persistSelection = false, persistSelection = false,
} = component; } = component;
console.log("🎨 TabsWidget 렌더링:", {
componentId: component.id,
tabs,
tabsLength: tabs.length,
component,
});
const storageKey = `tabs-${component.id}-selected`; const storageKey = `tabs-${component.id}-selected`;
@ -67,15 +61,7 @@ export function TabsWidget({ component, className, style, menuObjid }: TabsWidge
// 초기 로드 시 선택된 탭의 화면 불러오기 // 초기 로드 시 선택된 탭의 화면 불러오기
useEffect(() => { useEffect(() => {
const currentTab = visibleTabs.find((t) => t.id === selectedTab); const currentTab = visibleTabs.find((t) => t.id === selectedTab);
console.log("🔄 초기 탭 로드:", {
selectedTab,
currentTab,
hasScreenId: !!currentTab?.screenId,
screenId: currentTab?.screenId,
});
if (currentTab && currentTab.screenId && !screenLayouts[currentTab.screenId]) { if (currentTab && currentTab.screenId && !screenLayouts[currentTab.screenId]) {
console.log("📥 초기 화면 로딩 시작:", currentTab.screenId);
loadScreenLayout(currentTab.screenId); loadScreenLayout(currentTab.screenId);
} }
}, [selectedTab, visibleTabs]); }, [selectedTab, visibleTabs]);
@ -83,26 +69,20 @@ export function TabsWidget({ component, className, style, menuObjid }: TabsWidge
// 화면 레이아웃 로드 // 화면 레이아웃 로드
const loadScreenLayout = async (screenId: number) => { const loadScreenLayout = async (screenId: number) => {
if (screenLayouts[screenId]) { if (screenLayouts[screenId]) {
console.log("✅ 이미 로드된 화면:", screenId);
return; // 이미 로드됨 return; // 이미 로드됨
} }
console.log("📥 화면 레이아웃 로딩 시작:", screenId);
setLoadingScreens((prev) => ({ ...prev, [screenId]: true })); setLoadingScreens((prev) => ({ ...prev, [screenId]: true }));
try { try {
const { apiClient } = await import("@/lib/api/client"); const { apiClient } = await import("@/lib/api/client");
const response = await apiClient.get(`/screen-management/screens/${screenId}/layout`); const response = await apiClient.get(`/screen-management/screens/${screenId}/layout`);
console.log("📦 API 응답:", { screenId, success: response.data.success, hasData: !!response.data.data });
if (response.data.success && response.data.data) { if (response.data.success && response.data.data) {
console.log("✅ 화면 레이아웃 로드 완료:", screenId);
setScreenLayouts((prev) => ({ ...prev, [screenId]: response.data.data })); setScreenLayouts((prev) => ({ ...prev, [screenId]: response.data.data }));
} else {
console.error("❌ 화면 레이아웃 로드 실패 - success false");
} }
} catch (error) { } catch (error) {
console.error(`화면 레이아웃 로드 실패 ${screenId}:`, error); console.error(`화면 레이아웃 로드 실패 ${screenId}:`, error);
} finally { } finally {
setLoadingScreens((prev) => ({ ...prev, [screenId]: false })); setLoadingScreens((prev) => ({ ...prev, [screenId]: false }));
} }
@ -110,10 +90,9 @@ export function TabsWidget({ component, className, style, menuObjid }: TabsWidge
// 탭 변경 핸들러 // 탭 변경 핸들러
const handleTabChange = (tabId: string) => { const handleTabChange = (tabId: string) => {
console.log("🔄 탭 변경:", tabId);
setSelectedTab(tabId); setSelectedTab(tabId);
// 🆕 마운트된 탭 목록에 추가 (한 번 마운트되면 유지) // 마운트된 탭 목록에 추가 (한 번 마운트되면 유지)
setMountedTabs(prev => { setMountedTabs(prev => {
if (prev.has(tabId)) return prev; if (prev.has(tabId)) return prev;
const newSet = new Set(prev); const newSet = new Set(prev);
@ -123,10 +102,7 @@ export function TabsWidget({ component, className, style, menuObjid }: TabsWidge
// 해당 탭의 화면 로드 // 해당 탭의 화면 로드
const tab = visibleTabs.find((t) => t.id === tabId); const tab = visibleTabs.find((t) => t.id === tabId);
console.log("🔍 선택된 탭 정보:", { tab, hasScreenId: !!tab?.screenId, screenId: tab?.screenId });
if (tab && tab.screenId && !screenLayouts[tab.screenId]) { if (tab && tab.screenId && !screenLayouts[tab.screenId]) {
console.log("📥 탭 변경 시 화면 로딩:", tab.screenId);
loadScreenLayout(tab.screenId); loadScreenLayout(tab.screenId);
} }
}; };
@ -157,7 +133,6 @@ export function TabsWidget({ component, className, style, menuObjid }: TabsWidge
}; };
if (visibleTabs.length === 0) { if (visibleTabs.length === 0) {
console.log("⚠️ 보이는 탭이 없음");
return ( return (
<div className="flex h-full w-full items-center justify-center rounded border-2 border-dashed border-gray-300 bg-gray-50"> <div className="flex h-full w-full items-center justify-center rounded border-2 border-dashed border-gray-300 bg-gray-50">
<p className="text-muted-foreground text-sm"> </p> <p className="text-muted-foreground text-sm"> </p>
@ -165,13 +140,6 @@ export function TabsWidget({ component, className, style, menuObjid }: TabsWidge
); );
} }
console.log("🎨 TabsWidget 최종 렌더링:", {
visibleTabsCount: visibleTabs.length,
selectedTab,
screenLayoutsKeys: Object.keys(screenLayouts),
loadingScreensKeys: Object.keys(loadingScreens),
});
return ( return (
<div className="flex h-full w-full flex-col pt-4" style={style}> <div className="flex h-full w-full flex-col pt-4" style={style}>
<Tabs <Tabs
@ -233,14 +201,6 @@ export function TabsWidget({ component, className, style, menuObjid }: TabsWidge
const layoutData = screenLayouts[tab.screenId]; const layoutData = screenLayouts[tab.screenId];
const { components = [], screenResolution } = layoutData; const { components = [], screenResolution } = layoutData;
// 비활성 탭은 로그 생략
if (isActive) {
console.log("🎯 렌더링할 화면 데이터:", {
screenId: tab.screenId,
componentsCount: components.length,
screenResolution,
});
}
const designWidth = screenResolution?.width || 1920; const designWidth = screenResolution?.width || 1920;
const designHeight = screenResolution?.height || 1080; const designHeight = screenResolution?.height || 1080;

View File

@ -282,10 +282,6 @@ export function SplitPanelProvider({
* 🆕 * 🆕
*/ */
const handleSetSelectedLeftData = useCallback((data: Record<string, any> | null) => { const handleSetSelectedLeftData = useCallback((data: Record<string, any> | null) => {
logger.info(`[SplitPanelContext] 좌측 선택 데이터 설정:`, {
hasData: !!data,
dataKeys: data ? Object.keys(data) : [],
});
setSelectedLeftData(data); setSelectedLeftData(data);
}, []); }, []);
@ -323,11 +319,6 @@ export function SplitPanelProvider({
} }
} }
logger.info(`[SplitPanelContext] 매핑된 부모 데이터 (자동+명시적):`, {
autoMappedKeys: Object.keys(selectedLeftData),
explicitMappings: parentDataMapping.length,
finalKeys: Object.keys(mappedData),
});
return mappedData; return mappedData;
}, [selectedLeftData, parentDataMapping]); }, [selectedLeftData, parentDataMapping]);
@ -350,7 +341,6 @@ export function SplitPanelProvider({
} }
} }
logger.info(`[SplitPanelContext] 연결 필터 값:`, filterValues);
return filterValues; return filterValues;
}, [selectedLeftData, linkedFilters]); }, [selectedLeftData, linkedFilters]);

View File

@ -83,14 +83,8 @@ export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({
const updatedTable = { ...table, dataCount: count }; const updatedTable = { ...table, dataCount: count };
const newMap = new Map(prev); const newMap = new Map(prev);
newMap.set(tableId, updatedTable); newMap.set(tableId, updatedTable);
console.log("🔄 [TableOptionsContext] 데이터 건수 업데이트:", {
tableId,
count,
updated: true,
});
return newMap; return newMap;
} }
console.warn("⚠️ [TableOptionsContext] 테이블을 찾을 수 없음:", tableId);
return prev; return prev;
}); });
}, []); }, []);

View File

@ -226,43 +226,6 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
// 1. 새 컴포넌트 시스템에서 먼저 조회 // 1. 새 컴포넌트 시스템에서 먼저 조회
const newComponent = ComponentRegistry.getComponent(componentType); const newComponent = ComponentRegistry.getComponent(componentType);
// 🔍 디버깅: screen-split-panel 조회 결과 확인
if (componentType === "screen-split-panel") {
console.log("🔍 [DynamicComponentRenderer] screen-split-panel 조회:", {
componentType,
found: !!newComponent,
componentId: component.id,
componentConfig: component.componentConfig,
hasFormData: !!props.formData,
formDataKeys: props.formData ? Object.keys(props.formData) : [],
registeredComponents: ComponentRegistry.getAllComponents().map(c => c.id),
});
}
// 🔍 디버깅: select-basic 조회 결과 확인
if (componentType === "select-basic") {
console.log("🔍 [DynamicComponentRenderer] select-basic 조회:", {
componentType,
found: !!newComponent,
componentId: component.id,
componentConfig: component.componentConfig,
});
}
// 🔍 디버깅: text-input 컴포넌트 조회 결과 확인
if (componentType === "text-input" || component.id?.includes("text") || (component as any).webType === "text") {
console.log("🔍 [DynamicComponentRenderer] text-input 조회:", {
componentType,
componentId: component.id,
componentLabel: component.label,
componentConfig: component.componentConfig,
webTypeConfig: (component as any).webTypeConfig,
autoGeneration: (component as any).autoGeneration,
found: !!newComponent,
registeredComponents: ComponentRegistry.getAllComponents().map(c => c.id),
});
}
if (newComponent) { if (newComponent) {
// 새 컴포넌트 시스템으로 렌더링 // 새 컴포넌트 시스템으로 렌더링
try { try {
@ -324,19 +287,6 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
currentValue = formData?.[fieldName] || ""; currentValue = formData?.[fieldName] || "";
} }
// 🆕 디버깅: text-input 값 추출 확인
if (componentType === "text-input" && formData && Object.keys(formData).length > 0) {
console.log("🔍 [DynamicComponentRenderer] text-input 값 추출:", {
componentId: component.id,
componentLabel: component.label,
columnName: (component as any).columnName,
fieldName,
currentValue,
hasFormData: !!formData,
formDataKeys: Object.keys(formData).slice(0, 10), // 처음 10개만
});
}
// onChange 핸들러 - 컴포넌트 타입에 따라 다르게 처리 // onChange 핸들러 - 컴포넌트 타입에 따라 다르게 처리
const handleChange = (value: any) => { const handleChange = (value: any) => {
// autocomplete-search-input, entity-search-input은 자체적으로 onFormDataChange를 호출하므로 중복 저장 방지 // autocomplete-search-input, entity-search-input은 자체적으로 onFormDataChange를 호출하므로 중복 저장 방지

View File

@ -388,16 +388,6 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
}; };
} }
// 🔍 디버깅: processedConfig.action 확인
console.log("[ButtonPrimaryComponent] processedConfig.action 생성 완료", {
actionType: processedConfig.action?.type,
enableDataflowControl: processedConfig.action?.enableDataflowControl,
dataflowTiming: processedConfig.action?.dataflowTiming,
dataflowConfig: processedConfig.action?.dataflowConfig,
webTypeConfigRaw: component.webTypeConfig,
componentText: component.text,
});
// 스타일 계산 // 스타일 계산
// height: 100%로 부모(RealtimePreviewDynamic의 내부 div)의 높이를 따라감 // height: 100%로 부모(RealtimePreviewDynamic의 내부 div)의 높이를 따라감
// width는 항상 100%로 고정 (부모 컨테이너가 gridColumns로 크기 제어) // width는 항상 100%로 고정 (부모 컨테이너가 gridColumns로 크기 제어)
@ -839,10 +829,6 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
groupedData.length > 0 groupedData.length > 0
) { ) {
effectiveSelectedRowsData = groupedData; effectiveSelectedRowsData = groupedData;
console.log("🔗 [ButtonPrimaryComponent] groupedData에서 부모창 데이터 가져옴:", {
count: groupedData.length,
data: groupedData,
});
} }
// modalDataStore에서 선택된 데이터 가져오기 (분할 패널 등에서 선택한 데이터) // modalDataStore에서 선택된 데이터 가져오기 (분할 패널 등에서 선택한 데이터)
@ -858,12 +844,6 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// originalData가 있으면 그것을 사용, 없으면 item 자체 사용 (하위 호환성) // originalData가 있으면 그것을 사용, 없으면 item 자체 사용 (하위 호환성)
return item.originalData || item; return item.originalData || item;
}); });
console.log("🔗 [ButtonPrimaryComponent] modalDataStore에서 선택된 데이터 가져옴:", {
tableName: effectiveTableName,
count: modalData.length,
rawData: modalData,
extractedData: effectiveSelectedRowsData,
});
} }
} catch (error) { } catch (error) {
console.warn("modalDataStore 접근 실패:", error); console.warn("modalDataStore 접근 실패:", error);
@ -928,17 +908,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
} }
} }
// 🆕 디버깅: tableName 확인 // 분할 패널 부모 데이터 가져오기 (우측 화면에서 저장 시 좌측 선택 데이터 포함)
console.log("🔍 [ButtonPrimaryComponent] context 생성:", {
propsTableName: tableName,
contextTableName: screenContext?.tableName,
effectiveTableName,
propsScreenId: screenId,
contextScreenId: screenContext?.screenId,
effectiveScreenId,
});
// 🆕 분할 패널 부모 데이터 가져오기 (우측 화면에서 저장 시 좌측 선택 데이터 포함)
// 조건 완화: splitPanelContext가 있고 selectedLeftData가 있으면 가져옴 // 조건 완화: splitPanelContext가 있고 selectedLeftData가 있으면 가져옴
// (탭 안에서도 분할 패널 컨텍스트에 접근 가능하도록) // (탭 안에서도 분할 패널 컨텍스트에 접근 가능하도록)
let splitPanelParentData: Record<string, any> | undefined; let splitPanelParentData: Record<string, any> | undefined;
@ -947,13 +917,6 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// 좌측 화면이 아닌 경우에만 부모 데이터 포함 (좌측에서 저장 시 자신의 데이터를 부모로 포함하면 안됨) // 좌측 화면이 아닌 경우에만 부모 데이터 포함 (좌측에서 저장 시 자신의 데이터를 부모로 포함하면 안됨)
if (splitPanelPosition !== "left") { if (splitPanelPosition !== "left") {
splitPanelParentData = splitPanelContext.getMappedParentData(); splitPanelParentData = splitPanelContext.getMappedParentData();
if (Object.keys(splitPanelParentData).length > 0) {
console.log("🔗 [ButtonPrimaryComponent] 분할 패널 부모 데이터 포함:", {
splitPanelParentData,
splitPanelPosition,
isInTab: !splitPanelPosition, // splitPanelPosition이 없으면 탭 안
});
}
} }
} }
@ -966,22 +929,11 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// (일반 폼 필드는 props.formData, RepeaterFieldGroup은 screenContext.formData에 있음) // (일반 폼 필드는 props.formData, RepeaterFieldGroup은 screenContext.formData에 있음)
let effectiveFormData = { ...propsFormData, ...screenContextFormData }; let effectiveFormData = { ...propsFormData, ...screenContextFormData };
// 🆕 분할 패널 우측이고 formData가 비어있으면 splitPanelParentData 사용 // 분할 패널 우측이고 formData가 비어있으면 splitPanelParentData 사용
if (splitPanelPosition === "right" && Object.keys(effectiveFormData).length === 0 && splitPanelParentData) { if (splitPanelPosition === "right" && Object.keys(effectiveFormData).length === 0 && splitPanelParentData) {
effectiveFormData = { ...splitPanelParentData }; effectiveFormData = { ...splitPanelParentData };
console.log("🔍 [ButtonPrimary] 분할 패널 우측 - splitPanelParentData 사용:", Object.keys(effectiveFormData));
} }
console.log("🔍 [ButtonPrimary] formData 선택:", {
hasScreenContextFormData: Object.keys(screenContextFormData).length > 0,
screenContextKeys: Object.keys(screenContextFormData),
hasPropsFormData: Object.keys(propsFormData).length > 0,
propsFormDataKeys: Object.keys(propsFormData),
hasSplitPanelParentData: !!splitPanelParentData && Object.keys(splitPanelParentData).length > 0,
splitPanelPosition,
effectiveFormDataKeys: Object.keys(effectiveFormData),
});
const context: ButtonActionContext = { const context: ButtonActionContext = {
formData: effectiveFormData, formData: effectiveFormData,
originalData: originalData, // 🔧 빈 객체 대신 undefined 유지 (UPDATE 판단에 사용) originalData: originalData, // 🔧 빈 객체 대신 undefined 유지 (UPDATE 판단에 사용)

View File

@ -61,20 +61,17 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
// 테이블 데이터 상태 관리 // 테이블 데이터 상태 관리
const [loadedTableData, setLoadedTableData] = useState<any[]>([]); const [loadedTableData, setLoadedTableData] = useState<any[]>([]);
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]); const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(true); // 초기 로딩 상태를 true로 설정
const [initialLoadDone, setInitialLoadDone] = useState(false); // 초기 로드 완료 여부
const [hasEverSelectedLeftData, setHasEverSelectedLeftData] = useState(false); // 좌측 데이터 선택 이력
// 필터 상태 (검색 필터 위젯에서 전달받은 필터) // 필터 상태 (검색 필터 위젯에서 전달받은 필터)
const [filters, setFiltersInternal] = useState<TableFilter[]>([]); const [filters, setFiltersInternal] = useState<TableFilter[]>([]);
// 필터 상태 변경 래퍼 (로깅용) // 필터 상태 변경 래퍼
const setFilters = useCallback((newFilters: TableFilter[]) => { const setFilters = useCallback((newFilters: TableFilter[]) => {
console.log("🎴 [CardDisplay] setFilters 호출됨:", {
componentId: component.id,
filtersCount: newFilters.length,
filters: newFilters,
});
setFiltersInternal(newFilters); setFiltersInternal(newFilters);
}, [component.id]); }, []);
// 카테고리 매핑 상태 (카테고리 코드 -> 라벨/색상) // 카테고리 매핑 상태 (카테고리 코드 -> 라벨/색상)
const [columnMeta, setColumnMeta] = useState< const [columnMeta, setColumnMeta] = useState<
@ -125,10 +122,6 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
// 삭제할 데이터를 배열로 감싸기 (API가 배열을 기대함) // 삭제할 데이터를 배열로 감싸기 (API가 배열을 기대함)
const deleteData = [data]; const deleteData = [data];
console.log("🗑️ [CardDisplay] 삭제 요청:", {
tableName: tableNameToUse,
data: deleteData,
});
// API 호출로 데이터 삭제 (POST 방식으로 변경 - DELETE는 body 전달이 불안정) // API 호출로 데이터 삭제 (POST 방식으로 변경 - DELETE는 body 전달이 불안정)
// 백엔드 API는 DELETE /api/table-management/tables/:tableName/delete 이지만 // 백엔드 API는 DELETE /api/table-management/tables/:tableName/delete 이지만
@ -143,7 +136,6 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
}); });
if (response.data.success) { if (response.data.success) {
console.log("삭제 완료:", response.data.data?.deletedCount || 1, "건");
alert("삭제되었습니다."); alert("삭제되었습니다.");
// 로컬 상태에서 삭제된 항목 제거 // 로컬 상태에서 삭제된 항목 제거
@ -157,11 +149,9 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
setSelectedRows(newSelectedRows); setSelectedRows(newSelectedRows);
} }
} else { } else {
console.error("삭제 실패:", response.data.error);
alert(`삭제 실패: ${response.data.message || response.data.error || "알 수 없는 오류"}`); alert(`삭제 실패: ${response.data.message || response.data.error || "알 수 없는 오류"}`);
} }
} catch (error: any) { } catch (error: any) {
console.error("삭제 중 오류 발생:", error);
const errorMessage = error.response?.data?.message || error.message || "알 수 없는 오류"; const errorMessage = error.response?.data?.message || error.message || "알 수 없는 오류";
alert(`삭제 중 오류가 발생했습니다: ${errorMessage}`); alert(`삭제 중 오류가 발생했습니다: ${errorMessage}`);
} }
@ -194,8 +184,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
// loadTableData(); // loadTableData();
} catch (error) { } catch (error) {
console.error("❌ 편집 저장 실패:", error); alert("저장에 실패했습니다.");
alert("❌ 저장에 실패했습니다.");
} }
}; };
@ -204,6 +193,25 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
const loadTableData = async () => { const loadTableData = async () => {
// 디자인 모드에서는 테이블 데이터를 로드하지 않음 // 디자인 모드에서는 테이블 데이터를 로드하지 않음
if (isDesignMode) { if (isDesignMode) {
setLoading(false);
setInitialLoadDone(true);
return;
}
// 우측 패널인 경우, 좌측 데이터가 선택되지 않으면 데이터 로드하지 않음 (깜빡임 방지)
// splitPanelPosition이 "right"이면 분할 패널 내부이므로 연결 필터가 있을 가능성이 높음
const isRightPanelEarly = splitPanelPosition === "right";
const hasSelectedLeftDataEarly = splitPanelContext?.selectedLeftData &&
Object.keys(splitPanelContext.selectedLeftData).length > 0;
if (isRightPanelEarly && !hasSelectedLeftDataEarly) {
// 우측 패널이고 좌측 데이터가 선택되지 않은 경우 - 기존 데이터 유지 (깜빡임 방지)
// 초기 로드가 아닌 경우에는 데이터를 지우지 않음
if (!initialLoadDone) {
setLoadedTableData([]);
}
setLoading(false);
setInitialLoadDone(true);
return; return;
} }
@ -211,6 +219,8 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
const tableNameToUse = tableName || component.componentConfig?.tableName || 'user_info'; // 기본 테이블명 설정 const tableNameToUse = tableName || component.componentConfig?.tableName || 'user_info'; // 기본 테이블명 설정
if (!tableNameToUse) { if (!tableNameToUse) {
setLoading(false);
setInitialLoadDone(true);
return; return;
} }
@ -251,19 +261,23 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
} }
linkedFilterValues = tableSpecificFilters; linkedFilterValues = tableSpecificFilters;
console.log("🎴 [CardDisplay] 연결 필터 확인:", {
tableNameToUse,
hasLinkedFiltersConfigured,
hasSelectedLeftData,
linkedFilterValues,
});
} }
// 연결 필터가 설정되어 있지만 좌측에서 데이터가 선택되지 않은 경우 빈 데이터 표시 // 우측 패널이고 연결 필터가 설정되어 있지만 좌측에서 데이터가 선택되지 않은 경우 빈 데이터 표시
if (splitPanelContext && hasLinkedFiltersConfigured && !hasSelectedLeftData) { // 또는 우측 패널이고 linkedFilters 설정이 있으면 좌측 선택 필수
console.log("🎴 [CardDisplay] 연결 필터 활성화됨 - 좌측 선택 대기"); // splitPanelPosition은 screenContext에서 가져오거나, splitPanelContext에서 screenId로 확인
const isRightPanelFromContext = splitPanelPosition === "right";
const isRightPanelFromSplitContext = screenId && splitPanelContext?.getPositionByScreenId
? splitPanelContext.getPositionByScreenId(screenId as number) === "right"
: false;
const isRightPanel = isRightPanelFromContext || isRightPanelFromSplitContext;
const hasLinkedFiltersInConfig = splitPanelContext?.linkedFilters && splitPanelContext.linkedFilters.length > 0;
if (isRightPanel && (hasLinkedFiltersConfigured || hasLinkedFiltersInConfig) && !hasSelectedLeftData) {
setLoadedTableData([]); setLoadedTableData([]);
setLoading(false); setLoading(false);
setInitialLoadDone(true);
return; return;
} }
@ -277,7 +291,6 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
search: Object.keys(linkedFilterValues).length > 0 ? linkedFilterValues : undefined, search: Object.keys(linkedFilterValues).length > 0 ? linkedFilterValues : undefined,
}; };
console.log("🎴 [CardDisplay] API 호출 파라미터:", apiParams);
// 테이블 데이터, 컬럼 정보, 입력 타입 정보를 병렬로 로드 // 테이블 데이터, 컬럼 정보, 입력 타입 정보를 병렬로 로드
const [dataResponse, columnsResponse, inputTypesResponse] = await Promise.all([ const [dataResponse, columnsResponse, inputTypesResponse] = await Promise.all([
@ -298,7 +311,6 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
codeCategory: item.codeCategory || item.code_category, codeCategory: item.codeCategory || item.code_category,
}; };
}); });
console.log("📋 [CardDisplay] 컬럼 메타 정보:", meta);
setColumnMeta(meta); setColumnMeta(meta);
// 카테고리 타입 컬럼 찾기 및 매핑 로드 // 카테고리 타입 컬럼 찾기 및 매핑 로드
@ -306,17 +318,14 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
.filter(([_, m]) => m.inputType === "category") .filter(([_, m]) => m.inputType === "category")
.map(([columnName]) => columnName); .map(([columnName]) => columnName);
console.log("📋 [CardDisplay] 카테고리 컬럼:", categoryColumns);
if (categoryColumns.length > 0) { if (categoryColumns.length > 0) {
const mappings: Record<string, Record<string, { label: string; color?: string }>> = {}; const mappings: Record<string, Record<string, { label: string; color?: string }>> = {};
for (const columnName of categoryColumns) { for (const columnName of categoryColumns) {
try { try {
console.log(`📋 [CardDisplay] 카테고리 매핑 로드 시작: ${tableNameToUse}/${columnName}`);
const response = await apiClient.get(`/table-categories/${tableNameToUse}/${columnName}/values`); const response = await apiClient.get(`/table-categories/${tableNameToUse}/${columnName}/values`);
console.log(`📋 [CardDisplay] 카테고리 API 응답 [${columnName}]:`, response.data);
if (response.data.success && response.data.data) { if (response.data.success && response.data.data) {
const mapping: Record<string, { label: string; color?: string }> = {}; const mapping: Record<string, { label: string; color?: string }> = {};
@ -328,29 +337,27 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
const rawColor = item.color ?? item.badge_color; const rawColor = item.color ?? item.badge_color;
const color = (rawColor && rawColor !== "none") ? rawColor : undefined; const color = (rawColor && rawColor !== "none") ? rawColor : undefined;
mapping[code] = { label, color }; mapping[code] = { label, color };
console.log(`📋 [CardDisplay] 매핑 추가: ${code} -> ${label} (color: ${color})`);
}); });
mappings[columnName] = mapping; mappings[columnName] = mapping;
} }
} catch (error) { } catch (error) {
console.error(`❌ CardDisplay: 카테고리 매핑 로드 실패 [${columnName}]`, error); // 카테고리 매핑 로드 실패 시 무시
} }
} }
console.log("📋 [CardDisplay] 최종 카테고리 매핑:", mappings);
setCategoryMappings(mappings); setCategoryMappings(mappings);
} }
} catch (error) { } catch (error) {
console.error(`❌ CardDisplay: 데이터 로딩 실패`, error);
setLoadedTableData([]); setLoadedTableData([]);
setLoadedTableColumns([]); setLoadedTableColumns([]);
} finally { } finally {
setLoading(false); setLoading(false);
setInitialLoadDone(true);
} }
}; };
loadTableData(); loadTableData();
}, [isDesignMode, tableName, component.componentConfig?.tableName, splitPanelContext?.selectedLeftData]); }, [isDesignMode, tableName, component.componentConfig?.tableName, splitPanelContext?.selectedLeftData, splitPanelPosition]);
// 컴포넌트 설정 (기본값 보장) // 컴포넌트 설정 (기본값 보장)
const componentConfig = { const componentConfig = {
@ -390,8 +397,34 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
componentStyle.borderColor = isSelected ? "hsl(var(--ring))" : "hsl(var(--border))"; componentStyle.borderColor = isSelected ? "hsl(var(--ring))" : "hsl(var(--border))";
} }
// 우측 패널 + 좌측 미선택 상태 체크를 위한 값들 (displayData 외부에서 계산)
const isRightPanelForDisplay = splitPanelPosition === "right" ||
(screenId && splitPanelContext?.getPositionByScreenId?.(screenId as number) === "right");
const hasLinkedFiltersForDisplay = splitPanelContext?.linkedFilters && splitPanelContext.linkedFilters.length > 0;
const selectedLeftDataForDisplay = splitPanelContext?.selectedLeftData;
const hasSelectedLeftDataForDisplay = selectedLeftDataForDisplay &&
Object.keys(selectedLeftDataForDisplay).length > 0;
// 좌측 데이터가 한 번이라도 선택된 적이 있으면 기록
useEffect(() => {
if (hasSelectedLeftDataForDisplay) {
setHasEverSelectedLeftData(true);
}
}, [hasSelectedLeftDataForDisplay]);
// 우측 패널이고 연결 필터가 있고, 좌측 데이터가 한 번도 선택된 적이 없는 경우에만 "선택해주세요" 표시
// 한 번이라도 선택된 적이 있으면 깜빡임 방지를 위해 기존 데이터 유지
const shouldHideDataForRightPanel = isRightPanelForDisplay &&
!hasEverSelectedLeftData &&
!hasSelectedLeftDataForDisplay;
// 표시할 데이터 결정 (로드된 테이블 데이터 우선 사용) // 표시할 데이터 결정 (로드된 테이블 데이터 우선 사용)
const displayData = useMemo(() => { const displayData = useMemo(() => {
// 우측 패널이고 linkedFilters가 설정되어 있지만 좌측 데이터가 선택되지 않은 경우 빈 배열 반환
if (shouldHideDataForRightPanel) {
return [];
}
// 로드된 테이블 데이터가 있으면 항상 우선 사용 (dataSource 설정 무시) // 로드된 테이블 데이터가 있으면 항상 우선 사용 (dataSource 설정 무시)
if (loadedTableData.length > 0) { if (loadedTableData.length > 0) {
return loadedTableData; return loadedTableData;
@ -408,7 +441,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
// 데이터가 없으면 빈 배열 반환 // 데이터가 없으면 빈 배열 반환
return []; return [];
}, [componentConfig.dataSource, loadedTableData, tableData, componentConfig.staticData]); }, [shouldHideDataForRightPanel, loadedTableData, tableData, componentConfig.staticData]);
// 실제 사용할 테이블 컬럼 정보 (로드된 컬럼 우선 사용) // 실제 사용할 테이블 컬럼 정보 (로드된 컬럼 우선 사용)
const actualTableColumns = loadedTableColumns.length > 0 ? loadedTableColumns : tableColumns; const actualTableColumns = loadedTableColumns.length > 0 ? loadedTableColumns : tableColumns;
@ -453,13 +486,8 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
additionalData: {}, additionalData: {},
})); }));
useModalDataStore.getState().setData(tableNameToUse, modalItems); useModalDataStore.getState().setData(tableNameToUse, modalItems);
console.log("[CardDisplay] modalDataStore에 데이터 저장:", {
dataSourceId: tableNameToUse,
count: modalItems.length,
});
} else if (tableNameToUse && selectedRowsData.length === 0) { } else if (tableNameToUse && selectedRowsData.length === 0) {
useModalDataStore.getState().clearData(tableNameToUse); useModalDataStore.getState().clearData(tableNameToUse);
console.log("[CardDisplay] modalDataStore 데이터 제거:", tableNameToUse);
} }
// 분할 패널 컨텍스트에 선택된 데이터 저장 (좌측 화면인 경우) // 분할 패널 컨텍스트에 선택된 데이터 저장 (좌측 화면인 경우)
@ -467,13 +495,8 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
if (splitPanelContext && splitPanelPosition === "left" && !splitPanelContext.disableAutoDataTransfer) { if (splitPanelContext && splitPanelPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
if (checked) { if (checked) {
splitPanelContext.setSelectedLeftData(data); splitPanelContext.setSelectedLeftData(data);
console.log("[CardDisplay] 분할 패널 좌측 데이터 저장:", {
data,
parentDataMapping: splitPanelContext.parentDataMapping,
});
} else { } else {
splitPanelContext.setSelectedLeftData(null); splitPanelContext.setSelectedLeftData(null);
console.log("[CardDisplay] 분할 패널 좌측 데이터 초기화");
} }
} }
}, [displayData, getCardKey, onFormDataChange, componentConfig.dataSource?.tableName, tableName, splitPanelContext, splitPanelPosition]); }, [displayData, getCardKey, onFormDataChange, componentConfig.dataSource?.tableName, tableName, splitPanelContext, splitPanelPosition]);
@ -540,21 +563,38 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
}, [categoryMappings]); }, [categoryMappings]);
// 필터가 변경되면 데이터 다시 로드 (테이블 리스트와 동일한 패턴) // 필터가 변경되면 데이터 다시 로드 (테이블 리스트와 동일한 패턴)
// 초기 로드 여부 추적 // 초기 로드 여부 추적 - 마운트 카운터 사용 (Strict Mode 대응)
const isInitialLoadRef = useRef(true); const mountCountRef = useRef(0);
useEffect(() => { useEffect(() => {
mountCountRef.current += 1;
const currentMount = mountCountRef.current;
if (!tableNameToUse || isDesignMode) return; if (!tableNameToUse || isDesignMode) return;
// 초기 로드는 별도 useEffect에서 처리하므로 스킵 // 우측 패널이고 linkedFilters가 설정되어 있지만 좌측 데이터가 선택되지 않은 경우 스킵
if (isInitialLoadRef.current) { const isRightPanel = splitPanelPosition === "right" ||
isInitialLoadRef.current = false; (screenId && splitPanelContext?.getPositionByScreenId?.(screenId as number) === "right");
const hasLinkedFiltersInConfig = splitPanelContext?.linkedFilters && splitPanelContext.linkedFilters.length > 0;
const hasSelectedLeftData = splitPanelContext?.selectedLeftData &&
Object.keys(splitPanelContext.selectedLeftData).length > 0;
// 우측 패널이고 좌측 데이터가 선택되지 않은 경우 - 기존 데이터 유지 (깜빡임 방지)
if (isRightPanel && !hasSelectedLeftData) {
// 데이터를 지우지 않고 로딩만 false로 설정
setLoading(false);
return;
}
// 첫 2번의 마운트는 초기 로드 useEffect에서 처리 (Strict Mode에서 2번 호출됨)
// 필터 변경이 아닌 경우 스킵
if (currentMount <= 2 && filters.length === 0) {
return; return;
} }
const loadFilteredData = async () => { const loadFilteredData = async () => {
try { try {
setLoading(true); // 로딩 상태를 true로 설정하지 않음 - 기존 데이터 유지하면서 새 데이터 로드 (깜빡임 방지)
// 필터 값을 검색 파라미터로 변환 // 필터 값을 검색 파라미터로 변환
const searchParams: Record<string, any> = {}; const searchParams: Record<string, any> = {};
@ -564,12 +604,6 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
} }
}); });
console.log("🔍 [CardDisplay] 필터 적용 데이터 로드:", {
tableName: tableNameToUse,
filtersCount: filters.length,
searchParams,
});
// search 파라미터로 검색 조건 전달 (API 스펙에 맞게) // search 파라미터로 검색 조건 전달 (API 스펙에 맞게)
const dataResponse = await tableTypeApi.getTableData(tableNameToUse, { const dataResponse = await tableTypeApi.getTableData(tableNameToUse, {
page: 1, page: 1,
@ -584,16 +618,14 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
tableOptionsContext.updateTableDataCount(tableId, dataResponse.data?.length || 0); tableOptionsContext.updateTableDataCount(tableId, dataResponse.data?.length || 0);
} }
} catch (error) { } catch (error) {
console.error("❌ [CardDisplay] 필터 적용 실패:", error); // 필터 적용 실패 시 무시
} finally {
setLoading(false);
} }
}; };
// 필터 변경 시 항상 데이터 다시 로드 (빈 필터 = 전체 데이터) // 필터 변경 시 항상 데이터 다시 로드 (빈 필터 = 전체 데이터)
loadFilteredData(); loadFilteredData();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [filters, tableNameToUse, isDesignMode, tableId]); }, [filters, tableNameToUse, isDesignMode, tableId, splitPanelContext?.selectedLeftData, splitPanelPosition]);
// 컬럼 고유 값 조회 함수 (select 타입 필터용) // 컬럼 고유 값 조회 함수 (select 타입 필터용)
const getColumnUniqueValues = useCallback(async (columnName: string): Promise<Array<{ label: string; value: string }>> => { const getColumnUniqueValues = useCallback(async (columnName: string): Promise<Array<{ label: string; value: string }>> => {
@ -616,7 +648,6 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
label: mapping?.[value]?.label || value, label: mapping?.[value]?.label || value,
})); }));
} catch (error) { } catch (error) {
console.error(`❌ [CardDisplay] 고유 값 조회 실패: ${columnName}`, error);
return []; return [];
} }
}, [tableNameToUse]); }, [tableNameToUse]);
@ -663,10 +694,6 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
// onFilterChange는 ref를 통해 최신 함수를 호출하는 래퍼 사용 // onFilterChange는 ref를 통해 최신 함수를 호출하는 래퍼 사용
const onFilterChangeWrapper = (newFilters: TableFilter[]) => { const onFilterChangeWrapper = (newFilters: TableFilter[]) => {
console.log("🎴 [CardDisplay] onFilterChange 래퍼 호출:", {
tableId,
filtersCount: newFilters.length,
});
setFiltersRef.current(newFilters); setFiltersRef.current(newFilters);
}; };
@ -686,20 +713,12 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
getColumnUniqueValues: getColumnUniqueValuesWrapper, getColumnUniqueValues: getColumnUniqueValuesWrapper,
}; };
console.log("📋 [CardDisplay] TableOptionsContext에 등록:", {
tableId,
tableName: tableNameToUse,
columnsCount: columns.length,
dataCount: loadedTableData.length,
});
registerTableRef.current(registration); registerTableRef.current(registration);
const unregister = unregisterTableRef.current; const unregister = unregisterTableRef.current;
const currentTableId = tableId; const currentTableId = tableId;
return () => { return () => {
console.log("📋 [CardDisplay] TableOptionsContext에서 해제:", currentTableId);
unregister(currentTableId); unregister(currentTableId);
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -711,8 +730,34 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
columnsKey, // 컬럼 변경 시에만 재등록 columnsKey, // 컬럼 변경 시에만 재등록
]); ]);
// 로딩 중인 경우 로딩 표시 // 우측 패널이고 좌측 데이터가 한 번도 선택된 적이 없는 경우에만 "선택해주세요" 표시
if (loading) { // 한 번이라도 선택된 적이 있으면 로딩 중에도 기존 데이터 유지 (깜빡임 방지)
if (shouldHideDataForRightPanel) {
return (
<div
className={className}
style={{
...componentStyle,
...style,
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "20px",
background: "#f8fafc",
borderRadius: "12px",
}}
>
<div className="text-muted-foreground text-center">
<div className="text-lg mb-2"> </div>
<div className="text-sm text-gray-400"> </div>
</div>
</div>
);
}
// 로딩 중이고 데이터가 없는 경우에만 로딩 표시
// 데이터가 있으면 로딩 중에도 기존 데이터 유지 (깜빡임 방지)
if (loading && displayData.length === 0 && !hasEverSelectedLeftData) {
return ( return (
<div <div
className={className} className={className}

View File

@ -66,31 +66,10 @@ class ScreenSplitPanelRenderer extends AutoRegisteringComponentRenderer {
}; };
render() { render() {
console.log("🚀 [ScreenSplitPanelRenderer] render() 호출됨!", this.props);
const { component, style = {}, componentConfig, config, screenId, formData } = this.props as any; const { component, style = {}, componentConfig, config, screenId, formData } = this.props as any;
// componentConfig 또는 config 또는 component.componentConfig 사용 // componentConfig 또는 config 또는 component.componentConfig 사용
const finalConfig = componentConfig || config || component?.componentConfig || {}; const finalConfig = componentConfig || config || component?.componentConfig || {};
console.log("🔍 [ScreenSplitPanelRenderer] 설정 분석:", {
hasComponentConfig: !!componentConfig,
hasConfig: !!config,
hasComponentComponentConfig: !!component?.componentConfig,
finalConfig,
splitRatio: finalConfig.splitRatio,
leftScreenId: finalConfig.leftScreenId,
rightScreenId: finalConfig.rightScreenId,
componentType: component?.componentType,
componentId: component?.id,
});
// 🆕 formData 별도 로그 (명확한 확인)
console.log("📝 [ScreenSplitPanelRenderer] formData 확인:", {
hasFormData: !!formData,
formDataKeys: formData ? Object.keys(formData) : [],
formData: formData,
});
return ( return (
<div style={{ width: "100%", height: "100%", ...style }}> <div style={{ width: "100%", height: "100%", ...style }}>

View File

@ -1268,18 +1268,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}); });
} }
console.log(`📡 [TableList] API 호출 시작 [${columnName}]:`, {
url: `/table-categories/${targetTable}/${targetColumn}/values`,
});
const response = await apiClient.get(`/table-categories/${targetTable}/${targetColumn}/values`); const response = await apiClient.get(`/table-categories/${targetTable}/${targetColumn}/values`);
console.log(`📡 [TableList] API 응답 [${columnName}]:`, {
success: response.data.success,
dataLength: response.data.data?.length,
rawData: response.data,
items: response.data.data,
});
if (response.data.success && response.data.data && Array.isArray(response.data.data)) { if (response.data.success && response.data.data && Array.isArray(response.data.data)) {
const mapping: Record<string, { label: string; color?: string }> = {}; const mapping: Record<string, { label: string; color?: string }> = {};
@ -1291,18 +1282,11 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
label: item.valueLabel, label: item.valueLabel,
color: item.color, color: item.color,
}; };
console.log(` 🔑 [${columnName}] "${key}" => "${item.valueLabel}" (색상: ${item.color})`);
}); });
if (Object.keys(mapping).length > 0) { if (Object.keys(mapping).length > 0) {
// 🆕 원래 컬럼명(item_info.material)으로 매핑 저장 // 🆕 원래 컬럼명(item_info.material)으로 매핑 저장
mappings[columnName] = mapping; mappings[columnName] = mapping;
console.log(`✅ [TableList] 카테고리 매핑 로드 완료 [${columnName}]:`, {
columnName,
mappingCount: Object.keys(mapping).length,
mappingKeys: Object.keys(mapping),
mapping,
});
} else { } else {
console.warn(`⚠️ [TableList] 매핑 데이터가 비어있음 [${columnName}]`); console.warn(`⚠️ [TableList] 매핑 데이터가 비어있음 [${columnName}]`);
} }
@ -1342,7 +1326,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
col.columnName, col.columnName,
})) || []; })) || [];
console.log("🔍 [TableList] additionalJoinInfo 컬럼:", additionalJoinColumns);
// 조인 테이블별로 그룹화 // 조인 테이블별로 그룹화
const joinedTableColumns: Record<string, { columnName: string; actualColumn: string }[]> = {}; const joinedTableColumns: Record<string, { columnName: string; actualColumn: string }[]> = {};
@ -1375,7 +1358,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}); });
} }
console.log("🔍 [TableList] 조인 테이블별 컬럼:", joinedTableColumns);
// 조인된 테이블별로 inputType 정보 가져오기 // 조인된 테이블별로 inputType 정보 가져오기
const newJoinedColumnMeta: Record<string, { webType?: string; codeCategory?: string; inputType?: string }> = {}; const newJoinedColumnMeta: Record<string, { webType?: string; codeCategory?: string; inputType?: string }> = {};
@ -1421,9 +1403,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
if (Object.keys(mapping).length > 0) { if (Object.keys(mapping).length > 0) {
mappings[col.columnName] = mapping; mappings[col.columnName] = mapping;
console.log(`✅ [TableList] 조인 테이블 카테고리 매핑 로드 완료 [${col.columnName}]:`, {
mappingCount: Object.keys(mapping).length,
});
} }
} }
} catch (error) { } catch (error) {
@ -1442,16 +1421,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
console.log("✅ [TableList] 조인 컬럼 메타데이터 설정:", newJoinedColumnMeta); console.log("✅ [TableList] 조인 컬럼 메타데이터 설정:", newJoinedColumnMeta);
} }
console.log("📊 [TableList] 전체 카테고리 매핑 설정:", {
mappingsCount: Object.keys(mappings).length,
mappingsKeys: Object.keys(mappings),
mappings,
});
if (Object.keys(mappings).length > 0) { if (Object.keys(mappings).length > 0) {
setCategoryMappings(mappings); setCategoryMappings(mappings);
setCategoryMappingsKey((prev) => prev + 1); setCategoryMappingsKey((prev) => prev + 1);
console.log("✅ [TableList] setCategoryMappings 호출 완료");
} else { } else {
console.warn("⚠️ [TableList] 매핑이 비어있어 상태 업데이트 스킵"); console.warn("⚠️ [TableList] 매핑이 비어있어 상태 업데이트 스킵");
} }
@ -1473,11 +1445,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// ======================================== // ========================================
const fetchTableDataInternal = useCallback(async () => { const fetchTableDataInternal = useCallback(async () => {
console.log("📡 [TableList] fetchTableDataInternal 호출됨", {
tableName: tableConfig.selectedTable,
isDesignMode,
currentPage,
});
if (!tableConfig.selectedTable || isDesignMode) { if (!tableConfig.selectedTable || isDesignMode) {
setData([]); setData([]);
@ -1501,13 +1468,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
let hasLinkedFiltersConfigured = false; // 연결 필터가 설정되어 있는지 여부 let hasLinkedFiltersConfigured = false; // 연결 필터가 설정되어 있는지 여부
let hasSelectedLeftData = false; // 좌측에서 데이터가 선택되었는지 여부 let hasSelectedLeftData = false; // 좌측에서 데이터가 선택되었는지 여부
console.log("🔍 [TableList] 분할 패널 컨텍스트 확인:", {
hasSplitPanelContext: !!splitPanelContext,
tableName: tableConfig.selectedTable,
selectedLeftData: splitPanelContext?.selectedLeftData,
linkedFilters: splitPanelContext?.linkedFilters,
splitPanelPosition: splitPanelPosition,
});
if (splitPanelContext) { if (splitPanelContext) {
// 연결 필터 설정 여부 확인 (현재 테이블에 해당하는 필터가 있는지) // 연결 필터 설정 여부 확인 (현재 테이블에 해당하는 필터가 있는지)
@ -1523,7 +1483,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
splitPanelContext.selectedLeftData && Object.keys(splitPanelContext.selectedLeftData).length > 0; splitPanelContext.selectedLeftData && Object.keys(splitPanelContext.selectedLeftData).length > 0;
const allLinkedFilters = splitPanelContext.getLinkedFilterValues(); const allLinkedFilters = splitPanelContext.getLinkedFilterValues();
console.log("🔗 [TableList] 연결 필터 원본:", allLinkedFilters);
// 현재 테이블에 해당하는 필터만 추출 (테이블명.컬럼명 형식에서) // 현재 테이블에 해당하는 필터만 추출 (테이블명.컬럼명 형식에서)
// 연결 필터는 코드 값이므로 정확한 매칭(equals)을 사용해야 함 // 연결 필터는 코드 값이므로 정확한 매칭(equals)을 사용해야 함
@ -1655,7 +1614,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}; };
}); });
console.log("🎯 [TableList] 화면별 엔티티 설정:", screenEntityConfigs);
// 🆕 제외 필터 처리 (다른 테이블에 이미 존재하는 데이터 제외) // 🆕 제외 필터 처리 (다른 테이블에 이미 존재하는 데이터 제외)
let excludeFilterParam: any = undefined; let excludeFilterParam: any = undefined;
@ -2146,16 +2104,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 분할 패널 컨텍스트가 있고, 좌측 화면인 경우에만 행 선택 및 데이터 전달 // 분할 패널 컨텍스트가 있고, 좌측 화면인 경우에만 행 선택 및 데이터 전달
const effectiveSplitPosition = splitPanelPosition || currentSplitPosition; const effectiveSplitPosition = splitPanelPosition || currentSplitPosition;
console.log("🔗 [TableList] 셀 클릭 - 분할 패널 위치 확인:", {
rowIndex,
colIndex,
splitPanelPosition,
currentSplitPosition,
effectiveSplitPosition,
hasSplitPanelContext: !!splitPanelContext,
isCurrentlySelected,
});
if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) { if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
// 이미 선택된 행과 다른 행을 클릭한 경우에만 처리 // 이미 선택된 행과 다른 행을 클릭한 경우에만 처리
if (!isCurrentlySelected) { if (!isCurrentlySelected) {
@ -2165,10 +2113,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 분할 패널 컨텍스트에 데이터 저장 // 분할 패널 컨텍스트에 데이터 저장
splitPanelContext.setSelectedLeftData(row); splitPanelContext.setSelectedLeftData(row);
console.log("🔗 [TableList] 셀 클릭으로 분할 패널 좌측 데이터 저장:", {
row,
parentDataMapping: splitPanelContext.parentDataMapping,
});
// onSelectedRowsChange 콜백 호출 // onSelectedRowsChange 콜백 호출
if (onSelectedRowsChange) { if (onSelectedRowsChange) {
@ -2888,7 +2832,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
try { try {
localStorage.setItem(tableStateKey, JSON.stringify(state)); localStorage.setItem(tableStateKey, JSON.stringify(state));
console.log("✅ 테이블 상태 저장:", tableStateKey);
} catch (error) { } catch (error) {
console.error("❌ 테이블 상태 저장 실패:", error); console.error("❌ 테이블 상태 저장 실패:", error);
} }
@ -2930,7 +2873,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
setHeaderFilters(filters); setHeaderFilters(filters);
} }
console.log("✅ 테이블 상태 복원:", tableStateKey);
} catch (error) { } catch (error) {
console.error("❌ 테이블 상태 복원 실패:", error); console.error("❌ 테이블 상태 복원 실패:", error);
} }
@ -2951,7 +2893,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
setShowGridLines(true); setShowGridLines(true);
setHeaderFilters({}); setHeaderFilters({});
toast.success("테이블 설정이 초기화되었습니다."); toast.success("테이블 설정이 초기화되었습니다.");
console.log("✅ 테이블 상태 초기화:", tableStateKey);
} catch (error) { } catch (error) {
console.error("❌ 테이블 상태 초기화 실패:", error); console.error("❌ 테이블 상태 초기화 실패:", error);
} }

View File

@ -115,21 +115,9 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
// 필터링된 결과가 없으면 모든 테이블 반환 (폴백) // 필터링된 결과가 없으면 모든 테이블 반환 (폴백)
if (filteredTables.length === 0) { if (filteredTables.length === 0) {
console.log("🔍 [TableSearchWidget] 대상 패널에 테이블 없음, 전체 테이블 사용:", {
targetPanelPosition,
allTablesCount: allTableList.length,
allTableIds: allTableList.map(t => t.tableId),
});
return allTableList; return allTableList;
} }
console.log("🔍 [TableSearchWidget] 테이블 필터링:", {
targetPanelPosition,
allTablesCount: allTableList.length,
filteredCount: filteredTables.length,
filteredTableIds: filteredTables.map(t => t.tableId),
});
return filteredTables; return filteredTables;
}, [allTableList, targetPanelPosition]); }, [allTableList, targetPanelPosition]);
@ -159,11 +147,6 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
// 현재 선택된 테이블이 대상 패널에 없으면 대상 패널의 첫 번째 테이블 선택 // 현재 선택된 테이블이 대상 패널에 없으면 대상 패널의 첫 번째 테이블 선택
if (!selectedTableId || !isCurrentTableInTarget) { if (!selectedTableId || !isCurrentTableInTarget) {
const targetTable = tableList[0]; const targetTable = tableList[0];
console.log("🔍 [TableSearchWidget] 대상 패널 테이블 자동 선택:", {
targetPanelPosition,
selectedTableId: targetTable.tableId,
tableName: targetTable.tableName,
});
setSelectedTableId(targetTable.tableId); setSelectedTableId(targetTable.tableId);
} }
}, [tableList, selectedTableId, autoSelectFirstTable, setSelectedTableId, targetPanelPosition]); }, [tableList, selectedTableId, autoSelectFirstTable, setSelectedTableId, targetPanelPosition]);
@ -374,12 +357,6 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
return true; return true;
}); });
console.log("🔍 [TableSearchWidget] 필터 적용:", {
currentTableId: currentTable?.tableId,
currentTableName: currentTable?.tableName,
filtersCount: filtersWithValues.length,
filtersWithValues,
});
currentTable?.onFilterChange(filtersWithValues); currentTable?.onFilterChange(filtersWithValues);
}; };

View File

@ -23,12 +23,6 @@ const TabsWidgetWrapper: React.FC<any> = (props) => {
persistSelection: tabsConfig.persistSelection || false, persistSelection: tabsConfig.persistSelection || false,
}; };
console.log("🎨 TabsWidget 렌더링:", {
componentId: component.id,
tabs: tabsComponent.tabs,
tabsLength: tabsComponent.tabs.length,
component,
});
// TabsWidget 동적 로드 // TabsWidget 동적 로드
const TabsWidget = require("@/components/screen/widgets/TabsWidget").TabsWidget; const TabsWidget = require("@/components/screen/widgets/TabsWidget").TabsWidget;