diff --git a/src/app/(dashboard)/jobmonitor/layout.tsx b/src/app/(dashboard)/jobmonitor/layout.tsx new file mode 100644 index 00000000..c0bcec50 --- /dev/null +++ b/src/app/(dashboard)/jobmonitor/layout.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +export default function JobMonitorLayout({ + children, +}: { + children: React.ReactNode; +}) { + return
{children}
; +} diff --git a/src/components/ui/DashboardDrawer.tsx b/src/components/ui/DashboardDrawer.tsx index ccc641e1..8ffd50c7 100644 --- a/src/components/ui/DashboardDrawer.tsx +++ b/src/components/ui/DashboardDrawer.tsx @@ -1,5 +1,4 @@ import { usePathname } from "next/navigation"; -import NextLink from "next/link"; import { Drawer, Icon, @@ -13,19 +12,31 @@ import { import { Dashboard, FolderCopy } from "@mui/icons-material"; import MonitorIcon from "@mui/icons-material/Monitor"; import MenuBookIcon from "@mui/icons-material/MenuBook"; -import { ReactEventHandler } from "react"; +import React, { ComponentType, ReactEventHandler } from "react"; +import { DragDropContext, Droppable } from "react-beautiful-dnd"; +import DrawerItemGroup from "./DrawerItemGroup"; import { DiracLogo } from "./DiracLogo"; // Define the sections that are accessible to users. // Each section has an associated icon and path. -const userSections: Record< - string, - { icon: React.ComponentType; path: string } -> = { - Dashboard: { icon: Dashboard, path: "/" }, - "Job Monitor": { icon: MonitorIcon, path: "/jobmonitor" }, - "File Catalog": { icon: FolderCopy, path: "/filecatalog" }, -}; +let userSections: { + title: string; + items: { title: string; id: number; icon: ComponentType; path: string }[]; +}[] = [ + { + title: "Dashboard", + items: [ + { title: "Dashboard", id: 0, icon: Dashboard, path: "/" }, + { title: "Job Monitor", id: 1, icon: MonitorIcon, path: "/jobmonitor" }, + ], + }, + { + title: "Other", + items: [ + { title: "File Catalog", id: 2, icon: FolderCopy, path: "/filecatalog" }, + ], + }, +]; interface DashboardDrawerProps { variant: "permanent" | "temporary"; @@ -34,6 +45,27 @@ interface DashboardDrawerProps { handleDrawerToggle: ReactEventHandler; } +function onDragEnd(result: any) { + // Reorder the list of items in the group. + if (!result.destination) { + return; + } + const source = result.source; + const destination = result.destination; + + const sourceGroup = userSections.find( + (group) => group.title == source.droppableId, + ); + const destinationGroup = userSections.find( + (group) => group.title == destination.droppableId, + ); + + if (sourceGroup && destinationGroup) { + const [removed] = sourceGroup.items.splice(source.index, 1); + destinationGroup.items.splice(destination.index, 0, removed); + } +} + export default function DashboardDrawer(props: DashboardDrawerProps) { // Get the current URL const pathname = usePathname(); @@ -67,22 +99,15 @@ export default function DashboardDrawer(props: DashboardDrawerProps) { {/* Map over user sections and render them as list items in the drawer. */} - - {Object.keys(userSections).map((title: string) => ( - - - - - - - - - ))} - + + + {userSections.map(({ title, items }, index: number) => ( + + + + ))} + + {/* Render a link to documentation, positioned at the bottom of the drawer. */} diff --git a/src/components/ui/DrawerItemGroup.tsx b/src/components/ui/DrawerItemGroup.tsx new file mode 100644 index 00000000..14cc2bba --- /dev/null +++ b/src/components/ui/DrawerItemGroup.tsx @@ -0,0 +1,73 @@ +import { + Accordion, + AccordionDetails, + AccordionSummary, + Icon, + ListItemButton, + ListItemIcon, + ListItemText, +} from "@mui/material"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { Draggable, Droppable } from "react-beautiful-dnd"; +import React from "react"; +import Link from "next/link"; +import DragIndicatorIcon from "@mui/icons-material/DragIndicator"; +import { StrictModeDroppable } from "./StrictModeDroppable"; + +export default function DrawerItemGroup({ + title, + items, +}: { + title: string; + items: { + title: string; + id: number; + icon: React.ComponentType; + path: string; + }[]; +}) { + return ( + + {/* Accordion summary */} + }> + {title} + + {/* Accordion details */} + + {(provided, snapshot) => ( + + {items.map(({ title, id, icon, path }, index) => ( + + {(provided) => ( +
+ + + + + +
+ +
+
+
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
+ ); +} diff --git a/src/components/ui/StrictModeDroppable.tsx b/src/components/ui/StrictModeDroppable.tsx new file mode 100644 index 00000000..db689f05 --- /dev/null +++ b/src/components/ui/StrictModeDroppable.tsx @@ -0,0 +1,16 @@ +import { useEffect, useState } from "react"; +import { Droppable, DroppableProps } from "react-beautiful-dnd"; +export const StrictModeDroppable = ({ children, ...props }: DroppableProps) => { + const [enabled, setEnabled] = useState(false); + useEffect(() => { + const animation = requestAnimationFrame(() => setEnabled(true)); + return () => { + cancelAnimationFrame(animation); + setEnabled(false); + }; + }, []); + if (!enabled) { + return null; + } + return {children}; +};