diff options
Diffstat (limited to 'lib/evaluation/table/evaluation-table.tsx')
| -rw-r--r-- | lib/evaluation/table/evaluation-table.tsx | 223 |
1 files changed, 179 insertions, 44 deletions
diff --git a/lib/evaluation/table/evaluation-table.tsx b/lib/evaluation/table/evaluation-table.tsx index 257225c8..4404967a 100644 --- a/lib/evaluation/table/evaluation-table.tsx +++ b/lib/evaluation/table/evaluation-table.tsx @@ -28,7 +28,7 @@ import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-adv import { cn } from "@/lib/utils" import { useTablePresets } from "@/components/data-table/use-table-presets" import { TablePresetManager } from "@/components/data-table/data-table-preset" -import { PeriodicEvaluationFilterSheet } from "./evaluation-filter-sheet" +import { PeriodicEvaluationFilterSheet } from "./evaluation-filter-sheet" // ✅ 올바른 컴포넌트 이름 import { getPeriodicEvaluationsColumns } from "./evaluation-columns" import { PeriodicEvaluationView, PeriodicEvaluationAggregatedView } from "@/db/schema" import { @@ -300,9 +300,20 @@ export function PeriodicEvaluationsTable({ const [detailedCount, setDetailedCount] = React.useState<number | undefined>(undefined) const [aggregatedCount, setAggregatedCount] = React.useState<number | undefined>(undefined) + // ✅ 외부 필터 상태 (폼에서 전달받은 필터) - EvaluationTargetsTable 패턴과 동일 const [externalFilters, setExternalFilters] = React.useState<any[]>([]); const [externalJoinOperator, setExternalJoinOperator] = React.useState<"and" | "or">("and"); + + // ✅ 폼에서 전달받은 필터를 처리하는 핸들러 - EvaluationTargetsTable 패턴과 동일 + const handleFiltersApply = React.useCallback((filters: any[], joinOperator: "and" | "or") => { + console.log("=== 폼에서 필터 전달받음 ===", filters, joinOperator); + setExternalFilters(filters); + setExternalJoinOperator(joinOperator); + // 필터 적용 후 패널 닫기 + setIsFilterPanelOpen(false); + }, []); + // ✅ 뷰 모드 변경 시 URL 업데이트 const handleViewModeChange = React.useCallback((newMode: "detailed" | "aggregated") => { setViewMode(newMode); @@ -321,14 +332,116 @@ export function PeriodicEvaluationsTable({ router.push(`?${newSearchParams.toString()}`, { scroll: false }) }, [router, searchParams]) - const handleFiltersApply = React.useCallback((filters: any[], joinOperator: "and" | "or") => { - console.log("=== 폼에서 필터 전달받음 ===", filters, joinOperator); - setExternalFilters(filters); - setExternalJoinOperator(joinOperator); - setIsFilterPanelOpen(false); - }, []); + const searchString = React.useMemo( + () => searchParams.toString(), + [searchParams] + ) + + const getSearchParam = React.useCallback( + (key: string, def = "") => + new URLSearchParams(searchString).get(key) ?? def, + [searchString] + ) + + // ✅ 초기 데이터 설정 - EvaluationTargetsTable 패턴과 동일 + const [initialPromiseData] = React.use(promises) + const [tableData, setTableData] = React.useState(initialPromiseData) + const [isDataLoading, setIsDataLoading] = React.useState(false) + + // ✅ URL 필터 변경 감지 및 데이터 새로고침 - EvaluationTargetsTable 패턴과 동일 + React.useEffect(() => { + const refetchData = async () => { + try { + setIsDataLoading(true) + + // 현재 URL 파라미터 기반으로 새 검색 파라미터 생성 + const currentFilters = getSearchParam("filters") + const currentJoinOperator = getSearchParam("joinOperator", "and") + const currentPage = parseInt(getSearchParam("page", "1")) + const currentPerPage = parseInt(getSearchParam("perPage", "10")) + const currentSort = getSearchParam('sort') ? JSON.parse(getSearchParam('sort')!) : [{ id: "createdAt", desc: true }] + const currentSearch = getSearchParam("search", "") + const currentAggregated = getSearchParam("aggregated") === "true" + + const searchParams = { + filters: currentFilters ? JSON.parse(currentFilters) : [], + joinOperator: currentJoinOperator as "and" | "or", + page: currentPage, + perPage: currentPerPage, + sort: currentSort, + search: currentSearch, + evaluationYear: evaluationYear, + aggregated: currentAggregated + } + + console.log("=== 새 데이터 요청 ===", searchParams) + + // 서버 액션 직접 호출 + const newData = await getPeriodicEvaluationsWithAggregation(searchParams) + setTableData(newData) + + console.log("=== 데이터 업데이트 완료 ===", newData.data.length, "건") + } catch (error) { + console.error("데이터 새로고침 오류:", error) + } finally { + setIsDataLoading(false) + } + } + + // 필터나 검색 파라미터가 변경되면 데이터 새로고침 (디바운스 적용) + const timeoutId = setTimeout(() => { + // 필터, 검색, 페이지네이션, 정렬 중 하나라도 변경되면 새로고침 + const hasChanges = getSearchParam("filters") || + getSearchParam("search") || + getSearchParam("page") !== "1" || + getSearchParam("perPage") !== "10" || + getSearchParam("sort") || + getSearchParam("aggregated") + + if (hasChanges) { + refetchData() + } + }, 300) // 디바운스 시간 단축 + + return () => clearTimeout(timeoutId) + }, [searchString, evaluationYear, getSearchParam]) - // 컨테이너 위치 추적 + const refreshData = React.useCallback(async () => { + try { + setIsDataLoading(true) + + // 현재 URL 파라미터로 데이터 새로고침 + const currentFilters = getSearchParam("filters") + const currentJoinOperator = getSearchParam("joinOperator", "and") + const currentPage = parseInt(getSearchParam("page", "1")) + const currentPerPage = parseInt(getSearchParam("perPage", "10")) + const currentSort = getSearchParam('sort') ? JSON.parse(getSearchParam('sort')!) : [{ id: "createdAt", desc: true }] + const currentSearch = getSearchParam("search", "") + const currentAggregated = getSearchParam("aggregated") === "true" + + const searchParams = { + filters: currentFilters ? JSON.parse(currentFilters) : [], + joinOperator: currentJoinOperator as "and" | "or", + page: currentPage, + perPage: currentPerPage, + sort: currentSort, + search: currentSearch, + evaluationYear: evaluationYear, + aggregated: currentAggregated + } + + const newData = await getPeriodicEvaluationsWithAggregation(searchParams) + setTableData(newData) + + console.log("=== 데이터 새로고침 완료 ===", newData.data.length, "건") + } catch (error) { + console.error("데이터 새로고침 오류:", error) + } finally { + setIsDataLoading(false) + } + }, [evaluationYear, getSearchParam]) + + // 컨테이너 위치 추적 - EvaluationTargetsTable 패턴과 동일 const containerRef = React.useRef<HTMLDivElement>(null) const [containerTop, setContainerTop] = React.useState(0) @@ -347,42 +460,47 @@ export function PeriodicEvaluationsTable({ React.useEffect(() => { updateContainerBounds() - const throttledHandler = () => { - let timeoutId: NodeJS.Timeout - return () => { - clearTimeout(timeoutId) - timeoutId = setTimeout(updateContainerBounds, 16) - } + + const handleResize = () => { + updateContainerBounds() } - - const handler = throttledHandler() - window.addEventListener('resize', updateContainerBounds) - window.addEventListener('scroll', handler) - + + window.addEventListener('resize', handleResize) + window.addEventListener('scroll', updateContainerBounds) + return () => { - window.removeEventListener('resize', updateContainerBounds) - window.removeEventListener('scroll', handler) + window.removeEventListener('resize', handleResize) + window.removeEventListener('scroll', updateContainerBounds) } }, [updateContainerBounds]) - // 데이터 로드 - const [promiseData] = React.use(promises) - const tableData = promiseData + const parseSearchParamHelper = React.useCallback((key: string, defaultValue: any): any => { + try { + const value = getSearchParam(key) + return value ? JSON.parse(value) : defaultValue + } catch { + return defaultValue + } + }, [getSearchParam]) + + const parseSearchParam = <T,>(key: string, defaultValue: T): T => { + return parseSearchParamHelper(key, defaultValue) + } // 테이블 설정 const initialSettings = React.useMemo(() => ({ - page: currentParams.page || 1, - perPage: currentParams.perPage || 10, - sort: currentParams.sort || [{ id: "createdAt", desc: true }], - filters: currentParams.filters || [], - joinOperator: currentParams.joinOperator || "and", - search: "", + page: parseInt(getSearchParam("page", "1")), + perPage: parseInt(getSearchParam("perPage", "10")), + sort: getSearchParam('sort') ? JSON.parse(getSearchParam('sort')!) : [{ id: "createdAt", desc: true }], + filters: parseSearchParam("filters", []), + joinOperator: (getSearchParam("joinOperator") as "and" | "or") || "and", + search: getSearchParam("search", ""), columnVisibility: {}, columnOrder: [], pinnedColumns: { left: [], right: ["actions"] }, groupBy: [], expandedRows: [] - }), [currentParams]) + }), [getSearchParam, parseSearchParam]) const { presets, @@ -469,11 +587,17 @@ export function PeriodicEvaluationsTable({ const getActiveFilterCount = React.useCallback(() => { try { - return currentParams.filters?.length || 0; + // URL에서 현재 필터 수 확인 + const filtersParam = getSearchParam("filters") + if (filtersParam) { + const filters = JSON.parse(filtersParam) + return Array.isArray(filters) ? filters.length : 0 + } + return 0 } catch { - return 0; + return 0 } - }, [currentParams.filters]); + }, [getSearchParam]) const FILTER_PANEL_WIDTH = 400; @@ -491,14 +615,13 @@ export function PeriodicEvaluationsTable({ height: `calc(100vh - ${containerTop}px)` }} > - <div className="h-full"> - <PeriodicEvaluationFilterSheet - isOpen={isFilterPanelOpen} - onClose={() => setIsFilterPanelOpen(false)} - onFiltersApply={handleFiltersApply} - isLoading={false} - /> - </div> + {/* ✅ 올바른 컴포넌트 사용 */} + <PeriodicEvaluationFilterSheet + isOpen={isFilterPanelOpen} + onClose={() => setIsFilterPanelOpen(false)} + onFiltersApply={handleFiltersApply} // ✅ 필터 적용 콜백 전달 + isLoading={false} + /> </div> {/* Main Content Container */} @@ -567,9 +690,18 @@ export function PeriodicEvaluationsTable({ </div> {/* Table Content Area */} - <div className="flex-1 overflow-hidden" style={{ height: 'calc(100vh - 500px)' }}> + <div className="flex-1 overflow-hidden relative" style={{ height: 'calc(100vh - 500px)' }}> + {isDataLoading && ( + <div className="absolute inset-0 bg-background/50 backdrop-blur-sm z-10 flex items-center justify-center"> + <div className="flex items-center gap-2 text-sm text-muted-foreground"> + <div className="w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin" /> + 필터링 중... + </div> + </div> + )} <div className="h-full w-full"> <DataTable table={table} className="h-full"> + {/* ✅ EvaluationTargetsTable 패턴과 동일하게 수정 */} <DataTableAdvancedToolbar table={table} filterFields={advancedFilterFields} @@ -596,7 +728,10 @@ export function PeriodicEvaluationsTable({ onRenamePreset={renamePreset} /> - <PeriodicEvaluationsTableToolbarActions table={table} /> + <PeriodicEvaluationsTableToolbarActions + table={table} + onRefresh={refreshData} + /> </div> </DataTableAdvancedToolbar> </DataTable> |
