검색필터 고장
This commit is contained in:
parent
5102eec46f
commit
5daef415ad
|
|
@ -542,10 +542,12 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
|
||||
if (response.success) {
|
||||
// 리피터 데이터 저장 이벤트 발생 (UnifiedRepeater 컴포넌트가 리스닝)
|
||||
window.dispatchEvent(new CustomEvent("repeaterSave", {
|
||||
detail: { parentId: response.data?.id || formData.id }
|
||||
}));
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: { parentId: response.data?.id || formData.id },
|
||||
}),
|
||||
);
|
||||
|
||||
toast.success("데이터가 성공적으로 저장되었습니다.");
|
||||
} else {
|
||||
toast.error(response.message || "저장에 실패했습니다.");
|
||||
|
|
|
|||
|
|
@ -45,9 +45,6 @@ export const UnifiedList = forwardRef<HTMLDivElement, UnifiedListProps>((props,
|
|||
[config.columns],
|
||||
);
|
||||
|
||||
// 디버깅: config.cardConfig 확인
|
||||
console.log("📋 UnifiedList config.cardConfig:", config.cardConfig);
|
||||
|
||||
// TableListComponent에 전달할 component 객체 생성
|
||||
const componentObj = useMemo(
|
||||
() => ({
|
||||
|
|
|
|||
|
|
@ -1,27 +1,11 @@
|
|||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useCallback,
|
||||
useMemo,
|
||||
ReactNode,
|
||||
} from "react";
|
||||
import {
|
||||
TableRegistration,
|
||||
TableOptionsContextValue,
|
||||
} from "@/types/table-options";
|
||||
import React, { createContext, useContext, useState, useCallback, useMemo, ReactNode } from "react";
|
||||
import { TableRegistration, TableOptionsContextValue } from "@/types/table-options";
|
||||
import { useActiveTab } from "./ActiveTabContext";
|
||||
|
||||
const TableOptionsContext = createContext<TableOptionsContextValue | undefined>(
|
||||
undefined
|
||||
);
|
||||
const TableOptionsContext = createContext<TableOptionsContextValue | undefined>(undefined);
|
||||
|
||||
export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [registeredTables, setRegisteredTables] = useState<
|
||||
Map<string, TableRegistration>
|
||||
>(new Map());
|
||||
export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [registeredTables, setRegisteredTables] = useState<Map<string, TableRegistration>>(new Map());
|
||||
const [selectedTableId, setSelectedTableId] = useState<string | null>(null);
|
||||
|
||||
/**
|
||||
|
|
@ -43,7 +27,7 @@ export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({
|
|||
|
||||
/**
|
||||
* 테이블 등록 해제
|
||||
* 주의:
|
||||
* 주의:
|
||||
* 1. selectedTableId를 의존성으로 사용하면 무한 루프 발생 가능
|
||||
* 2. 재등록 시에도 unregister가 호출되므로 selectedTableId를 변경하면 안됨
|
||||
*/
|
||||
|
|
@ -54,13 +38,13 @@ export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({
|
|||
newMap.delete(tableId);
|
||||
return newMap;
|
||||
});
|
||||
|
||||
|
||||
// 🚫 selectedTableId를 변경하지 않음
|
||||
// 이유: useEffect 재실행 시 cleanup → register 순서로 호출되는데,
|
||||
// cleanup에서 selectedTableId를 null로 만들면 필터 설정이 초기화됨
|
||||
// 다른 테이블이 선택되어야 하면 TableSearchWidget에서 자동 선택함
|
||||
},
|
||||
[] // 의존성 없음 - 무한 루프 방지
|
||||
[], // 의존성 없음 - 무한 루프 방지
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -70,7 +54,7 @@ export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({
|
|||
(tableId: string) => {
|
||||
return registeredTables.get(tableId);
|
||||
},
|
||||
[registeredTables]
|
||||
[registeredTables],
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -99,25 +83,26 @@ export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({
|
|||
const getActiveTabTables = useCallback(() => {
|
||||
const allTables = Array.from(registeredTables.values());
|
||||
const activeTabIds = activeTabContext.getAllActiveTabIds();
|
||||
|
||||
|
||||
// 활성 탭이 없으면 탭에 속하지 않은 테이블만 반환
|
||||
if (activeTabIds.length === 0) {
|
||||
return allTables.filter(table => !table.parentTabId);
|
||||
return allTables.filter((table) => !table.parentTabId);
|
||||
}
|
||||
|
||||
|
||||
// 활성 탭에 속한 테이블 + 탭에 속하지 않은 테이블
|
||||
return allTables.filter(table =>
|
||||
!table.parentTabId || activeTabIds.includes(table.parentTabId)
|
||||
);
|
||||
return allTables.filter((table) => !table.parentTabId || activeTabIds.includes(table.parentTabId));
|
||||
}, [registeredTables, activeTabContext]);
|
||||
|
||||
/**
|
||||
* 특정 탭의 테이블만 반환
|
||||
*/
|
||||
const getTablesForTab = useCallback((tabId: string) => {
|
||||
const allTables = Array.from(registeredTables.values());
|
||||
return allTables.filter(table => table.parentTabId === tabId);
|
||||
}, [registeredTables]);
|
||||
const getTablesForTab = useCallback(
|
||||
(tabId: string) => {
|
||||
const allTables = Array.from(registeredTables.values());
|
||||
return allTables.filter((table) => table.parentTabId === tabId);
|
||||
},
|
||||
[registeredTables],
|
||||
);
|
||||
|
||||
return (
|
||||
<TableOptionsContext.Provider
|
||||
|
|
@ -142,10 +127,12 @@ export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({
|
|||
* Context Hook
|
||||
*/
|
||||
export const useTableOptions = () => {
|
||||
console.log("🔍🔍🔍 [useTableOptions] Hook 호출됨");
|
||||
const context = useContext(TableOptionsContext);
|
||||
console.log("🔍 [useTableOptions] context 확인:", { hasContext: !!context });
|
||||
if (!context) {
|
||||
console.error("❌ [useTableOptions] Context가 없습니다! TableOptionsProvider 외부에서 호출됨");
|
||||
throw new Error("useTableOptions must be used within TableOptionsProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -720,17 +720,53 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
};
|
||||
|
||||
// 렌더러가 클래스인지 함수인지 확인
|
||||
if (
|
||||
const isClass =
|
||||
typeof NewComponentRenderer === "function" &&
|
||||
NewComponentRenderer.prototype &&
|
||||
NewComponentRenderer.prototype.render
|
||||
) {
|
||||
NewComponentRenderer.prototype.render;
|
||||
|
||||
if (componentType === "table-search-widget") {
|
||||
console.log("🔍 [DynamicComponentRenderer] table-search-widget 렌더링 분기:", {
|
||||
isClass,
|
||||
hasPrototype: !!NewComponentRenderer.prototype,
|
||||
hasRender: !!NewComponentRenderer.prototype?.render,
|
||||
componentName: NewComponentRenderer.name,
|
||||
componentProp: rendererProps.component,
|
||||
screenId: rendererProps.screenId,
|
||||
});
|
||||
}
|
||||
|
||||
if (isClass) {
|
||||
// 클래스 기반 렌더러 (AutoRegisteringComponentRenderer 상속)
|
||||
const rendererInstance = new NewComponentRenderer(rendererProps);
|
||||
return rendererInstance.render();
|
||||
} else {
|
||||
// 함수형 컴포넌트
|
||||
// refreshKey를 React key로 전달하여 컴포넌트 리마운트 강제
|
||||
|
||||
// 🔧 디버깅: table-search-widget인 경우 직접 호출 후 반환
|
||||
if (componentType === "table-search-widget") {
|
||||
console.log("🔧🔧🔧 [DynamicComponentRenderer] TableSearchWidget 직접 호출 반환");
|
||||
console.log("🔧 [DynamicComponentRenderer] NewComponentRenderer 함수 확인:", {
|
||||
name: NewComponentRenderer.name,
|
||||
toString: NewComponentRenderer.toString().substring(0, 200),
|
||||
});
|
||||
try {
|
||||
const result = NewComponentRenderer(rendererProps);
|
||||
console.log("🔧 [DynamicComponentRenderer] TableSearchWidget 결과 상세:", {
|
||||
resultType: typeof result,
|
||||
type: result?.type?.name || result?.type || "unknown",
|
||||
propsKeys: result?.props ? Object.keys(result.props) : [],
|
||||
propsStyle: result?.props?.style,
|
||||
propsChildren: typeof result?.props?.children,
|
||||
});
|
||||
// 직접 호출 결과를 반환
|
||||
return result;
|
||||
} catch (directCallError) {
|
||||
console.error("❌ [DynamicComponentRenderer] TableSearchWidget 직접 호출 실패:", directCallError);
|
||||
}
|
||||
}
|
||||
|
||||
return <NewComponentRenderer key={refreshKey} {...rendererProps} />;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -342,14 +342,19 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
const newSearchValues: Record<string, any> = {};
|
||||
filters.forEach((filter) => {
|
||||
if (filter.value) {
|
||||
newSearchValues[filter.columnName] = filter.value;
|
||||
// operator 정보도 함께 전달 (백엔드에서 equals/contains 구분)
|
||||
newSearchValues[filter.columnName] = {
|
||||
value: filter.value,
|
||||
operator: filter.operator || "contains",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// console.log("🔍 [TableListComponent] filters → searchValues:", {
|
||||
// filters: filters.length,
|
||||
// searchValues: newSearchValues,
|
||||
// });
|
||||
console.log("🔍 [TableListComponent] filters → searchValues:", {
|
||||
filtersCount: filters.length,
|
||||
filters: filters.map((f) => ({ col: f.columnName, op: f.operator, val: f.value })),
|
||||
searchValues: newSearchValues,
|
||||
});
|
||||
|
||||
setSearchValues(newSearchValues);
|
||||
setCurrentPage(1); // 필터 변경 시 첫 페이지로
|
||||
|
|
|
|||
|
|
@ -50,7 +50,24 @@ interface TableSearchWidgetProps {
|
|||
}
|
||||
|
||||
export function TableSearchWidget({ component, screenId, onHeightChange }: TableSearchWidgetProps) {
|
||||
const { registeredTables, selectedTableId, setSelectedTableId, getTable, getActiveTabTables } = useTableOptions();
|
||||
console.log("🎯🎯🎯 [TableSearchWidget] 함수 시작!", { componentId: component?.id, screenId });
|
||||
|
||||
// 🔧 직접 useTableOptions 호출 (에러 발생 시 catch하지 않고 그대로 throw)
|
||||
const tableOptionsContext = useTableOptions();
|
||||
console.log("✅ [TableSearchWidget] useTableOptions 성공", { hasContext: !!tableOptionsContext });
|
||||
|
||||
const { registeredTables, selectedTableId, setSelectedTableId, getTable, getActiveTabTables } = tableOptionsContext;
|
||||
|
||||
// 등록된 테이블 확인 로그
|
||||
console.log("🔍 [TableSearchWidget] 등록된 테이블:", {
|
||||
count: registeredTables.size,
|
||||
tables: Array.from(registeredTables.entries()).map(([id, t]) => ({
|
||||
id,
|
||||
tableName: t.tableName,
|
||||
hasOnFilterChange: typeof t.onFilterChange === "function",
|
||||
})),
|
||||
selectedTableId,
|
||||
});
|
||||
const { isPreviewMode } = useScreenPreview(); // 미리보기 모드 확인
|
||||
const { getAllActiveTabIds, activeTabs } = useActiveTab(); // 활성 탭 정보
|
||||
|
||||
|
|
@ -65,7 +82,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
// Context가 없으면 (디자이너 모드) 무시
|
||||
setWidgetHeight = undefined;
|
||||
}
|
||||
|
||||
|
||||
// 탭별 필터 값 저장 (탭 ID -> 필터 값)
|
||||
const [tabFilterValues, setTabFilterValues] = useState<Record<string, Record<string, any>>>({});
|
||||
|
||||
|
|
@ -92,16 +109,16 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
|
||||
// Map을 배열로 변환
|
||||
const allTableList = Array.from(registeredTables.values());
|
||||
|
||||
|
||||
// 현재 활성 탭 ID 목록
|
||||
const activeTabIds = useMemo(() => getAllActiveTabIds(), [activeTabs]);
|
||||
|
||||
|
||||
// 대상 패널 위치 + 활성 탭에 따라 테이블 필터링
|
||||
const tableList = useMemo(() => {
|
||||
// 1단계: 활성 탭 기반 필터링
|
||||
// - 활성 탭에 속한 테이블만 표시
|
||||
// - 탭에 속하지 않은 테이블(parentTabId가 없는)도 포함
|
||||
let filteredByTab = allTableList.filter(table => {
|
||||
let filteredByTab = allTableList.filter((table) => {
|
||||
// 탭에 속하지 않는 테이블은 항상 표시
|
||||
if (!table.parentTabId) return true;
|
||||
// 활성 탭에 속한 테이블만 표시
|
||||
|
|
@ -110,9 +127,9 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
|
||||
// 2단계: 대상 패널 위치에 따라 추가 필터링
|
||||
if (targetPanelPosition !== "auto") {
|
||||
filteredByTab = filteredByTab.filter(table => {
|
||||
filteredByTab = filteredByTab.filter((table) => {
|
||||
const tableId = table.tableId.toLowerCase();
|
||||
|
||||
|
||||
if (targetPanelPosition === "left") {
|
||||
// 좌측 패널 대상: card-display만
|
||||
return tableId.includes("card-display") || tableId.includes("card");
|
||||
|
|
@ -121,16 +138,14 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
const isCardDisplay = tableId.includes("card-display") || tableId.includes("card");
|
||||
return !isCardDisplay;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// 필터링된 결과가 없으면 탭 기반 필터링 결과만 반환
|
||||
if (filteredByTab.length === 0) {
|
||||
return allTableList.filter(table =>
|
||||
!table.parentTabId || activeTabIds.includes(table.parentTabId)
|
||||
);
|
||||
return allTableList.filter((table) => !table.parentTabId || activeTabIds.includes(table.parentTabId));
|
||||
}
|
||||
|
||||
return filteredByTab;
|
||||
|
|
@ -141,18 +156,18 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
console.log("🔍 [TableSearchWidget] currentTable 계산:", {
|
||||
selectedTableId,
|
||||
tableListLength: tableList.length,
|
||||
tableList: tableList.map(t => ({ id: t.tableId, name: t.tableName, parentTabId: t.parentTabId }))
|
||||
tableList: tableList.map((t) => ({ id: t.tableId, name: t.tableName, parentTabId: t.parentTabId })),
|
||||
});
|
||||
|
||||
|
||||
if (!selectedTableId) return undefined;
|
||||
|
||||
|
||||
// 먼저 tableList(필터링된 목록)에서 찾기
|
||||
const tableFromList = tableList.find(t => t.tableId === selectedTableId);
|
||||
const tableFromList = tableList.find((t) => t.tableId === selectedTableId);
|
||||
if (tableFromList) {
|
||||
console.log("✅ [TableSearchWidget] 테이블 찾음 (tableList):", tableFromList.tableName);
|
||||
return tableFromList;
|
||||
}
|
||||
|
||||
|
||||
// tableList에 없으면 전체에서 찾기 (폴백)
|
||||
const tableFromAll = getTable(selectedTableId);
|
||||
console.log("🔄 [TableSearchWidget] 테이블 찾음 (전체):", tableFromAll?.tableName);
|
||||
|
|
@ -161,10 +176,10 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
|
||||
// 🆕 활성 탭 ID 문자열 (변경 감지용)
|
||||
const activeTabIdsStr = useMemo(() => activeTabIds.join(","), [activeTabIds]);
|
||||
|
||||
|
||||
// 🆕 이전 활성 탭 ID 추적 (탭 전환 감지용)
|
||||
const prevActiveTabIdsRef = useRef<string>(activeTabIdsStr);
|
||||
|
||||
|
||||
// 대상 패널의 첫 번째 테이블 자동 선택
|
||||
useEffect(() => {
|
||||
if (!autoSelectFirstTable || tableList.length === 0) {
|
||||
|
|
@ -177,21 +192,21 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
console.log("🔄 [TableSearchWidget] 탭 전환 감지:", {
|
||||
이전탭: prevActiveTabIdsRef.current,
|
||||
현재탭: activeTabIdsStr,
|
||||
가용테이블: tableList.map(t => ({ id: t.tableId, tableName: t.tableName, parentTabId: t.parentTabId })),
|
||||
현재선택테이블: selectedTableId
|
||||
가용테이블: tableList.map((t) => ({ id: t.tableId, tableName: t.tableName, parentTabId: t.parentTabId })),
|
||||
현재선택테이블: selectedTableId,
|
||||
});
|
||||
prevActiveTabIdsRef.current = activeTabIdsStr;
|
||||
|
||||
|
||||
// 🆕 탭 전환 시: 해당 탭에 속한 테이블 중 첫 번째 강제 선택
|
||||
const activeTabTable = tableList.find(t => t.parentTabId && activeTabIds.includes(t.parentTabId));
|
||||
const activeTabTable = tableList.find((t) => t.parentTabId && activeTabIds.includes(t.parentTabId));
|
||||
const targetTable = activeTabTable || tableList[0];
|
||||
|
||||
|
||||
if (targetTable) {
|
||||
console.log("✅ [TableSearchWidget] 탭 전환으로 테이블 강제 선택:", {
|
||||
테이블ID: targetTable.tableId,
|
||||
테이블명: targetTable.tableName,
|
||||
탭ID: targetTable.parentTabId,
|
||||
이전테이블: selectedTableId
|
||||
이전테이블: selectedTableId,
|
||||
});
|
||||
setSelectedTableId(targetTable.tableId);
|
||||
}
|
||||
|
|
@ -199,30 +214,38 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
}
|
||||
|
||||
// 현재 선택된 테이블이 대상 패널에 있는지 확인
|
||||
const isCurrentTableInTarget = selectedTableId && tableList.some(t => t.tableId === selectedTableId);
|
||||
const isCurrentTableInTarget = selectedTableId && tableList.some((t) => t.tableId === selectedTableId);
|
||||
|
||||
// 현재 선택된 테이블이 대상 패널에 없으면 첫 번째 테이블 선택
|
||||
if (!selectedTableId || !isCurrentTableInTarget) {
|
||||
const activeTabTable = tableList.find(t => t.parentTabId && activeTabIds.includes(t.parentTabId));
|
||||
const activeTabTable = tableList.find((t) => t.parentTabId && activeTabIds.includes(t.parentTabId));
|
||||
const targetTable = activeTabTable || tableList[0];
|
||||
|
||||
|
||||
if (targetTable && targetTable.tableId !== selectedTableId) {
|
||||
console.log("✅ [TableSearchWidget] 테이블 자동 선택 (초기):", {
|
||||
테이블ID: targetTable.tableId,
|
||||
테이블명: targetTable.tableName,
|
||||
탭ID: targetTable.parentTabId
|
||||
탭ID: targetTable.parentTabId,
|
||||
});
|
||||
setSelectedTableId(targetTable.tableId);
|
||||
}
|
||||
}
|
||||
}, [tableList, selectedTableId, autoSelectFirstTable, setSelectedTableId, targetPanelPosition, activeTabIdsStr, activeTabIds]);
|
||||
}, [
|
||||
tableList,
|
||||
selectedTableId,
|
||||
autoSelectFirstTable,
|
||||
setSelectedTableId,
|
||||
targetPanelPosition,
|
||||
activeTabIdsStr,
|
||||
activeTabIds,
|
||||
]);
|
||||
|
||||
// 현재 선택된 테이블의 탭 ID (탭별 필터 저장용)
|
||||
const currentTableTabId = currentTable?.parentTabId;
|
||||
|
||||
// 탭별 필터 값 저장 키 생성
|
||||
const getTabFilterStorageKey = (tableName: string, tabId?: string) => {
|
||||
const baseKey = screenId
|
||||
const baseKey = screenId
|
||||
? `table_filter_values_${tableName}_screen_${screenId}`
|
||||
: `table_filter_values_${tableName}`;
|
||||
return tabId ? `${baseKey}_tab_${tabId}` : baseKey;
|
||||
|
|
@ -231,16 +254,16 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
// 탭 변경 시 이전 탭의 필터 값 저장 + 새 탭의 필터 값 복원
|
||||
useEffect(() => {
|
||||
if (!currentTable?.tableName) return;
|
||||
|
||||
|
||||
// 현재 필터 값이 있으면 탭별로 저장
|
||||
if (Object.keys(filterValues).length > 0 && currentTableTabId) {
|
||||
const storageKey = getTabFilterStorageKey(currentTable.tableName, currentTableTabId);
|
||||
localStorage.setItem(storageKey, JSON.stringify(filterValues));
|
||||
|
||||
|
||||
// 메모리 캐시에도 저장
|
||||
setTabFilterValues(prev => ({
|
||||
setTabFilterValues((prev) => ({
|
||||
...prev,
|
||||
[currentTableTabId]: filterValues
|
||||
[currentTableTabId]: filterValues,
|
||||
}));
|
||||
}
|
||||
}, [currentTableTabId, currentTable?.tableName]);
|
||||
|
|
@ -252,7 +275,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
currentTableTabId,
|
||||
filterMode,
|
||||
selectedTableId,
|
||||
컬럼수: currentTable?.columns?.length
|
||||
컬럼수: currentTable?.columns?.length,
|
||||
});
|
||||
if (!currentTable?.tableName) return;
|
||||
|
||||
|
|
@ -266,7 +289,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
width: f.width || 200,
|
||||
}));
|
||||
setActiveFilters(activeFiltersList);
|
||||
|
||||
|
||||
// 탭별 저장된 필터 값 복원
|
||||
if (currentTableTabId) {
|
||||
const storageKey = getTabFilterStorageKey(currentTable.tableName, currentTableTabId);
|
||||
|
|
@ -289,7 +312,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
|
||||
// 동적 모드: 화면별로 독립적인 필터 설정 불러오기
|
||||
// 참고: FilterPanel.tsx에서도 screenId만 사용하여 저장하므로 키가 일치해야 함
|
||||
const filterConfigKey = screenId
|
||||
const filterConfigKey = screenId
|
||||
? `table_filters_${currentTable.tableName}_screen_${screenId}`
|
||||
: `table_filters_${currentTable.tableName}`;
|
||||
const savedFilters = localStorage.getItem(filterConfigKey);
|
||||
|
|
@ -298,7 +321,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
filterConfigKey,
|
||||
savedFilters: savedFilters ? `${savedFilters.substring(0, 100)}...` : null,
|
||||
screenId,
|
||||
tableName: currentTable.tableName
|
||||
tableName: currentTable.tableName,
|
||||
});
|
||||
|
||||
if (savedFilters) {
|
||||
|
|
@ -327,11 +350,11 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
filterConfigKey,
|
||||
총필터수: parsed.length,
|
||||
활성화필터수: activeFiltersList.length,
|
||||
활성화필터: activeFiltersList.map(f => f.columnName)
|
||||
활성화필터: activeFiltersList.map((f) => f.columnName),
|
||||
});
|
||||
|
||||
setActiveFilters(activeFiltersList);
|
||||
|
||||
|
||||
// 탭별 저장된 필터 값 복원
|
||||
if (currentTableTabId) {
|
||||
const valuesStorageKey = getTabFilterStorageKey(currentTable.tableName, currentTableTabId);
|
||||
|
|
@ -361,7 +384,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
// 필터 설정이 없으면 activeFilters와 filterValues 모두 초기화
|
||||
console.log("⚠️ [TableSearchWidget] 저장된 필터 설정 없음 - 필터 초기화:", {
|
||||
tableName: currentTable.tableName,
|
||||
filterConfigKey
|
||||
filterConfigKey,
|
||||
});
|
||||
setActiveFilters([]);
|
||||
setFilterValues({});
|
||||
|
|
@ -482,21 +505,26 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
const filtersWithValues = activeFilters
|
||||
.map((filter) => {
|
||||
let filterValue = values[filter.columnName];
|
||||
|
||||
|
||||
// 날짜 범위 객체를 처리
|
||||
if (filter.filterType === "date" && filterValue && typeof filterValue === "object" && (filterValue.from || filterValue.to)) {
|
||||
if (
|
||||
filter.filterType === "date" &&
|
||||
filterValue &&
|
||||
typeof filterValue === "object" &&
|
||||
(filterValue.from || filterValue.to)
|
||||
) {
|
||||
// 날짜 범위 객체를 문자열 형식으로 변환 (백엔드 재시작 불필요)
|
||||
const formatDate = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
|
||||
// "YYYY-MM-DD|YYYY-MM-DD" 형식으로 변환
|
||||
const fromStr = filterValue.from ? formatDate(filterValue.from) : "";
|
||||
const toStr = filterValue.to ? formatDate(filterValue.to) : "";
|
||||
|
||||
|
||||
if (fromStr && toStr) {
|
||||
// 둘 다 있으면 파이프로 연결
|
||||
filterValue = `${fromStr}|${toStr}`;
|
||||
|
|
@ -510,12 +538,12 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
filterValue = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 다중선택 배열을 처리 (파이프로 연결된 문자열로 변환)
|
||||
if (filter.filterType === "select" && Array.isArray(filterValue)) {
|
||||
filterValue = filterValue.join("|");
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
...filter,
|
||||
value: filterValue || "",
|
||||
|
|
@ -529,7 +557,23 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
return true;
|
||||
});
|
||||
|
||||
currentTable?.onFilterChange(filtersWithValues);
|
||||
console.log("🔍 [TableSearchWidget] applyFilters 호출:", {
|
||||
currentTableId: currentTable?.tableId,
|
||||
currentTableName: currentTable?.tableName,
|
||||
hasOnFilterChange: !!currentTable?.onFilterChange,
|
||||
filtersCount: filtersWithValues.length,
|
||||
filters: filtersWithValues.map((f) => ({
|
||||
col: f.columnName,
|
||||
op: f.operator,
|
||||
val: f.value,
|
||||
})),
|
||||
});
|
||||
|
||||
if (currentTable?.onFilterChange) {
|
||||
currentTable.onFilterChange(filtersWithValues);
|
||||
} else {
|
||||
console.warn("⚠️ [TableSearchWidget] onFilterChange가 없음!", { currentTable });
|
||||
}
|
||||
};
|
||||
|
||||
// 필터 초기화
|
||||
|
|
@ -537,7 +581,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
setFilterValues({});
|
||||
setSelectedLabels({});
|
||||
currentTable?.onFilterChange([]);
|
||||
|
||||
|
||||
// 탭별 저장된 필터 값도 초기화
|
||||
if (currentTable?.tableName && currentTableTabId) {
|
||||
const storageKey = getTabFilterStorageKey(currentTable.tableName, currentTableTabId);
|
||||
|
|
@ -557,7 +601,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
<div style={{ width: `${width}px` }}>
|
||||
<ModernDatePicker
|
||||
label={column?.columnLabel || filter.columnName}
|
||||
value={value ? (typeof value === 'string' ? { from: new Date(value), to: new Date(value) } : value) : {}}
|
||||
value={value ? (typeof value === "string" ? { from: new Date(value), to: new Date(value) } : value) : {}}
|
||||
onChange={(dateRange) => {
|
||||
if (dateRange.from && dateRange.to) {
|
||||
// 기간이 선택되면 from과 to를 모두 저장
|
||||
|
|
@ -584,7 +628,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
);
|
||||
|
||||
case "select": {
|
||||
let options = selectOptions[filter.columnName] || [];
|
||||
const options = selectOptions[filter.columnName] || [];
|
||||
|
||||
// 중복 제거 (value 기준)
|
||||
const uniqueOptions = options.reduce(
|
||||
|
|
@ -598,13 +642,13 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
);
|
||||
|
||||
// 항상 다중선택 모드
|
||||
const selectedValues: string[] = Array.isArray(value) ? value : (value ? [value] : []);
|
||||
|
||||
const selectedValues: string[] = Array.isArray(value) ? value : value ? [value] : [];
|
||||
|
||||
// 선택된 값들의 라벨 표시
|
||||
const getDisplayText = () => {
|
||||
if (selectedValues.length === 0) return column?.columnLabel || "선택";
|
||||
if (selectedValues.length === 1) {
|
||||
const opt = uniqueOptions.find(o => o.value === selectedValues[0]);
|
||||
const opt = uniqueOptions.find((o) => o.value === selectedValues[0]);
|
||||
return opt?.label || selectedValues[0];
|
||||
}
|
||||
return `${selectedValues.length}개 선택됨`;
|
||||
|
|
@ -615,7 +659,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
if (checked) {
|
||||
newValues = [...selectedValues, optionValue];
|
||||
} else {
|
||||
newValues = selectedValues.filter(v => v !== optionValue);
|
||||
newValues = selectedValues.filter((v) => v !== optionValue);
|
||||
}
|
||||
handleFilterChange(filter.columnName, newValues.length > 0 ? newValues : "");
|
||||
};
|
||||
|
|
@ -628,7 +672,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
role="combobox"
|
||||
className={cn(
|
||||
"h-9 min-h-9 justify-between text-xs font-normal focus:ring-0 focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 sm:text-sm",
|
||||
selectedValues.length === 0 && "text-muted-foreground"
|
||||
selectedValues.length === 0 && "text-muted-foreground",
|
||||
)}
|
||||
style={{ width: `${width}px`, height: "36px", minHeight: "36px", outline: "none", boxShadow: "none" }}
|
||||
>
|
||||
|
|
@ -636,11 +680,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
style={{ width: `${width}px` }}
|
||||
align="start"
|
||||
>
|
||||
<PopoverContent className="p-0" style={{ width: `${width}px` }} align="start">
|
||||
<div className="max-h-60 overflow-auto">
|
||||
{uniqueOptions.length === 0 ? (
|
||||
<div className="text-muted-foreground px-3 py-2 text-xs">옵션 없음</div>
|
||||
|
|
@ -649,7 +689,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
{uniqueOptions.map((option, index) => (
|
||||
<div
|
||||
key={`${filter.columnName}-multi-${option.value}-${index}`}
|
||||
className="flex items-center space-x-2 rounded-sm px-2 py-1.5 hover:bg-accent cursor-pointer"
|
||||
className="hover:bg-accent flex cursor-pointer items-center space-x-2 rounded-sm px-2 py-1.5"
|
||||
onClick={() => handleMultiSelectChange(option.value, !selectedValues.includes(option.value))}
|
||||
>
|
||||
<Checkbox
|
||||
|
|
@ -668,7 +708,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full h-7 text-xs"
|
||||
className="h-7 w-full text-xs"
|
||||
onClick={() => handleFilterChange(filter.columnName, "")}
|
||||
>
|
||||
선택 초기화
|
||||
|
|
|
|||
Loading…
Reference in New Issue