diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-28 00:32:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-28 00:32:31 +0000 |
| commit | 20800b214145ee6056f94ca18fa1054f145eb977 (patch) | |
| tree | b5c8b27febe5b126e6d9ece115ea05eace33a020 /lib/pq/pq-review-table-new/vendors-table.tsx | |
| parent | e1344a5da1aeef8fbf0f33e1dfd553078c064ccc (diff) | |
(대표님) lib 파트 커밋
Diffstat (limited to 'lib/pq/pq-review-table-new/vendors-table.tsx')
| -rw-r--r-- | lib/pq/pq-review-table-new/vendors-table.tsx | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/lib/pq/pq-review-table-new/vendors-table.tsx b/lib/pq/pq-review-table-new/vendors-table.tsx new file mode 100644 index 00000000..e1c4cefe --- /dev/null +++ b/lib/pq/pq-review-table-new/vendors-table.tsx @@ -0,0 +1,308 @@ +"use client" + +import * as React from "react" +import { useRouter, useSearchParams } from "next/navigation" +import { Button } from "@/components/ui/button" +import { PanelLeftClose, PanelLeftOpen } from "lucide-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 { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" +import { getPQSubmissions } from "../service" +import { getColumns, PQSubmission } from "./vendors-table-columns" +import { VendorsTableToolbarActions } from "./vendors-table-toolbar-actions" +import { PQFilterSheet } from "./pq-filter-sheet" +import { cn } from "@/lib/utils" +// TablePresetManager 관련 import 추가 +import { useTablePresets } from "@/components/data-table/use-table-presets" +import { TablePresetManager } from "@/components/data-table/data-table-preset" +import { useMemo } from "react" + +interface PQSubmissionsTableProps { + promises: Promise<[Awaited<ReturnType<typeof getPQSubmissions>>]> + className?: string +} + +export function PQSubmissionsTable({ promises, className }: PQSubmissionsTableProps) { + const [rowAction, setRowAction] = React.useState<DataTableRowAction<PQSubmission> | null>(null) + const [isFilterPanelOpen, setIsFilterPanelOpen] = React.useState(false) + + const router = useRouter() + const searchParams = useSearchParams() + + // Container wrapper의 위치를 측정하기 위한 ref + const containerRef = React.useRef<HTMLDivElement>(null) + const [containerTop, setContainerTop] = React.useState(0) + + // Container 위치 측정 함수 - top만 측정 + const updateContainerBounds = React.useCallback(() => { + if (containerRef.current) { + const rect = containerRef.current.getBoundingClientRect() + setContainerTop(rect.top) + } + }, []) + + // 컴포넌트 마운트 시와 윈도우 리사이즈 시 위치 업데이트 + React.useEffect(() => { + updateContainerBounds() + + const handleResize = () => { + updateContainerBounds() + } + + window.addEventListener('resize', handleResize) + window.addEventListener('scroll', updateContainerBounds) + + return () => { + window.removeEventListener('resize', handleResize) + window.removeEventListener('scroll', updateContainerBounds) + } + }, [updateContainerBounds]) + + // Suspense 방식으로 데이터 처리 + const [promiseData] = React.use(promises) + const tableData = promiseData + + // 디버깅용 로그 + console.log("PQ Table Data:", { + dataLength: tableData.data?.length, + pageCount: tableData.pageCount, + sampleData: tableData.data?.[0] + }) + + // 초기 설정 정의 (RFQ와 동일한 패턴) + 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') || searchParams.get('pqBasicFilters') ? + JSON.parse(searchParams.get('basicFilters') || searchParams.get('pqBasicFilters')!) : [], + 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: ["actions"] }, // PQ는 actions를 오른쪽에 고정 + groupBy: [], + expandedRows: [] + }), [searchParams]) + + // DB 기반 프리셋 훅 사용 + const { + presets, + activePresetId, + hasUnsavedChanges, + isLoading: presetsLoading, + createPreset, + applyPreset, + updatePreset, + deletePreset, + setDefaultPreset, + renamePreset, + updateClientState, + getCurrentSettings, + } = useTablePresets<PQSubmission>('pq-submissions-table', initialSettings) + + const columns = React.useMemo( + () => getColumns({ setRowAction, router }), + [setRowAction, router] + ) + + // PQ 제출 필터링을 위한 필드 정의 + const filterFields: DataTableFilterField<PQSubmission>[] = [ + { id: "vendorName", label: "협력업체" }, + { id: "projectName", label: "프로젝트" }, + { id: "status", label: "상태" }, + ] + + // 고급 필터 필드 정의 + const advancedFilterFields: DataTableAdvancedFilterField<PQSubmission>[] = [ + { id: "requesterName", label: "요청자명", type: "text" }, + { id: "pqNumber", label: "PQ 번호", type: "text" }, + { id: "vendorName", label: "협력업체명", type: "text" }, + { id: "vendorCode", label: "협력업체 코드", type: "text" }, + { id: "type", label: "PQ 유형", type: "select", options: [ + { label: "일반 PQ", value: "GENERAL" }, + { label: "프로젝트 PQ", value: "PROJECT" }, + ]}, + { id: "projectName", label: "프로젝트명", type: "text" }, + { id: "status", label: "PQ 상태", type: "select", options: [ + { label: "요청됨", value: "REQUESTED" }, + { label: "진행 중", value: "IN_PROGRESS" }, + { label: "제출됨", value: "SUBMITTED" }, + { label: "승인됨", value: "APPROVED" }, + { label: "거부됨", value: "REJECTED" }, + ]}, + { id: "evaluationResult", label: "평가 결과", type: "select", options: [ + { label: "승인", value: "APPROVED" }, + { label: "보완", value: "SUPPLEMENT" }, + { label: "불가", value: "REJECTED" }, + ]}, + { id: "createdAt", label: "생성일", type: "date" }, + { id: "submittedAt", label: "제출일", type: "date" }, + { id: "approvedAt", label: "승인일", type: "date" }, + { id: "rejectedAt", label: "거부일", type: "date" }, + ] + + // 현재 설정 가져오기 + const currentSettings = useMemo(() => { + return getCurrentSettings() + }, [getCurrentSettings]) + + // useDataTable 초기 상태 설정 (RFQ와 동일한 패턴) + const initialState = useMemo(() => { + 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]) + + const { table } = useDataTable({ + data: tableData.data, + columns, + pageCount: tableData.pageCount, + rowCount: tableData.total || tableData.data.length, // total 추가 + filterFields, // RFQ와 달리 빈 배열이 아닌 실제 필터 필드 사용 + enablePinning: true, + enableAdvancedFilter: true, + initialState, + getRowId: (originalRow) => String(originalRow.id), + shallow: false, + clearOnDefault: true, + }) + + // 조회 버튼 클릭 핸들러 - PQFilterSheet에 전달 + const handleSearch = () => { + // Close the panel after search + setIsFilterPanelOpen(false) + } + + // Get active basic filter count + const getActiveBasicFilterCount = () => { + try { + const basicFilters = searchParams.get('basicFilters') || searchParams.get('pqBasicFilters') + return basicFilters ? JSON.parse(basicFilters).length : 0 + } catch (e) { + return 0 + } + } + + // Filter panel width + const FILTER_PANEL_WIDTH = 400; + + return ( + <> + {/* Filter Panel - fixed positioning으로 화면 최대 좌측에서 시작 */} + <div + className={cn( + "fixed left-0 bg-background border-r z-50 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: `${containerTop}px`, + height: `calc(100vh - ${containerTop}px)` + }} + > + {/* Filter Content */} + <div className="h-full"> + <PQFilterSheet + isOpen={isFilterPanelOpen} + onClose={() => setIsFilterPanelOpen(false)} + onSearch={handleSearch} + isLoading={false} + /> + </div> + </div> + + {/* Main Content Container */} + <div + ref={containerRef} + className={cn("relative w-full overflow-hidden", className)} + > + <div className="flex w-full h-full"> + {/* Main Content - 너비 조정으로 필터 패널 공간 확보 */} + <div + className="flex flex-col min-w-0 overflow-hidden transition-all duration-300 ease-in-out" + style={{ + width: isFilterPanelOpen ? `calc(100% - ${FILTER_PANEL_WIDTH}px)` : '100%', + marginLeft: isFilterPanelOpen ? `${FILTER_PANEL_WIDTH}px` : '0px' + }} + > + {/* Header Bar */} + <div className="flex items-center justify-between p-4 bg-background shrink-0"> + <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 || tableData.data.length}건</span> + )} + </div> + </div> + + {/* Table Content Area */} + <div className="flex-1 overflow-hidden" style={{ height: 'calc(100vh - 380px)' }}> + <div className="h-full w-full"> + <DataTable table={table} className="h-full"> + <DataTableAdvancedToolbar + table={table} + filterFields={advancedFilterFields} + shallow={false} + > + <div className="flex items-center gap-2"> + {/* DB 기반 테이블 프리셋 매니저 추가 */} + <TablePresetManager<PQSubmission> + presets={presets} + activePresetId={activePresetId} + currentSettings={currentSettings} + hasUnsavedChanges={hasUnsavedChanges} + isLoading={presetsLoading} + onCreatePreset={createPreset} + onUpdatePreset={updatePreset} + onDeletePreset={deletePreset} + onApplyPreset={applyPreset} + onSetDefaultPreset={setDefaultPreset} + onRenamePreset={renamePreset} + /> + + {/* 기존 툴바 액션들 */} + <VendorsTableToolbarActions table={table} /> + </div> + </DataTableAdvancedToolbar> + </DataTable> + </div> + </div> + </div> + </div> + </div> + </> + ) +}
\ No newline at end of file |
