From 27924b0fdb7d1af3ab515430239d12fb6dc8a2fc Mon Sep 17 00:00:00 2001 From: "web@ppanel" Date: Sat, 21 Dec 2024 16:30:43 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(table):=20Add=20sorting=20supp?= =?UTF-8?q?ort=20for=20Node=20and=20subscription=20columns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/app/dashboard/server/node-table.tsx | 27 +++ .../dashboard/subscribe/subscribe-table.tsx | 27 +++ packages/ui/src/pro-table/pro-table.tsx | 192 ++++++++---------- packages/ui/src/pro-table/wrapper.tsx | 54 +++++ 4 files changed, 192 insertions(+), 108 deletions(-) create mode 100644 packages/ui/src/pro-table/wrapper.tsx diff --git a/apps/admin/app/dashboard/server/node-table.tsx b/apps/admin/app/dashboard/server/node-table.tsx index 46fa7c3..178e9e4 100644 --- a/apps/admin/app/dashboard/server/node-table.tsx +++ b/apps/admin/app/dashboard/server/node-table.tsx @@ -8,6 +8,7 @@ import { deleteNode, getNodeGroupList, getNodeList, + nodeSort, updateNode, } from '@/services/admin/server'; import { ConfirmButton } from '@repo/ui/confirm-button'; @@ -230,6 +231,32 @@ export default function NodeTable() { ]; }, }} + onSort={async (source, target, items) => { + const sourceIndex = items.findIndex((item) => String(item.id) === source); + const targetIndex = items.findIndex((item) => String(item.id) === target); + + const originalSorts = items.map((item) => ({ id: item.id, sort: item.sort || item.id })); + + const [movedItem] = items.splice(sourceIndex, 1); + items.splice(targetIndex, 0, movedItem!); + + const updatedItems = items.map((item) => { + const originalSort = originalSorts.find((sortItem) => sortItem.id === item.id)?.sort; + return { + ...item, + sort: originalSort !== undefined ? originalSort : item.sort, + }; + }); + nodeSort({ + sort: updatedItems.map((item) => { + return { + id: item.id, + sort: item.sort, + }; + }), + }); + return updatedItems; + }} /> ); } diff --git a/apps/admin/app/dashboard/subscribe/subscribe-table.tsx b/apps/admin/app/dashboard/subscribe/subscribe-table.tsx index f100b26..bf397e4 100644 --- a/apps/admin/app/dashboard/subscribe/subscribe-table.tsx +++ b/apps/admin/app/dashboard/subscribe/subscribe-table.tsx @@ -8,6 +8,7 @@ import { deleteSubscribe, getSubscribeGroupList, getSubscribeList, + subscribeSort, updateSubscribe, } from '@/services/admin/subscribe'; import { ConfirmButton } from '@repo/ui/confirm-button'; @@ -237,6 +238,32 @@ export default function SubscribeTable() { />, ], }} + onSort={async (source, target, items) => { + const sourceIndex = items.findIndex((item) => String(item.id) === source); + const targetIndex = items.findIndex((item) => String(item.id) === target); + + const originalSorts = items.map((item) => ({ id: item.id, sort: item.sort || item.id })); + + const [movedItem] = items.splice(sourceIndex, 1); + items.splice(targetIndex, 0, movedItem!); + + const updatedItems = items.map((item) => { + const originalSort = originalSorts.find((sortItem) => sortItem.id === item.id)?.sort; + return { + ...item, + sort: originalSort !== undefined ? originalSort : item.sort, + }; + }); + subscribeSort({ + sort: updatedItems.map((item) => { + return { + id: item.id, + sort: item.sort, + }; + }), + }); + return updatedItems; + }} /> ); } diff --git a/packages/ui/src/pro-table/pro-table.tsx b/packages/ui/src/pro-table/pro-table.tsx index cec46fd..3ce74d9 100644 --- a/packages/ui/src/pro-table/pro-table.tsx +++ b/packages/ui/src/pro-table/pro-table.tsx @@ -1,20 +1,7 @@ 'use client'; -import { - closestCenter, - DndContext, - DragEndEvent, - KeyboardSensor, - PointerSensor, - useSensor, - useSensors, -} from '@dnd-kit/core'; -import { - arrayMove, - SortableContext, - sortableKeyboardCoordinates, - verticalListSortingStrategy, -} from '@dnd-kit/sortable'; +import { DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; +import { sortableKeyboardCoordinates } from '@dnd-kit/sortable'; import { Alert, AlertDescription, AlertTitle } from '@shadcn/ui/alert'; import { Button } from '@shadcn/ui/button'; import { Checkbox } from '@shadcn/ui/checkbox'; @@ -40,6 +27,7 @@ import { ColumnHeader } from './column-header'; import { ColumnToggle } from './column-toggle'; import { Pagination } from './pagination'; import { SortableRow } from './sortable-row'; +import { ProTableWrapper } from './wrapper'; export interface ProTableProps { columns: ColumnDef[]; @@ -70,7 +58,11 @@ export interface ProTableProps { selectedRowsText: (total: number) => string; }>; empty?: React.ReactNode; - onSort?: (newOrder: TData[]) => void; + onSort?: ( + sourceId: string | number, + targetId: string | number | null, + items: TData[], + ) => Promise; } export interface ProTableActions { @@ -120,7 +112,13 @@ export function ProTable< ] : []), ...(actions?.batchRender ? [createSelectColumn()] : []), - ...columns, + ...columns.map( + (column) => + ({ + enableSorting: false, + ...column, + }) as ColumnDef, + ), ...(actions?.render ? ([ { @@ -141,7 +139,6 @@ export function ProTable< ] as ColumnDef[], onPaginationChange: setPagination, onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), @@ -207,22 +204,11 @@ export function ProTable< }), ); - const handleDragEnd = (event: DragEndEvent) => { + const handleDragEnd = async (event: DragEndEvent) => { const { active, over } = event; - - if (active?.id !== over?.id) { - setData((items) => { - const oldIndex = items.findIndex((item) => { - return String(item.id) === active?.id; - }); - const newIndex = items.findIndex((item) => { - return String(item.id) === over?.id; - }); - - const newOrder = arrayMove(items, oldIndex, newIndex); - if (onSort) onSort(newOrder); - return newOrder; - }); + if (onSort) { + const items = await onSort(active.id, over?.id || null, data); + setData(items); } }; @@ -272,80 +258,70 @@ export function ProTable< width: size?.width, }} > - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - - - ))} - - ))} - - - {table.getRowModel()?.rows?.length ? ( - onSort ? ( - - { - return String(row.original.id); - })} - strategy={verticalListSortingStrategy} - > - {table.getRowModel().rows.map((row) => ( - - {row - .getVisibleCells() - .filter((cell) => { - return cell.column.id !== 'sortable'; - }) - .map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ))} - - + +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + + + ))} + + ))} + + + {table.getRowModel()?.rows?.length ? ( + onSort ? ( + table.getRowModel().rows.map((row) => ( + + {row + .getVisibleCells() + .filter((cell) => { + return cell.column.id !== 'sortable'; + }) + .map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) ) : ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - )) - ) - ) : ( - - - {empty || } - - - )} - -
+ + + {empty || } + + + )} + + + + {loading && (
@@ -390,7 +366,7 @@ function createSelectColumn(): ColumnDef { } function getTableHeaderClass(columnId: string) { - if (columnId === 'selected') { + if (['sortable', 'selected'].includes(columnId)) { return 'sticky left-0 z-10 bg-background shadow-[2px_0_5px_-2px_rgba(0,0,0,0.1)] [&:has([role=checkbox])]:pr-2'; } else if (columnId === 'actions') { return 'sticky right-0 z-10 text-right bg-background shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'; @@ -399,7 +375,7 @@ function getTableHeaderClass(columnId: string) { } function getTableCellClass(columnId: string) { - if (columnId === 'selected') { + if (['sortable', 'selected'].includes(columnId)) { return 'sticky left-0 bg-background shadow-[2px_0_5px_-2px_rgba(0,0,0,0.1)]'; } else if (columnId === 'actions') { return 'sticky right-0 bg-background shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'; diff --git a/packages/ui/src/pro-table/wrapper.tsx b/packages/ui/src/pro-table/wrapper.tsx new file mode 100644 index 0000000..bf9506f --- /dev/null +++ b/packages/ui/src/pro-table/wrapper.tsx @@ -0,0 +1,54 @@ +import { + DndContext, + DragEndEvent, + KeyboardSensor, + PointerSensor, + closestCenter, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; + +export function ProTableWrapper({ + children, + onSort, + data, + setData, +}: { + children: React.ReactNode; + onSort?: ( + sourceId: string | number, + targetId: string | number | null, + items: TData[], + ) => Promise; + data: TData[]; + setData: React.Dispatch>; +}) { + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }), + ); + + const handleDragEnd = async (event: DragEndEvent) => { + const { active, over } = event; + if (onSort) { + const updatedData = await onSort(active.id, over?.id || null, data); + setData(updatedData); + } + }; + if (!onSort) return children; + return ( + + String(item.id))} + strategy={verticalListSortingStrategy} + > + {children} + + + ); +}