From 9eb8e80f4f736c4edffa650c685d1f170ca51aa1 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 15 May 2025 01:19:49 +0000
Subject: (대표님) 구매 요청사항 반영한 통합 rfq / 필터 개인화 / po-rfq
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/procurement-rfqs/table/rfq-table.tsx | 258 +++++++++++++++++++++++++------
 1 file changed, 212 insertions(+), 46 deletions(-)

(limited to 'lib/procurement-rfqs/table/rfq-table.tsx')

diff --git a/lib/procurement-rfqs/table/rfq-table.tsx b/lib/procurement-rfqs/table/rfq-table.tsx
index 510f474d..23cd66fa 100644
--- a/lib/procurement-rfqs/table/rfq-table.tsx
+++ b/lib/procurement-rfqs/table/rfq-table.tsx
@@ -3,63 +3,96 @@
 import * as React from "react"
 import type {
   DataTableAdvancedFilterField,
-  DataTableFilterField,
   DataTableRowAction,
 } from "@/types/table"
 import { useDataTable } from "@/hooks/use-data-table"
 import { DataTable } from "@/components/data-table/data-table"
 import { getColumns, EditingCellState } from "./rfq-table-column"
-import { useEffect } from "react"
+import { useEffect, useCallback, useRef, useMemo } from "react"
 import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
 import { RFQTableToolbarActions } from "./rfq-table-toolbar-actions"
 import { ProcurementRfqsView } from "@/db/schema"
 import { getPORfqs } from "../services"
 import { toast } from "sonner"
-import { updateRfqRemark } from "@/lib/procurement-rfqs/services" // 구현 필요
+import { updateRfqRemark } from "@/lib/procurement-rfqs/services"
+import { useSearchParams } from "next/navigation"
+import { useTablePresets } from "@/components/data-table/use-table-presets"
+import { TablePresetManager } from "@/components/data-table/data-table-preset"
+import { Loader2 } from "lucide-react"
 
 interface RFQListTableProps {
   data?: Awaited<ReturnType<typeof getPORfqs>>;
   onSelectRFQ?: (rfq: ProcurementRfqsView | null) => void;
-  // 데이터 새로고침을 위한 콜백 추가
   onDataRefresh?: () => void;
-  maxHeight?: string | number; // Add this prop
+  maxHeight?: string | number;
 }
 
-// 보다 유연한 타입 정의
-type LocalDataType = Awaited<ReturnType<typeof getPORfqs>>;
-
 export function RFQListTable({
   data,
   onSelectRFQ,
   onDataRefresh,
   maxHeight
 }: RFQListTableProps) {
+  const searchParams = useSearchParams()
   const [rowAction, setRowAction] = React.useState<DataTableRowAction<ProcurementRfqsView> | null>(null)
-  // 인라인 에디팅을 위한 상태 추가
   const [editingCell, setEditingCell] = React.useState<EditingCellState | null>(null)
-  // 로컬 데이터를 관리하기 위한 상태 추가
-  const [localData, setLocalData] = React.useState<LocalDataType>(data || { data: [], pageCount: 0, total: 0 });
+  const [localData, setLocalData] = React.useState<typeof data>(data || { data: [], pageCount: 0, total: 0 })
+  const [isMounted, setIsMounted] = React.useState(false)
+  
+  // 초기 설정 정의
+  const initialSettings = React.useMemo(() => ({
+    page: parseInt(searchParams.get('page') || '1'),
+    perPage: parseInt(searchParams.get('perPage') || '10'),
+    sort: searchParams.get('sort') ? JSON.parse(searchParams.get('sort')!) : [{ id: "updatedAt", desc: true }],
+    filters: searchParams.get('filters') ? JSON.parse(searchParams.get('filters')!) : [],
+    joinOperator: (searchParams.get('joinOperator') as "and" | "or") || "and",
+    basicFilters: searchParams.get('basicFilters') ? JSON.parse(searchParams.get('basicFilters')!) : [],
+    basicJoinOperator: (searchParams.get('basicJoinOperator') as "and" | "or") || "and",
+    search: searchParams.get('search') || '',
+    from: searchParams.get('from') || undefined,
+    to: searchParams.get('to') || undefined,
+    columnVisibility: {},
+    columnOrder: [],
+    pinnedColumns: { left: [], right: [] },
+    groupBy: [],
+    expandedRows: []
+  }), [searchParams])
   
-  // 데이터가 변경될 때 로컬 데이터도 업데이트
+  // DB 기반 프리셋 훅 사용
+  const {
+    presets,
+    activePresetId,
+    hasUnsavedChanges,
+    isLoading: presetsLoading,
+    createPreset,
+    applyPreset,
+    updatePreset,
+    deletePreset,
+    setDefaultPreset,
+    renamePreset,
+    updateClientState,
+    getCurrentSettings,
+  } = useTablePresets<ProcurementRfqsView>('rfq-list-table', initialSettings)
+  
+  // 클라이언트 마운트 체크
+  useEffect(() => {
+    setIsMounted(true)
+  }, [])
+  
+  // 데이터 변경 감지
   useEffect(() => {
     setLocalData(data || { data: [], pageCount: 0, total: 0 })
   }, [data])
-
   
   // 비고 업데이트 함수
   const updateRemark = async (rfqId: number, remark: string) => {
     try {
-      // 낙관적 UI 업데이트 (로컬 데이터 먼저 갱신)
       if (localData && localData.data) {
-        // 로컬 데이터에서 해당 행 찾기
         const rowIndex = localData.data.findIndex(row => row.id === rfqId);
         if (rowIndex >= 0) {
-          // 불변성을 유지하면서 로컬 데이터 업데이트
           const newData = [...localData.data];
           newData[rowIndex] = { ...newData[rowIndex], remark };
-          
-          // 전체 데이터 구조 복사하여 업데이트
-          setLocalData({ ...localData, data: newData } as typeof localData);
+          setLocalData({ ...localData, data: newData });
         }
       }
       
@@ -67,8 +100,6 @@ export function RFQListTable({
       
       if (result.success) {
         toast.success("비고가 업데이트되었습니다");
-        
-        // 서버 데이터 리프레시 호출
         if (onDataRefresh) {
           onDataRefresh();
         }
@@ -80,29 +111,23 @@ export function RFQListTable({
       toast.error("업데이트 중 오류가 발생했습니다");
     }
   }
-
+  
   // 행 액션 처리
   useEffect(() => {
     if (rowAction) {
-      // 액션 유형에 따라 처리
       switch (rowAction.type) {
         case "select":
-          // 선택된 문서 처리
           if (onSelectRFQ) {
             onSelectRFQ(rowAction.row.original)
           }
           break;
         case "update":
-          // 업데이트 처리 로직
           console.log("Update rfq:", rowAction.row.original)
           break;
         case "delete":
-          // 삭제 처리 로직
           console.log("Delete rfq:", rowAction.row.original)
           break;
       }
-      
-      // 액션 처리 후 rowAction 초기화
       setRowAction(null)
     }
   }, [rowAction, onSelectRFQ])
@@ -116,11 +141,8 @@ export function RFQListTable({
     }),
     [setRowAction, editingCell, setEditingCell, updateRemark]
   )
-
-  
-  // Filter fields
-  const filterFields: DataTableFilterField<ProcurementRfqsView>[] = []
   
+  // 고급 필터 필드 정의
   const advancedFilterFields: DataTableAdvancedFilterField<ProcurementRfqsView>[] = [
     {
       id: "rfqCode",
@@ -142,7 +164,6 @@ export function RFQListTable({
       label: "자재명",
       type: "text",
     },
-    
     {
       id: "rfqSealedYn",
       label: "RFQ 밀봉여부",
@@ -170,38 +191,183 @@ export function RFQListTable({
     },
   ]
   
-  // useDataTable 훅으로 react-table 구성 - 로컬 데이터 사용하도록 수정
+  // 현재 설정 가져오기
+  const currentSettings = useMemo(() => {
+    return getCurrentSettings()
+  }, [getCurrentSettings])
+  
+  // useDataTable 초기 상태 설정
+  const initialState = useMemo(() => {
+    console.log('Setting initial state:', currentSettings)
+    return {
+      sorting: initialSettings.sort.filter(sortItem => {
+        const columnExists = columns.some(col => col.accessorKey === sortItem.id)
+        return columnExists
+      }) as any,
+      columnVisibility: currentSettings.columnVisibility,
+      columnPinning: currentSettings.pinnedColumns,
+    }
+  }, [currentSettings, initialSettings.sort, columns])
+  
+  // useDataTable 훅 설정
   const { table } = useDataTable({
     data: localData?.data || [],
     columns,
     pageCount: localData?.pageCount || 0,
-    rowCount: localData?.total || 0, // 총 레코드 수 추가
-    filterFields,
+    rowCount: localData?.total || 0,
+    filterFields: [],
     enablePinning: true,
     enableAdvancedFilter: true,
-    initialState: {
-      sorting: [{ id: "updatedAt", desc: true }],
-    },
+    initialState,
     getRowId: (originalRow) => String(originalRow.id),
     shallow: false,
     clearOnDefault: true,
     columnResizeMode: "onEnd",
   })
+  
+  // 테이블 상태 변경 감지 및 자동 저장
+  const lastKnownStateRef = useRef<{
+    columnVisibility: string
+    columnPinning: string
+    columnOrder: string[]
+  }>({
+    columnVisibility: '{}',
+    columnPinning: '{"left":[],"right":[]}',
+    columnOrder: []
+  })
+  
+  const checkAndUpdateTableState = useCallback(() => {
+    if (!presetsLoading  && !activePresetId) return
+    
+    try {
+      const currentVisibility = table.getState().columnVisibility
+      const currentPinning = table.getState().columnPinning
+      
+      // 컬럼 순서 가져오기
+      const allColumns = table.getAllColumns()
+      const leftPinned = table.getLeftHeaderGroups()[0]?.headers.map(h => h.column.id) || []
+      const rightPinned = table.getRightHeaderGroups()[0]?.headers.map(h => h.column.id) || []
+      const center = table.getCenterHeaderGroups()[0]?.headers.map(h => h.column.id) || []
+      const currentOrder = [...leftPinned, ...center, ...rightPinned]
+      
+      const visibilityString = JSON.stringify(currentVisibility)
+      const pinningString = JSON.stringify(currentPinning)
+      const orderString = JSON.stringify(currentOrder)
+      
+      // 실제 변경이 있을 때만 업데이트
+      if (
+        visibilityString !== lastKnownStateRef.current.columnVisibility ||
+        pinningString !== lastKnownStateRef.current.columnPinning ||
+        orderString !== JSON.stringify(lastKnownStateRef.current.columnOrder)
+      ) {
+        console.log('Table state changed, updating preset...')
+        
+        const newClientState = {
+          columnVisibility: currentVisibility,
+          columnOrder: currentOrder,
+          pinnedColumns: currentPinning,
+        }
+        
+        // 상태 업데이트 전에 기록
+        lastKnownStateRef.current = {
+          columnVisibility: visibilityString,
+          columnPinning: pinningString,
+          columnOrder: currentOrder
+        }
+        
+        updateClientState(newClientState)
+      }
+    } catch (error) {
+      console.error('Error checking table state:', error)
+    }
+  }, [activePresetId, table, updateClientState, presetsLoading ])
+  
+  // 주기적으로 테이블 상태 체크
+  useEffect(() => {
+    if (!isMounted || !activePresetId) return
+    
+    console.log('Starting table state polling')
+    const intervalId = setInterval(checkAndUpdateTableState, 500)
+    
+    return () => {
+      clearInterval(intervalId)
+      console.log('Stopped table state polling')
+    }
+  }, [isMounted, activePresetId, checkAndUpdateTableState])
+  
+  // 프리셋 적용 시 테이블 상태 업데이트
+  useEffect(() => {
+    if (isMounted && activePresetId && currentSettings) {
+      const settings = currentSettings
+      console.log('Applying preset settings to table:', settings)
+      
+      const currentVisibility = table.getState().columnVisibility
+      const currentPinning = table.getState().columnPinning
+      
+      if (
+        JSON.stringify(currentVisibility) !== JSON.stringify(settings.columnVisibility) ||
+        JSON.stringify(currentPinning) !== JSON.stringify(settings.pinnedColumns)
+      ) {
+        console.log('Updating table state to match preset...')
+        
+        // 테이블 상태 업데이트
+        table.setColumnVisibility(settings.columnVisibility)
+        table.setColumnPinning(settings.pinnedColumns)
+        
+        // 상태 저장소 업데이트
+        lastKnownStateRef.current = {
+          columnVisibility: JSON.stringify(settings.columnVisibility),
+          columnPinning: JSON.stringify(settings.pinnedColumns),
+          columnOrder: settings.columnOrder || []
+        }
+      }
+    }
+  }, [isMounted, activePresetId, currentSettings, table])
 
+  // 로딩 중일 때는 스켈레톤 표시
+  if (!isMounted) {
+    return (
+      <div className="w-full h-96 flex items-center justify-center">
+        <div className="flex flex-col items-center gap-2">
+          <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
+          <span className="text-sm text-muted-foreground">테이블 설정을 로드하는 중...</span>
+        </div>
+      </div>
+    )
+  }
+  
   return (
-    <div className="w-full overflow-auto">    
+    <div className="w-full overflow-auto">
       <DataTable table={table} maxHeight={maxHeight}>
         <DataTableAdvancedToolbar
           table={table}
           filterFields={advancedFilterFields}
           shallow={false}
         >
-        <RFQTableToolbarActions 
-          table={table}
-          localData={localData}
-          setLocalData={setLocalData}
-          onSuccess={onDataRefresh}
-        />
+          <div className="flex items-center gap-2">
+            {/* DB 기반 테이블 프리셋 매니저 */}
+            <TablePresetManager<ProcurementRfqsView>
+              presets={presets}
+              activePresetId={activePresetId}
+              currentSettings={currentSettings}
+              hasUnsavedChanges={hasUnsavedChanges}
+              isLoading={presetsLoading}
+              onCreatePreset={createPreset}
+              onUpdatePreset={updatePreset}
+              onDeletePreset={deletePreset}
+              onApplyPreset={applyPreset}
+              onSetDefaultPreset={setDefaultPreset}
+              onRenamePreset={renamePreset}
+            />
+            
+            {/* 기존 툴바 액션들 */}
+            <RFQTableToolbarActions 
+              table={table}
+              localData={localData}
+              setLocalData={setLocalData}
+              onSuccess={onDataRefresh}
+            />
+          </div>
         </DataTableAdvancedToolbar>
       </DataTable>
     </div>
-- 
cgit v1.2.3

