Browse Source

feat(dashboard): Implement column reordering

Michael Bromley 9 months ago
parent
commit
186d325723

+ 15 - 1
package-lock.json

@@ -4088,7 +4088,6 @@
     },
     "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
       "version": "1.3.0",
-      "extraneous": true,
       "inBundle": true,
       "license": "MIT",
       "engines": {
@@ -4651,6 +4650,20 @@
         "react-dom": ">=16.8.0"
       }
     },
+    "node_modules/@dnd-kit/modifiers": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz",
+      "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==",
+      "license": "MIT",
+      "dependencies": {
+        "@dnd-kit/utilities": "^3.2.2",
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "@dnd-kit/core": "^6.3.0",
+        "react": ">=16.8.0"
+      }
+    },
     "node_modules/@dnd-kit/sortable": {
       "version": "10.0.0",
       "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
@@ -47200,6 +47213,7 @@
       "version": "3.2.3",
       "dependencies": {
         "@dnd-kit/core": "^6.3.1",
+        "@dnd-kit/modifiers": "^9.0.0",
         "@dnd-kit/sortable": "^10.0.0",
         "@hookform/resolvers": "^4.1.3",
         "@lingui/babel-plugin-lingui-macro": "^5.2.0",

+ 1 - 0
packages/dashboard/package.json

@@ -46,6 +46,7 @@
     ],
     "dependencies": {
         "@dnd-kit/core": "^6.3.1",
+        "@dnd-kit/modifiers": "^9.0.0",
         "@dnd-kit/sortable": "^10.0.0",
         "@hookform/resolvers": "^4.1.3",
         "@lingui/babel-plugin-lingui-macro": "^5.2.0",

+ 72 - 23
packages/dashboard/src/lib/components/data-table/data-table-view-options.tsx

@@ -1,51 +1,100 @@
 'use client';
 
-import { Badge } from '@/components/ui/badge.js';
+import { DndContext, closestCenter } from '@dnd-kit/core';
+import {
+    restrictToVerticalAxis,
+} from '@dnd-kit/modifiers';
+import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
 import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
 import { Table } from '@tanstack/react-table';
-import { CircleX, Cross, Filter, Settings2 } from 'lucide-react';
+import { GripVertical, Settings2 } from 'lucide-react';
 
 import { Button } from '@/components/ui/button.js';
 import {
     DropdownMenu,
     DropdownMenuCheckboxItem,
-    DropdownMenuContent,
-    DropdownMenuLabel,
-    DropdownMenuSeparator,
+    DropdownMenuContent
 } from '@/components/ui/dropdown-menu.js';
+import { usePage } from '@/hooks/use-page.js';
+import { useUserSettings } from '@/hooks/use-user-settings.js';
+import { Trans } from '@/lib/trans.js';
 
 interface DataTableViewOptionsProps<TData> {
     table: Table<TData>;
 }
 
+function SortableItem({ id, children }: { id: string; children: React.ReactNode }) {
+    const {
+        attributes,
+        listeners,
+        setNodeRef,
+        transform,
+        transition,
+    } = useSortable({ id });
+
+    const style = {
+        transform: CSS.Transform.toString(transform),
+        transition,
+    };
+
+    return (
+        <div ref={setNodeRef} style={style} className="flex items-center gap-.5">
+            <div {...attributes} {...listeners} className="cursor-grab">
+                <GripVertical className="h-4 w-4 text-muted-foreground" />
+            </div>
+            {children}
+        </div>
+    );
+}
+
 export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps<TData>) {
+    const { setTableSettings } = useUserSettings();
+    const page = usePage();
+    const columns = table
+        .getAllColumns()
+        .filter(column => typeof column.accessorFn !== 'undefined' && column.getCanHide());
+
+    const handleDragEnd = (event: any) => {
+        const { active, over } = event;
+        if (active.id !== over.id) {
+            const activeIndex = columns.findIndex(col => col.id === active.id);
+            const overIndex = columns.findIndex(col => col.id === over.id);
+            // update the column order in the `columns` array
+            const newColumns = [...columns];
+            newColumns.splice(overIndex, 0, newColumns.splice(activeIndex, 1)[0]);
+            if (page?.pageId) {
+                setTableSettings(page.pageId, 'columnOrder', newColumns.map(col => col.id));
+            }
+        }
+    };
+
     return (
         <div className="flex items-center gap-2">
             <DropdownMenu>
                 <DropdownMenuTrigger asChild>
                     <Button variant="outline" size="sm" className="ml-auto hidden h-8 lg:flex">
                         <Settings2 />
-                        View
+                        <Trans>Columns</Trans>
                     </Button>
                 </DropdownMenuTrigger>
                 <DropdownMenuContent align="end" className="w-[150px]">
-                    <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
-                    <DropdownMenuSeparator />
-                    {table
-                        .getAllColumns()
-                        .filter(column => typeof column.accessorFn !== 'undefined' && column.getCanHide())
-                        .map(column => {
-                            return (
-                                <DropdownMenuCheckboxItem
-                                    key={column.id}
-                                    className="capitalize"
-                                    checked={column.getIsVisible()}
-                                    onCheckedChange={value => column.toggleVisibility(!!value)}
-                                >
-                                    {column.id}
-                                </DropdownMenuCheckboxItem>
-                            );
-                        })}
+                    <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd} modifiers={[restrictToVerticalAxis]}>
+                        <SortableContext items={columns.map(col => col.id)} strategy={verticalListSortingStrategy}>
+                            {columns.map(column => (
+                                <SortableItem key={column.id} id={column.id}>
+                                    <DropdownMenuCheckboxItem
+                                        className="capitalize"
+                                        checked={column.getIsVisible()}
+                                        onCheckedChange={value => column.toggleVisibility(!!value)}
+                                        onSelect={(e) => e.preventDefault()}
+                                    >
+                                        {column.id}
+                                    </DropdownMenuCheckboxItem>
+                                </SortableItem>
+                            ))}
+                        </SortableContext>
+                    </DndContext>
                 </DropdownMenuContent>
             </DropdownMenu>
         </div>