From 5daef415ad13ad88ee4af6da9ef326a4dc5f2aee Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 24 Dec 2025 14:46:51 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B2=80=EC=83=89=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EA=B3=A0=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/InteractiveScreenViewerDynamic.tsx | 10 +- frontend/components/unified/UnifiedList.tsx | 3 - frontend/contexts/TableOptionsContext.tsx | 59 +++--- .../lib/registry/DynamicComponentRenderer.tsx | 42 ++++- .../table-list/TableListComponent.tsx | 15 +- .../table-search-widget/TableSearchWidget.tsx | 170 +++++++++++------- 6 files changed, 183 insertions(+), 116 deletions(-) diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index 6eebe5d2..0b26e91e 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -542,10 +542,12 @@ export const InteractiveScreenViewerDynamic: React.FC((props, [config.columns], ); - // 디버깅: config.cardConfig 확인 - console.log("📋 UnifiedList config.cardConfig:", config.cardConfig); - // TableListComponent에 전달할 component 객체 생성 const componentObj = useMemo( () => ({ diff --git a/frontend/contexts/TableOptionsContext.tsx b/frontend/contexts/TableOptionsContext.tsx index f49e82de..e3878f36 100644 --- a/frontend/contexts/TableOptionsContext.tsx +++ b/frontend/contexts/TableOptionsContext.tsx @@ -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( - undefined -); +const TableOptionsContext = createContext(undefined); -export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({ - children, -}) => { - const [registeredTables, setRegisteredTables] = useState< - Map - >(new Map()); +export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [registeredTables, setRegisteredTables] = useState>(new Map()); const [selectedTableId, setSelectedTableId] = useState(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 ( = ({ * 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; }; - diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index b6312c73..b2b2df2e 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -720,17 +720,53 @@ export const DynamicComponentRenderer: React.FC = }; // 렌더러가 클래스인지 함수인지 확인 - 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 ; } } diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 8de26e21..ffaacfc3 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -342,14 +342,19 @@ export const TableListComponent: React.FC = ({ const newSearchValues: Record = {}; 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); // 필터 변경 시 첫 페이지로 diff --git a/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx b/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx index 2bf77042..2422c89e 100644 --- a/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx +++ b/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx @@ -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>>({}); @@ -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(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
{ 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 - +
{uniqueOptions.length === 0 ? (
옵션 없음
@@ -649,7 +689,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table {uniqueOptions.map((option, index) => (
handleMultiSelectChange(option.value, !selectedValues.includes(option.value))} > handleFilterChange(filter.columnName, "")} > 선택 초기화