summaryrefslogtreecommitdiff
path: root/lib/evaluation/table/evaluation-table.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-08-06 04:23:40 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-08-06 04:23:40 +0000
commitde2ac5a2860bc25180971e7a11f852d9d44675b7 (patch)
treeb931c363f2cb19e177a0a7b17190d5de2a82d709 /lib/evaluation/table/evaluation-table.tsx
parent6c549b0f264e9be4d60af38f9efc05b189d6849f (diff)
(대표님) 정기평가, 법적검토, 정책, 가입관련 처리 및 관련 컴포넌트 추가, 메뉴 변경
Diffstat (limited to 'lib/evaluation/table/evaluation-table.tsx')
-rw-r--r--lib/evaluation/table/evaluation-table.tsx223
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>