From de2ac5a2860bc25180971e7a11f852d9d44675b7 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 6 Aug 2025 04:23:40 +0000
Subject: (대표님) 정기평가, 법적검토, 정책, 가입관련 처리 및 관련 컴포넌트
 추가, 메뉴 변경
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/evaluation/table/evaluation-table.tsx | 223 ++++++++++++++++++++++++------
 1 file changed, 179 insertions(+), 44 deletions(-)

(limited to 'lib/evaluation/table/evaluation-table.tsx')

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>
-- 
cgit v1.2.3

