summaryrefslogtreecommitdiff
path: root/lib/dolce/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dolce/table')
-rw-r--r--lib/dolce/table/detail-drawing-columns.tsx96
-rw-r--r--lib/dolce/table/drawing-list-columns.tsx7
-rw-r--r--lib/dolce/table/drawing-list-table-v2.tsx273
3 files changed, 289 insertions, 87 deletions
diff --git a/lib/dolce/table/detail-drawing-columns.tsx b/lib/dolce/table/detail-drawing-columns.tsx
index 77d25953..c082333d 100644
--- a/lib/dolce/table/detail-drawing-columns.tsx
+++ b/lib/dolce/table/detail-drawing-columns.tsx
@@ -8,8 +8,8 @@ import { formatDolceDateTime } from "../utils/date-formatter";
const DRAWING_USAGE_MAP: Record<string, { ko: string; en: string }> = {
APP: { ko: "승인용", en: "Approval" },
WOR: { ko: "작업용", en: "Working" },
- REC: { ko: "입수용 / GTT→SHI", en: "GTT→SHI" },
- SUB: { ko: "제출용 / SHI→GTT", en: "SHI→GTT" },
+ REC: { ko: "입수용", en: "GTT→SHI" },
+ SUB: { ko: "제출용", en: "SHI→GTT" },
};
const REGISTER_KIND_MAP: Record<string, { ko: string; en: string }> = {
@@ -36,82 +36,6 @@ const translateRegisterKind = (code: string | null, lng: string): string => {
return lng === "en" ? mapped.en : mapped.ko;
};
-// 기본 컬럼 (기존 호환성 유지)
-export const detailDrawingColumns: ColumnDef<DetailDwgReceiptItem>[] = [
- {
- accessorKey: "RegisterSerialNo",
- header: "일련번호",
- minSize: 80,
- cell: ({ row }) => {
- return <div className="text-center">{row.getValue("RegisterSerialNo")}</div>;
- },
- },
- {
- accessorKey: "DrawingRevNo",
- header: "Revision",
- minSize: 100,
- cell: ({ row }) => {
- return <div className="font-medium">{row.getValue("DrawingRevNo")}</div>;
- },
- },
- {
- accessorKey: "Status",
- header: "상태",
- minSize: 120,
- cell: ({ row }) => {
- return <div className="text-center">{row.getValue("Status")}</div>;
- },
- },
- {
- accessorKey: "CategoryENM",
- header: "카테고리",
- minSize: 120,
- cell: ({ row }) => {
- const categoryENM = row.getValue("CategoryENM") as string;
- const categoryNM = row.original.CategoryNM;
- return <div>{categoryENM || categoryNM}</div>;
- },
- },
- {
- accessorKey: "DrawingUsageENM",
- header: "도면용도",
- minSize: 100,
- cell: ({ row }) => {
- const usageENM = row.getValue("DrawingUsageENM") as string | null;
- const usageNM = row.original.DrawingUsageNM;
- return <div>{usageENM || usageNM}</div>;
- },
- },
- {
- accessorKey: "RegisterKindENM",
- header: "등록종류",
- minSize: 180,
- cell: ({ row }) => {
- const kindENM = row.getValue("RegisterKindENM") as string | null;
- const kindNM = row.original.RegisterKindNM;
- return <div>{kindENM || kindNM}</div>;
- },
- },
- {
- accessorKey: "CreateUserNM",
- header: "생성자",
- minSize: 150,
- cell: ({ row }) => {
- const userENM = row.original.CreateUserENM;
- const userNM = row.getValue("CreateUserNM") as string;
- return <div>{userENM || userNM}</div>;
- },
- },
- {
- accessorKey: "CreateDt",
- header: "생성일시",
- minSize: 200,
- cell: ({ row }) => {
- return <div className="text-sm text-muted-foreground">{row.getValue("CreateDt")}</div>;
- },
- },
-];
-
// 다국어 지원 컬럼 생성 함수
export function createDetailDrawingColumns(
lng: string,
@@ -155,25 +79,25 @@ export function createDetailDrawingColumns(
},
},
{
- accessorKey: "DrawingUsageENM",
+ accessorKey: "DrawingUsage",
header: t("detailDrawing.columns.drawingUsage"),
minSize: 100,
cell: ({ row }) => {
- // API의 ENM이 제대로 번역되지 않아 코드 값으로 직접 변환
- const usageCode = row.getValue("DrawingUsageENM") as string | null;
+ // API 응답의 DrawingUsage는 코드값이므로 직접 매핑하여 번역
+ const usageCode = row.getValue("DrawingUsage") as string;
const translated = translateDrawingUsage(usageCode, lng);
- return <div>{translated || usageCode || row.original.DrawingUsageNM}</div>;
+ return <div>{translated}</div>;
},
},
{
- accessorKey: "RegisterKindENM",
+ accessorKey: "RegisterKind",
header: t("detailDrawing.columns.registerKind"),
minSize: 180,
cell: ({ row }) => {
- // API의 ENM이 제대로 번역되지 않아 코드 값으로 직접 변환
- const kindCode = row.getValue("RegisterKindENM") as string | null;
+ // API 응답의 RegisterKind는 코드값이므로 직접 매핑하여 번역
+ const kindCode = row.getValue("RegisterKind") as string;
const translated = translateRegisterKind(kindCode, lng);
- return <div>{translated || kindCode || row.original.RegisterKindNM}</div>;
+ return <div>{translated}</div>;
},
},
{
diff --git a/lib/dolce/table/drawing-list-columns.tsx b/lib/dolce/table/drawing-list-columns.tsx
index 58631084..34055eff 100644
--- a/lib/dolce/table/drawing-list-columns.tsx
+++ b/lib/dolce/table/drawing-list-columns.tsx
@@ -15,6 +15,11 @@ export function drawingListColumns(lng: string, t: any): ColumnDef<DwgReceiptIte
},
},
{
+ accessorKey: "RegisterGroupId",
+ header: "RegisterGroupId",
+ minSize: 150,
+ },
+ {
accessorKey: "DrawingName",
header: t("drawingList.columns.drawingName"),
minSize: 600,
@@ -25,7 +30,7 @@ export function drawingListColumns(lng: string, t: any): ColumnDef<DwgReceiptIte
{
accessorKey: "Discipline",
header: t("drawingList.columns.discipline"),
- minSize: 80,
+ minSize: 120,
},
{
accessorKey: "Manager",
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>
+ );
+}
+