summaryrefslogtreecommitdiff
path: root/lib/procurement-rfqs/table/rfq-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/procurement-rfqs/table/rfq-table.tsx')
-rw-r--r--lib/procurement-rfqs/table/rfq-table.tsx411
1 files changed, 224 insertions, 187 deletions
diff --git a/lib/procurement-rfqs/table/rfq-table.tsx b/lib/procurement-rfqs/table/rfq-table.tsx
index 23cd66fa..ca976172 100644
--- a/lib/procurement-rfqs/table/rfq-table.tsx
+++ b/lib/procurement-rfqs/table/rfq-table.tsx
@@ -1,44 +1,86 @@
"use client"
import * as React from "react"
+import { useSearchParams } from "next/navigation"
+import { Button } from "@/components/ui/button"
+import { PanelLeftClose, PanelLeftOpen } from "lucide-react"
import type {
DataTableAdvancedFilterField,
DataTableRowAction,
} from "@/types/table"
+import {
+ ResizablePanelGroup,
+ ResizablePanel,
+ ResizableHandle,
+} from "@/components/ui/resizable"
+
import { useDataTable } from "@/hooks/use-data-table"
import { DataTable } from "@/components/data-table/data-table"
import { getColumns, EditingCellState } from "./rfq-table-column"
-import { useEffect, useCallback, useRef, useMemo } from "react"
+import { useEffect, useCallback, useRef, useMemo, useLayoutEffect } 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 { 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"
+import { RFQFilterSheet } from "./rfq-filter-sheet"
+import { RfqDetailTables } from "./detail-table/rfq-detail-table"
+import { cn } from "@/lib/utils"
interface RFQListTableProps {
- data?: Awaited<ReturnType<typeof getPORfqs>>;
- onSelectRFQ?: (rfq: ProcurementRfqsView | null) => void;
- onDataRefresh?: () => void;
- maxHeight?: string | number;
+ promises: Promise<[Awaited<ReturnType<typeof getPORfqs>>]>
+ className?: string;
+ calculatedHeight?: string; // 계산된 높이 추가
}
export function RFQListTable({
- data,
- onSelectRFQ,
- onDataRefresh,
- maxHeight
+ promises,
+ className,
+ calculatedHeight
}: RFQListTableProps) {
const searchParams = useSearchParams()
+
+ // 필터 패널 상태
+ const [isFilterPanelOpen, setIsFilterPanelOpen] = React.useState(false)
+
+ // 선택된 RFQ 상태
+ const [selectedRfq, setSelectedRfq] = React.useState<ProcurementRfqsView | null>(null)
+
+ // 패널 collapse 상태
+ const [isTopCollapsed, setIsTopCollapsed] = React.useState(false)
+ const [panelHeight, setPanelHeight] = React.useState<number>(55)
+
+ // refs
+ const headerRef = React.useRef<HTMLDivElement>(null)
+
+ // 고정 높이 설정을 위한 상수 (실제 측정값으로 조정 필요)
+ const LAYOUT_HEADER_HEIGHT = 64 // Layout Header 높이
+ const LAYOUT_FOOTER_HEIGHT = 60 // Layout Footer 높이 (있다면 실제 값)
+ const LOCAL_HEADER_HEIGHT = 72 // 로컬 헤더 바 높이 (p-4 + border)
+ const FILTER_PANEL_WIDTH = 400 // 필터 패널 너비
+
+ // 높이 계산
+ // 필터 패널 높이 - Layout Header와 Footer 사이
+ const FIXED_FILTER_HEIGHT = `calc(100vh - ${LAYOUT_HEADER_HEIGHT*2}px)`
+
+ console.log(calculatedHeight)
+
+ // 테이블 컨텐츠 높이 - 전달받은 높이에서 로컬 헤더 제외
+ const FIXED_TABLE_HEIGHT = calculatedHeight
+ ? `calc(${calculatedHeight} - ${LOCAL_HEADER_HEIGHT}px)`
+ : `calc(100vh - ${LAYOUT_HEADER_HEIGHT + LAYOUT_FOOTER_HEIGHT + LOCAL_HEADER_HEIGHT+76}px)` // fallback
+
+ // Suspense 방식으로 데이터 처리
+ const [promiseData] = React.use(promises)
+ const tableData = promiseData
+
const [rowAction, setRowAction] = React.useState<DataTableRowAction<ProcurementRfqsView> | null>(null)
const [editingCell, setEditingCell] = React.useState<EditingCellState | null>(null)
- 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'),
@@ -70,39 +112,16 @@ export function RFQListTable({
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 {
- 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 });
- }
- }
-
const result = await updateRfqRemark(rfqId, remark);
if (result.success) {
toast.success("비고가 업데이트되었습니다");
- if (onDataRefresh) {
- onDataRefresh();
- }
} else {
toast.error(result.message || "업데이트 중 오류가 발생했습니다");
}
@@ -117,9 +136,7 @@ export function RFQListTable({
if (rowAction) {
switch (rowAction.type) {
case "select":
- if (onSelectRFQ) {
- onSelectRFQ(rowAction.row.original)
- }
+ setSelectedRfq(rowAction.row.original)
break;
case "update":
console.log("Update rfq:", rowAction.row.original)
@@ -130,7 +147,7 @@ export function RFQListTable({
}
setRowAction(null)
}
- }, [rowAction, onSelectRFQ])
+ }, [rowAction])
const columns = React.useMemo(
() => getColumns({
@@ -198,7 +215,6 @@ export function RFQListTable({
// 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)
@@ -209,167 +225,188 @@ export function RFQListTable({
}
}, [currentSettings, initialSettings.sort, columns])
- // useDataTable 훅 설정
+ // useDataTable 훅 설정 (PQ와 동일한 설정)
const { table } = useDataTable({
- data: localData?.data || [],
+ data: tableData?.data || [],
columns,
- pageCount: localData?.pageCount || 0,
- rowCount: localData?.total || 0,
- filterFields: [],
+ pageCount: tableData?.pageCount || 0,
+ rowCount: tableData?.total || 0,
+ filterFields: [], // PQ와 동일하게 빈 배열
enablePinning: true,
enableAdvancedFilter: true,
initialState,
getRowId: (originalRow) => String(originalRow.id),
- shallow: false,
+ shallow: false, // PQ와 동일하게 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
-
+
+ // 조회 버튼 클릭 핸들러
+ const handleSearch = () => {
+ setIsFilterPanelOpen(false)
+ }
+
+ // Get active basic filter count (PQ와 동일한 방식)
+ const getActiveBasicFilterCount = () => {
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')
+ const basicFilters = searchParams.get('basicFilters')
+ return basicFilters ? JSON.parse(basicFilters).length : 0
+ } catch (e) {
+ return 0
}
- }, [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>
+ console.log(panelHeight)
+
+ return (
+ <div
+ className={cn("flex flex-col relative", className)}
+ style={{ height: calculatedHeight }}
+ >
+ {/* Filter Panel - 계산된 높이 적용 */}
+ <div
+ className={cn(
+ "fixed left-0 bg-background border-r z-30 flex flex-col transition-all duration-300 ease-in-out overflow-hidden",
+ isFilterPanelOpen ? "border-r shadow-lg" : "border-r-0"
+ )}
+ style={{
+ width: isFilterPanelOpen ? `${FILTER_PANEL_WIDTH}px` : '0px',
+ top: `${LAYOUT_HEADER_HEIGHT*2}px`,
+ height: FIXED_FILTER_HEIGHT
+ }}
+ >
+ {/* Filter Content */}
+ <div className="h-full">
+ <RFQFilterSheet
+ isOpen={isFilterPanelOpen}
+ onClose={() => setIsFilterPanelOpen(false)}
+ onSearch={handleSearch}
+ isLoading={false}
+ />
</div>
</div>
- )
- }
-
- return (
- <div className="w-full overflow-auto">
- <DataTable table={table} maxHeight={maxHeight}>
- <DataTableAdvancedToolbar
- table={table}
- filterFields={advancedFilterFields}
- shallow={false}
+
+ {/* Main Content */}
+ <div
+ className="flex flex-col transition-all duration-300 ease-in-out"
+ style={{
+ width: isFilterPanelOpen ? `calc(100% - ${FILTER_PANEL_WIDTH}px)` : '100%',
+ marginLeft: isFilterPanelOpen ? `${FILTER_PANEL_WIDTH}px` : '0px',
+ height: '100%'
+ }}
+ >
+ {/* Header Bar - 고정 높이 */}
+ <div
+ ref={headerRef}
+ className="flex items-center justify-between p-4 bg-background border-b"
+ style={{
+ height: `${LOCAL_HEADER_HEIGHT}px`,
+ flexShrink: 0
+ }}
>
- <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 className="flex items-center gap-3">
+ <Button
+ variant="outline"
+ size="sm"
+ type='button'
+ onClick={() => setIsFilterPanelOpen(!isFilterPanelOpen)}
+ className="flex items-center shadow-sm"
+ >
+ {isFilterPanelOpen ? <PanelLeftClose className="size-4"/> : <PanelLeftOpen className="size-4"/>}
+ {getActiveBasicFilterCount() > 0 && (
+ <span className="ml-2 bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
+ {getActiveBasicFilterCount()}
+ </span>
+ )}
+ </Button>
+ </div>
+
+ {/* Right side info */}
+ <div className="text-sm text-muted-foreground">
+ {tableData && (
+ <span>총 {tableData.total || 0}건</span>
+ )}
</div>
- </DataTableAdvancedToolbar>
- </DataTable>
+ </div>
+
+ {/* Table Content Area - 계산된 높이 사용 */}
+ <div
+ className="relative bg-background"
+ style={{
+ height: FIXED_TABLE_HEIGHT,
+ display: 'grid',
+ gridTemplateRows: '1fr',
+ gridTemplateColumns: '1fr'
+ }}
+ >
+ <ResizablePanelGroup
+ direction="vertical"
+ className="w-full h-full"
+ >
+ <ResizablePanel
+ defaultSize={60}
+ minSize={25}
+ maxSize={75}
+ collapsible={false}
+ onResize={(size) => {
+ setPanelHeight(size)
+ }}
+ className="flex flex-col overflow-hidden"
+ >
+ {/* 상단 테이블 영역 */}
+ <div className="flex-1 min-h-0 overflow-hidden">
+ <DataTable
+ table={table}
+ // className="h-full"
+ maxHeight={`${panelHeight*0.5}vh`}
+ >
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ >
+ <div className="flex items-center gap-2">
+ <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={tableData}
+ setLocalData={() => {}}
+ onSuccess={() => {}}
+ />
+ </div>
+ </DataTableAdvancedToolbar>
+ </DataTable>
+ </div>
+ </ResizablePanel>
+
+ <ResizableHandle withHandle />
+
+ <ResizablePanel
+ minSize={25}
+ defaultSize={40}
+ collapsible={false}
+ className="flex flex-col overflow-hidden"
+ >
+ {/* 하단 상세 테이블 영역 */}
+ <div className="flex-1 min-h-0 overflow-hidden bg-background">
+ <RfqDetailTables selectedRfq={selectedRfq} maxHeight={`${(100-panelHeight)*0.4}vh`}/>
+ </div>
+ </ResizablePanel>
+ </ResizablePanelGroup>
+ </div>
+ </div>
</div>
)
} \ No newline at end of file