summaryrefslogtreecommitdiff
path: root/lib/procurement-rfqs/table/rfq-table.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-05-15 01:19:49 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-05-15 01:19:49 +0000
commit9eb8e80f4f736c4edffa650c685d1f170ca51aa1 (patch)
treecae02173015c806cd5ea92be86938fe3bf14decd /lib/procurement-rfqs/table/rfq-table.tsx
parent71f4dd76b57e77676d8886ac0a8b0bd0a7f24e62 (diff)
(대표님) 구매 요청사항 반영한 통합 rfq / 필터 개인화 / po-rfq
Diffstat (limited to 'lib/procurement-rfqs/table/rfq-table.tsx')
-rw-r--r--lib/procurement-rfqs/table/rfq-table.tsx258
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>