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 ;
+}
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};
+};