diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-24 22:41:52 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-24 22:41:52 +0900 |
| commit | 25b2561bf17128b96f023c977efb5cb51da0b4aa (patch) | |
| tree | a0154c20a12d2dee8d5acddec5a66e56b7f07e0b /lib/dolce/table/drawing-list-table-v2.tsx | |
| parent | 7010b6a8c4d05cfb670aec6048f225db21c8c092 (diff) | |
(김준회) dolce: 기존 레이아웃과 유사한 v2 추가, bulk-upload를 MatchBatchDwgFile 사용하지 않도록 변경 (해당 API 동작 이상함)
Diffstat (limited to 'lib/dolce/table/drawing-list-table-v2.tsx')
| -rw-r--r-- | lib/dolce/table/drawing-list-table-v2.tsx | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/lib/dolce/table/drawing-list-table-v2.tsx b/lib/dolce/table/drawing-list-table-v2.tsx new file mode 100644 index 00000000..2ee80f11 --- /dev/null +++ b/lib/dolce/table/drawing-list-table-v2.tsx @@ -0,0 +1,273 @@ +"use client"; + +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, + getSortedRowModel, + SortingState, + getPaginationRowModel, +} from "@tanstack/react-table"; +import { useState } from "react"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ArrowUpDown, ArrowUp, ArrowDown, ChevronLeft, ChevronRight } from "lucide-react"; + +// 도면 데이터의 기본 인터페이스 +interface DrawingData { + RegisterGroupId?: string | null; + DrawingNo?: string | null; + Discipline?: string | null; + CreateDt?: string | Date | null; +} + +interface DrawingListTableV2Props<TData extends DrawingData, TValue> { + columns: ColumnDef<TData, TValue>[]; + data: TData[]; + onRowClick?: (row: TData) => void; + selectedRow?: TData; + getRowId?: (row: TData) => string; + maxHeight?: string; // e.g., "45vh" + minHeight?: string; // e.g., "400px" + defaultPageSize?: number; +} + +export function DrawingListTableV2<TData extends DrawingData, TValue>({ + columns, + data, + onRowClick, + selectedRow, + getRowId, + maxHeight = "45vh", + minHeight = "400px", + defaultPageSize = 10, +}: DrawingListTableV2Props<TData, TValue>) { + const [sorting, setSorting] = useState<SortingState>([]); + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(defaultPageSize); + + // 기본 getRowId 함수: RegisterGroupId + DrawingNo + Discipline + CreateDt 조합 + const defaultGetRowId = (row: TData): string => { + const registerId = row.RegisterGroupId || ''; + const drawingNo = row.DrawingNo || ''; + const discipline = row.Discipline || ''; + const createDt = row.CreateDt + ? (row.CreateDt instanceof Date ? row.CreateDt.toISOString() : String(row.CreateDt)) + : ''; + + return `${registerId}-${drawingNo}-${discipline}-${createDt}`; + }; + + const rowIdGetter = getRowId || defaultGetRowId; + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + onPaginationChange: (updater) => { + if (typeof updater === 'function') { + const newState = updater({ pageIndex, pageSize }); + setPageIndex(newState.pageIndex); + setPageSize(newState.pageSize); + } + }, + state: { + sorting, + pagination: { + pageIndex, + pageSize, + }, + }, + }); + + // 행이 선택되었는지 확인하는 함수 + const isRowSelected = (row: TData): boolean => { + if (!selectedRow) return false; + return rowIdGetter(row) === rowIdGetter(selectedRow); + }; + + const handlePageSizeChange = (value: string) => { + const newSize = value === "all" ? data.length : parseInt(value); + setPageSize(newSize); + setPageIndex(0); + }; + + return ( + <div className="flex flex-col h-full" style={{ minHeight }}> + {/* 테이블 영역 */} + <div + className="rounded-md border overflow-auto flex-1" + style={{ + maxHeight, + minHeight: data.length === 0 ? minHeight : undefined, + }} + > + <Table className="min-w-max"> + <TableHeader className="sticky top-0 z-10 bg-background"> + {table.getHeaderGroups().map((headerGroup) => ( + <TableRow key={headerGroup.id}> + {headerGroup.headers.map((header) => { + const isSorted = header.column.getIsSorted(); + const canSort = header.column.getCanSort(); + + return ( + <TableHead + key={header.id} + style={{ minWidth: header.column.columnDef.minSize }} + className="bg-background" + > + {header.isPlaceholder ? null : ( + <div + className={`flex items-center gap-2 ${ + canSort ? "cursor-pointer select-none hover:text-primary" : "" + }`} + onClick={ + canSort + ? header.column.getToggleSortingHandler() + : undefined + } + > + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {canSort && ( + <span className="text-muted-foreground"> + {isSorted === "asc" ? ( + <ArrowUp className="h-4 w-4" /> + ) : isSorted === "desc" ? ( + <ArrowDown className="h-4 w-4" /> + ) : ( + <ArrowUpDown className="h-4 w-4 opacity-50" /> + )} + </span> + )} + </div> + )} + </TableHead> + ); + })} + </TableRow> + ))} + </TableHeader> + <TableBody> + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => { + const isSelected = isRowSelected(row.original); + return ( + <TableRow + key={row.id} + data-state={row.getIsSelected() && "selected"} + onClick={() => onRowClick?.(row.original)} + className={`cursor-pointer transition-colors ${ + isSelected + ? "bg-accent hover:bg-accent" + : "hover:bg-muted/50" + }`} + > + {row.getVisibleCells().map((cell) => ( + <TableCell + key={cell.id} + style={{ minWidth: cell.column.columnDef.minSize }} + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} + </TableCell> + ))} + </TableRow> + ); + }) + ) : ( + <TableRow> + <TableCell + colSpan={columns.length} + className="text-center text-muted-foreground" + style={{ height: "300px" }} + > + <div className="flex items-center justify-center h-full"> + 데이터가 없습니다. + </div> + </TableCell> + </TableRow> + )} + </TableBody> + </Table> + </div> + + {/* 페이지네이션 컨트롤 - 항상 렌더링하여 높이 일관성 유지 */} + <div className="flex items-center justify-between py-2 px-2 border-t flex-shrink-0"> + {data.length > 0 ? ( + <> + <div className="flex items-center gap-2"> + <span className="text-sm text-muted-foreground"> + 페이지 당 행 수: + </span> + <Select value={pageSize === data.length ? "all" : String(pageSize)} onValueChange={handlePageSizeChange}> + <SelectTrigger className="w-[100px] h-8"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="10">10</SelectItem> + <SelectItem value="20">20</SelectItem> + <SelectItem value="50">50</SelectItem> + <SelectItem value="all">전체</SelectItem> + </SelectContent> + </Select> + </div> + + <div className="flex items-center gap-2"> + <span className="text-sm text-muted-foreground"> + {table.getState().pagination.pageIndex * pageSize + 1}- + {Math.min( + (table.getState().pagination.pageIndex + 1) * pageSize, + data.length + )}{" "} + / {data.length} + </span> + <div className="flex gap-1"> + <Button + variant="outline" + size="sm" + onClick={() => table.previousPage()} + disabled={!table.getCanPreviousPage()} + > + <ChevronLeft className="h-4 w-4" /> + </Button> + <Button + variant="outline" + size="sm" + onClick={() => table.nextPage()} + disabled={!table.getCanNextPage()} + > + <ChevronRight className="h-4 w-4" /> + </Button> + </div> + </div> + </> + ) : ( + <div className="text-sm text-muted-foreground opacity-0"> + Placeholder for consistent height + </div> + )} + </div> + </div> + ); +} + |
