카드디스플레이 검색필터 구현
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState, useMemo, useCallback } from "react";
|
||||
import React, { useEffect, useState, useMemo, useCallback, useRef } from "react";
|
||||
import { ComponentRendererProps } from "@/types/component";
|
||||
import { CardDisplayConfig } from "./types";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
@@ -13,6 +13,8 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { useScreenContextOptional } from "@/contexts/ScreenContext";
|
||||
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
|
||||
import { useModalDataStore } from "@/stores/modalDataStore";
|
||||
import { useTableOptions } from "@/contexts/TableOptionsContext";
|
||||
import { TableFilter, ColumnVisibility, TableColumn } from "@/types/table-options";
|
||||
|
||||
export interface CardDisplayComponentProps extends ComponentRendererProps {
|
||||
config?: CardDisplayConfig;
|
||||
@@ -48,11 +50,32 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||
const splitPanelContext = useSplitPanelContext();
|
||||
const splitPanelPosition = screenContext?.splitPanelPosition;
|
||||
|
||||
// TableOptions Context (검색 필터 위젯 연동용)
|
||||
let tableOptionsContext: ReturnType<typeof useTableOptions> | null = null;
|
||||
try {
|
||||
tableOptionsContext = useTableOptions();
|
||||
} catch (e) {
|
||||
// Context가 없으면 (디자이너 모드) 무시
|
||||
}
|
||||
|
||||
// 테이블 데이터 상태 관리
|
||||
const [loadedTableData, setLoadedTableData] = useState<any[]>([]);
|
||||
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 필터 상태 (검색 필터 위젯에서 전달받은 필터)
|
||||
const [filters, setFiltersInternal] = useState<TableFilter[]>([]);
|
||||
|
||||
// 필터 상태 변경 래퍼 (로깅용)
|
||||
const setFilters = useCallback((newFilters: TableFilter[]) => {
|
||||
console.log("🎴 [CardDisplay] setFilters 호출됨:", {
|
||||
componentId: component.id,
|
||||
filtersCount: newFilters.length,
|
||||
filters: newFilters,
|
||||
});
|
||||
setFiltersInternal(newFilters);
|
||||
}, [component.id]);
|
||||
|
||||
// 카테고리 매핑 상태 (카테고리 코드 -> 라벨/색상)
|
||||
const [columnMeta, setColumnMeta] = useState<
|
||||
Record<string, { webType?: string; codeCategory?: string; inputType?: string }>
|
||||
@@ -380,6 +403,195 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||
}
|
||||
}, [screenContext, component.id, dataProvider]);
|
||||
|
||||
// TableOptionsContext에 테이블 등록 (검색 필터 위젯 연동용)
|
||||
const tableId = `card-display-${component.id}`;
|
||||
const tableNameToUse = tableName || component.componentConfig?.tableName || '';
|
||||
const tableLabel = component.componentConfig?.title || component.label || "카드 디스플레이";
|
||||
|
||||
// ref로 최신 데이터 참조 (useCallback 의존성 문제 해결)
|
||||
const loadedTableDataRef = useRef(loadedTableData);
|
||||
const categoryMappingsRef = useRef(categoryMappings);
|
||||
|
||||
useEffect(() => {
|
||||
loadedTableDataRef.current = loadedTableData;
|
||||
}, [loadedTableData]);
|
||||
|
||||
useEffect(() => {
|
||||
categoryMappingsRef.current = categoryMappings;
|
||||
}, [categoryMappings]);
|
||||
|
||||
// 필터가 변경되면 데이터 다시 로드 (테이블 리스트와 동일한 패턴)
|
||||
// 초기 로드 여부 추적
|
||||
const isInitialLoadRef = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tableNameToUse || isDesignMode) return;
|
||||
|
||||
// 초기 로드는 별도 useEffect에서 처리하므로 스킵
|
||||
if (isInitialLoadRef.current) {
|
||||
isInitialLoadRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const loadFilteredData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 필터 값을 검색 파라미터로 변환
|
||||
const searchParams: Record<string, any> = {};
|
||||
filters.forEach(filter => {
|
||||
if (filter.value !== undefined && filter.value !== null && filter.value !== '') {
|
||||
searchParams[filter.columnName] = filter.value;
|
||||
}
|
||||
});
|
||||
|
||||
console.log("🔍 [CardDisplay] 필터 적용 데이터 로드:", {
|
||||
tableName: tableNameToUse,
|
||||
filtersCount: filters.length,
|
||||
searchParams,
|
||||
});
|
||||
|
||||
// search 파라미터로 검색 조건 전달 (API 스펙에 맞게)
|
||||
const dataResponse = await tableTypeApi.getTableData(tableNameToUse, {
|
||||
page: 1,
|
||||
size: 50,
|
||||
search: searchParams,
|
||||
});
|
||||
|
||||
setLoadedTableData(dataResponse.data);
|
||||
|
||||
// 데이터 건수 업데이트
|
||||
if (tableOptionsContext) {
|
||||
tableOptionsContext.updateTableDataCount(tableId, dataResponse.data?.length || 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ [CardDisplay] 필터 적용 실패:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 필터 변경 시 항상 데이터 다시 로드 (빈 필터 = 전체 데이터)
|
||||
loadFilteredData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filters, tableNameToUse, isDesignMode, tableId]);
|
||||
|
||||
// 컬럼 고유 값 조회 함수 (select 타입 필터용)
|
||||
const getColumnUniqueValues = useCallback(async (columnName: string): Promise<Array<{ label: string; value: string }>> => {
|
||||
if (!tableNameToUse) return [];
|
||||
|
||||
try {
|
||||
// 현재 로드된 데이터에서 고유 값 추출
|
||||
const uniqueValues = new Set<string>();
|
||||
loadedTableDataRef.current.forEach(row => {
|
||||
const value = row[columnName];
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
uniqueValues.add(String(value));
|
||||
}
|
||||
});
|
||||
|
||||
// 카테고리 매핑이 있으면 라벨 적용
|
||||
const mapping = categoryMappingsRef.current[columnName];
|
||||
return Array.from(uniqueValues).map(value => ({
|
||||
value,
|
||||
label: mapping?.[value]?.label || value,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error(`❌ [CardDisplay] 고유 값 조회 실패: ${columnName}`, error);
|
||||
return [];
|
||||
}
|
||||
}, [tableNameToUse]);
|
||||
|
||||
// TableOptionsContext에 등록
|
||||
// registerTable과 unregisterTable 함수 참조 저장 (의존성 안정화)
|
||||
const registerTableRef = useRef(tableOptionsContext?.registerTable);
|
||||
const unregisterTableRef = useRef(tableOptionsContext?.unregisterTable);
|
||||
|
||||
// setFiltersInternal을 ref로 저장 (등록 시 최신 함수 사용)
|
||||
const setFiltersRef = useRef(setFiltersInternal);
|
||||
const getColumnUniqueValuesRef = useRef(getColumnUniqueValues);
|
||||
|
||||
useEffect(() => {
|
||||
registerTableRef.current = tableOptionsContext?.registerTable;
|
||||
unregisterTableRef.current = tableOptionsContext?.unregisterTable;
|
||||
}, [tableOptionsContext]);
|
||||
|
||||
useEffect(() => {
|
||||
setFiltersRef.current = setFiltersInternal;
|
||||
}, [setFiltersInternal]);
|
||||
|
||||
useEffect(() => {
|
||||
getColumnUniqueValuesRef.current = getColumnUniqueValues;
|
||||
}, [getColumnUniqueValues]);
|
||||
|
||||
// 테이블 등록 (한 번만 실행, 컬럼 변경 시에만 재등록)
|
||||
const columnsKey = JSON.stringify(loadedTableColumns.map((col: any) => col.columnName || col.column_name));
|
||||
|
||||
useEffect(() => {
|
||||
if (!registerTableRef.current || !unregisterTableRef.current) return;
|
||||
if (isDesignMode || !tableNameToUse || loadedTableColumns.length === 0) return;
|
||||
|
||||
// 컬럼 정보를 TableColumn 형식으로 변환
|
||||
const columns: TableColumn[] = loadedTableColumns.map((col: any) => ({
|
||||
columnName: col.columnName || col.column_name,
|
||||
columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
|
||||
inputType: columnMeta[col.columnName || col.column_name]?.inputType || 'text',
|
||||
visible: true,
|
||||
width: 200,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
}));
|
||||
|
||||
// onFilterChange는 ref를 통해 최신 함수를 호출하는 래퍼 사용
|
||||
const onFilterChangeWrapper = (newFilters: TableFilter[]) => {
|
||||
console.log("🎴 [CardDisplay] onFilterChange 래퍼 호출:", {
|
||||
tableId,
|
||||
filtersCount: newFilters.length,
|
||||
});
|
||||
setFiltersRef.current(newFilters);
|
||||
};
|
||||
|
||||
const getColumnUniqueValuesWrapper = async (columnName: string) => {
|
||||
return getColumnUniqueValuesRef.current(columnName);
|
||||
};
|
||||
|
||||
const registration = {
|
||||
tableId,
|
||||
label: tableLabel,
|
||||
tableName: tableNameToUse,
|
||||
columns,
|
||||
dataCount: loadedTableData.length,
|
||||
onFilterChange: onFilterChangeWrapper,
|
||||
onGroupChange: () => {}, // 카드 디스플레이는 그룹핑 미지원
|
||||
onColumnVisibilityChange: () => {}, // 카드 디스플레이는 컬럼 가시성 미지원
|
||||
getColumnUniqueValues: getColumnUniqueValuesWrapper,
|
||||
};
|
||||
|
||||
console.log("📋 [CardDisplay] TableOptionsContext에 등록:", {
|
||||
tableId,
|
||||
tableName: tableNameToUse,
|
||||
columnsCount: columns.length,
|
||||
dataCount: loadedTableData.length,
|
||||
});
|
||||
|
||||
registerTableRef.current(registration);
|
||||
|
||||
const unregister = unregisterTableRef.current;
|
||||
const currentTableId = tableId;
|
||||
|
||||
return () => {
|
||||
console.log("📋 [CardDisplay] TableOptionsContext에서 해제:", currentTableId);
|
||||
unregister(currentTableId);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
isDesignMode,
|
||||
tableId,
|
||||
tableNameToUse,
|
||||
tableLabel,
|
||||
columnsKey, // 컬럼 변경 시에만 재등록
|
||||
]);
|
||||
|
||||
// 로딩 중인 경우 로딩 표시
|
||||
if (loading) {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user