diff options
Diffstat (limited to 'lib/avl/table/avl-table-columns.tsx')
| -rw-r--r-- | lib/avl/table/avl-table-columns.tsx | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/lib/avl/table/avl-table-columns.tsx b/lib/avl/table/avl-table-columns.tsx new file mode 100644 index 00000000..8caf012e --- /dev/null +++ b/lib/avl/table/avl-table-columns.tsx @@ -0,0 +1,353 @@ +import { Checkbox } from "@/components/ui/checkbox" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Eye, Edit, Trash2, History } from "lucide-react" +import { type ColumnDef, TableMeta } from "@tanstack/react-table" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import { EditableCell } from "@/components/data-table/editable-cell" +import { AvlListItem } from "../types" + +interface GetColumnsProps { + selectedRows?: number[] + onRowSelect?: (id: number, selected: boolean) => void +} + +// 수정 여부 확인 헬퍼 함수 +const getIsModified = (table: any, rowId: string, fieldName: string) => { + const pendingChanges = table.options.meta?.getPendingChanges?.() || {} + return String(rowId) in pendingChanges && fieldName in pendingChanges[String(rowId)] +} + +// 테이블 메타 타입 확장 +declare module "@tanstack/react-table" { + interface TableMeta<TData> { + onCellUpdate?: (id: string, field: keyof TData, newValue: any) => Promise<void> + onCellCancel?: (id: string, field: keyof TData) => void + onAction?: (action: string, data?: any) => void + onSaveEmptyRow?: (tempId: string) => Promise<void> + onCancelEmptyRow?: (tempId: string) => void + isEmptyRow?: (id: string) => boolean + } +} + +// 테이블 컬럼 정의 함수 +export function getColumns({ selectedRows = [], onRowSelect }: GetColumnsProps): ColumnDef<AvlListItem>[] { + const columns: ColumnDef<AvlListItem>[] = [ + // 기본 정보 그룹 + { + header: "기본 정보", + columns: [ + { + id: "select", + header: () => <div className="text-center">선택</div>, + cell: ({ row }) => ( + <div className="flex justify-center"> + <Checkbox + checked={selectedRows.includes(row.original.id)} + onCheckedChange={(checked) => { + onRowSelect?.(row.original.id, !!checked) + }} + aria-label="행 선택" + className="translate-y-[2px]" + /> + </div> + ), + enableSorting: false, + enableHiding: false, + size: 40, + }, + { + accessorKey: "no", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="No" /> + ), + cell: ({ getValue }) => <div className="text-center">{getValue() as number}</div>, + size: 60, + }, + { + accessorKey: "isTemplate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="AVL 분류" /> + ), + cell: ({ getValue, row, table }) => { + const value = getValue() as boolean + const isModified = getIsModified(table, row.id, "isTemplate") + return ( + <EditableCell + value={value ? "표준 AVL" : "프로젝트 AVL"} + isModified={isModified} + type="select" + options={[ + { value: false, label: "프로젝트 AVL" }, + { value: true, label: "표준 AVL" }, + ]} + onUpdate={(newValue) => { + table.options.meta?.onCellUpdate?.(row.id, "isTemplate", newValue === "true") + }} + onCancel={() => { + table.options.meta?.onCellCancel?.(row.id, "isTemplate") + }} + /> + ) + }, + size: 120, + }, + { + accessorKey: "constructionSector", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="공사부문" /> + ), + cell: ({ getValue, row, table }) => { + const value = getValue() as string + const isModified = getIsModified(table, row.id, "constructionSector") + return ( + <EditableCell + value={value} + isModified={isModified} + type="select" + options={[ + { value: "조선", label: "조선" }, + { value: "해양", label: "해양" }, + ]} + onUpdate={(newValue) => { + table.options.meta?.onCellUpdate?.(row.id, "constructionSector", newValue) + }} + onCancel={() => { + table.options.meta?.onCellCancel?.(row.id, "constructionSector") + }} + /> + ) + }, + size: 100, + }, + { + accessorKey: "projectCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" /> + ), + cell: ({ getValue, row, table }) => { + const value = getValue() as string + const isModified = getIsModified(table, row.id, "projectCode") + return ( + <EditableCell + value={value} + isModified={isModified} + onUpdate={(newValue) => { + table.options.meta?.onCellUpdate?.(row.id, "projectCode", newValue) + }} + onCancel={() => { + table.options.meta?.onCellCancel?.(row.id, "projectCode") + }} + /> + ) + }, + size: 140, + }, + { + accessorKey: "shipType", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="선종" /> + ), + cell: ({ getValue, row, table }) => { + const value = getValue() as string + const isModified = getIsModified(table, row.id, "shipType") + return ( + <EditableCell + value={value} + isModified={isModified} + onUpdate={(newValue) => { + table.options.meta?.onCellUpdate?.(row.id, "shipType", newValue) + }} + onCancel={() => { + table.options.meta?.onCellCancel?.(row.id, "shipType") + }} + /> + ) + }, + size: 100, + }, + { + accessorKey: "avlKind", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="AVL 종류" /> + ), + cell: ({ getValue, row, table }) => { + const value = getValue() as string + const isModified = getIsModified(table, row.id, "avlKind") + return ( + <EditableCell + value={value} + isModified={isModified} + onUpdate={(newValue) => { + table.options.meta?.onCellUpdate?.(row.id, "avlKind", newValue) + }} + onCancel={() => { + table.options.meta?.onCellCancel?.(row.id, "avlKind") + }} + /> + ) + }, + size: 120, + }, + { + accessorKey: "htDivision", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="H/T 구분" /> + ), + cell: ({ getValue, row, table }) => { + const value = getValue() as string + const isModified = getIsModified(table, row.id, "htDivision") + return ( + <EditableCell + value={value} + isModified={isModified} + type="select" + options={[ + { value: "H", label: "H" }, + { value: "T", label: "T" }, + ]} + onUpdate={(newValue) => { + table.options.meta?.onCellUpdate?.(row.id, "htDivision", newValue) + }} + onCancel={() => { + table.options.meta?.onCellCancel?.(row.id, "htDivision") + }} + /> + ) + }, + size: 80, + }, + { + accessorKey: "rev", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Rev" /> + ), + cell: ({ getValue, row, table }) => { + const value = getValue() as number + return ( + <div className="flex items-center gap-1"> + <Badge variant="outline" className="font-mono"> + {value || 1} + </Badge> + <Button + variant="ghost" + size="sm" + className="h-6 w-6 p-0" + onClick={() => table.options.meta?.onAction?.('view-history', row.original)} + title="리비전 히스토리 보기" + > + <History className="h-3 w-3" /> + </Button> + </div> + ) + }, + size: 100, + }, + ], + }, + + // 등록 정보 그룹 + { + header: "등록 정보", + columns: [ + { + accessorKey: "createdAt", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="등재일" /> + ), + cell: ({ getValue }) => { + const date = getValue() as string + return <div className="text-center text-sm">{date}</div> + }, + size: 100, + }, + { + accessorKey: "updatedAt", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="수정일" /> + ), + cell: ({ getValue }) => { + const date = getValue() as string + return <div className="text-center text-sm">{date}</div> + }, + size: 100, + }, + ], + }, + + // 액션 그룹 + { + id: "actions", + header: "액션", + columns: [ + { + id: "actions", + header: () => <div className="text-center">액션</div>, + cell: ({ row, table }) => { + const isEmptyRow = table.options.meta?.isEmptyRow?.(row.id) || false + + if (isEmptyRow) { + return ( + <div className="flex items-center justify-center gap-1"> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onSaveEmptyRow?.(row.id)} + className="h-8 w-8 p-0" + > + 저장 + </Button> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onCancelEmptyRow?.(row.id)} + className="h-8 w-8 p-0 text-destructive hover:text-destructive" + > + 취소 + </Button> + </div> + ) + } + + return ( + <div className="flex items-center justify-center gap-1"> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onAction?.("view-detail", { id: row.original.id })} + className="h-8 w-8 p-0" + title="상세보기" + > + <Eye className="h-4 w-4" /> + </Button> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onAction?.("edit", { id: row.original.id })} + className="h-8 w-8 p-0" + title="수정" + > + <Edit className="h-4 w-4" /> + </Button> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onAction?.("delete", { id: row.original.id })} + className="h-8 w-8 p-0 text-destructive hover:text-destructive" + title="삭제" + > + <Trash2 className="h-4 w-4" /> + </Button> + </div> + ) + }, + enableSorting: false, + enableHiding: false, + size: 120, + }, + ], + }, + ] + + return columns +} |
