diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-15 01:19:49 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-15 01:19:49 +0000 |
| commit | 9eb8e80f4f736c4edffa650c685d1f170ca51aa1 (patch) | |
| tree | cae02173015c806cd5ea92be86938fe3bf14decd /lib/procurement-rfqs/table/rfq-table.tsx | |
| parent | 71f4dd76b57e77676d8886ac0a8b0bd0a7f24e62 (diff) | |
(대표님) 구매 요청사항 반영한 통합 rfq / 필터 개인화 / po-rfq
Diffstat (limited to 'lib/procurement-rfqs/table/rfq-table.tsx')
| -rw-r--r-- | lib/procurement-rfqs/table/rfq-table.tsx | 258 |
1 files changed, 212 insertions, 46 deletions
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> |
