summaryrefslogtreecommitdiff
path: root/lib/dolce/table/drawing-list-table-v2.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-24 22:41:52 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-24 22:41:52 +0900
commit25b2561bf17128b96f023c977efb5cb51da0b4aa (patch)
treea0154c20a12d2dee8d5acddec5a66e56b7f07e0b /lib/dolce/table/drawing-list-table-v2.tsx
parent7010b6a8c4d05cfb670aec6048f225db21c8c092 (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.tsx273
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>
+ );
+}
+