diff --git a/apps/i15-1/src/AppProviders.tsx b/apps/i15-1/src/AppProviders.tsx index c39801e5..cd1f96e0 100644 --- a/apps/i15-1/src/AppProviders.tsx +++ b/apps/i15-1/src/AppProviders.tsx @@ -2,7 +2,7 @@ import type { Api } from "@atlas/blueapi"; import { ThemeProvider } from "@diamondlightsource/sci-react-ui"; import type { Theme } from "@mui/material"; import type { ReactNode } from "react"; -import { InstrumentSessionProvider } from "./context/instrumentSession/InstrumentSessionProvider"; +import { InstrumentSessionProvider } from "@atlas/app-shell"; import { Provider as ReduxProvider } from "react-redux"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BlueapiProvider } from "@atlas/blueapi-query"; @@ -22,7 +22,10 @@ export function AppProviders({ api, theme, children }: Props) { const config = useLoadPvwsConfig(); return ( - + diff --git a/apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts b/apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts deleted file mode 100644 index 74a9c936..00000000 --- a/apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createContext } from "react"; - -export type InstrumentSessionContextType = { - instrumentSession: string; - setInstrumentSession: (session: string) => void; -}; - -export const InstrumentSessionContext = createContext< - InstrumentSessionContextType | undefined ->(undefined); diff --git a/apps/i15-1/src/context/instrumentSession/InstrumentSessionProvider.tsx b/apps/i15-1/src/context/instrumentSession/InstrumentSessionProvider.tsx deleted file mode 100644 index cf91adcb..00000000 --- a/apps/i15-1/src/context/instrumentSession/InstrumentSessionProvider.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useState, useEffect, type ReactNode } from "react"; -import { InstrumentSessionContext } from "./InstrumentSessionContext"; - -const STORAGE_KEY = "instrument-session-id"; - -export const InstrumentSessionProvider = ({ - children, - defaultSessionId = "cm12345-1", -}: { - children: ReactNode; - defaultSessionId?: string; -}) => { - const [instrumentSession, setInstrumentSession] = useState(() => { - return localStorage.getItem(STORAGE_KEY) ?? defaultSessionId; - }); - - useEffect(() => { - localStorage.setItem(STORAGE_KEY, instrumentSession); - }, [instrumentSession]); - - return ( - - {children} - - ); -}; diff --git a/apps/i15-1/src/context/instrumentSession/useInstrumentSession.ts b/apps/i15-1/src/context/instrumentSession/useInstrumentSession.ts deleted file mode 100644 index 2b19f863..00000000 --- a/apps/i15-1/src/context/instrumentSession/useInstrumentSession.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext } from "react"; -import { InstrumentSessionContext } from "./InstrumentSessionContext"; - -export const useInstrumentSession = () => { - const context = useContext(InstrumentSessionContext); - if (!context) { - throw new Error( - "useInstrumentSession must be used within InstrumentSessionProvider", - ); - } - return context; -}; diff --git a/apps/i15-1/src/routes/Dashboard.tsx b/apps/i15-1/src/routes/Dashboard.tsx index 1abd388c..8e16f705 100644 --- a/apps/i15-1/src/routes/Dashboard.tsx +++ b/apps/i15-1/src/routes/Dashboard.tsx @@ -2,7 +2,6 @@ import { Container, Typography, Button, Stack } from "@mui/material"; import { Link } from "react-router-dom"; import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturing"; import QueueIcon from "@mui/icons-material/Queue"; -import InstrumentSessionView from "../components/InstrumentSessionSelection/InstrumentSessionView.tsx"; import { useUserAuth } from "../context/userAuth/useUserAuth.ts"; import { User } from "@diamondlightsource/sci-react-ui"; @@ -27,15 +26,6 @@ function Dashboard() { : { fedid: user.person } } /> - ; +} +vi.mock("./context/instrumentSession/InstrumentSessionView", () => ({ + InstrumentSessionView: InstrumentSessionView, +})); + describe("Layout", () => { it("shows title, nav section titles, and main content", () => { const props: RouterProps = { diff --git a/packages/app-shell/src/TopBar.test.tsx b/packages/app-shell/src/TopBar.test.tsx index 52a3c47d..9e31c941 100644 --- a/packages/app-shell/src/TopBar.test.tsx +++ b/packages/app-shell/src/TopBar.test.tsx @@ -10,6 +10,14 @@ vi.mock("@diamondlightsource/sci-react-ui", async () => { }; }); +// mock instrument session view which is out of scope of this test +export function InstrumentSessionView() { + return ; +} +vi.mock("./context/instrumentSession/InstrumentSessionView", () => ({ + InstrumentSessionView: InstrumentSessionView, +})); + describe("TopBar", () => { const barProps = { title: "Test application", diff --git a/packages/app-shell/src/TopBar.tsx b/packages/app-shell/src/TopBar.tsx index 6d123971..3b9355bc 100644 --- a/packages/app-shell/src/TopBar.tsx +++ b/packages/app-shell/src/TopBar.tsx @@ -10,6 +10,8 @@ import { ColourSchemeButton, Logo } from "@diamondlightsource/sci-react-ui"; import { Divider } from "@mui/material"; import type { Theme } from "@mui/material/styles"; +import { InstrumentSessionView } from "./context/instrumentSession/InstrumentSessionView"; + type Props = { title: string; open: boolean; @@ -62,11 +64,23 @@ export function TopBar({ title, open, setOpen }: Props) { sx={{ ml: 1.5, mt: 1.25, + mr: 1.25, }} > {title} + + + + + + diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx new file mode 100644 index 00000000..4951a618 --- /dev/null +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx @@ -0,0 +1,54 @@ +import { useState, useEffect, useContext, type ReactNode } from "react"; +import { createContext } from "react"; + +export const ID_STORAGE_KEY = "instrument-session-id"; + +export type InstrumentSessionContextType = { + instrumentSession: string; + setInstrumentSession: (session: string) => void; + sessionsList: string[]; +}; + +export const InstrumentSessionContext = createContext< + InstrumentSessionContextType | undefined +>(undefined); + +export const InstrumentSessionProvider = ({ + children, + defaultSessionId = "cm12345-1", + sessionsList = ["cm12345-2", "cm12345-2"], +}: { + children: ReactNode; + defaultSessionId?: string; + sessionsList?: string[]; +}) => { + const [instrumentSession, setInstrumentSession] = useState(() => { + return localStorage.getItem(ID_STORAGE_KEY) ?? defaultSessionId; + }); + + useEffect(() => { + localStorage.setItem(ID_STORAGE_KEY, instrumentSession); + }, [instrumentSession]); + + return ( + + {children} + + ); +}; + +export const useInstrumentSession = () => { + const context = useContext(InstrumentSessionContext); + if (!context) { + throw new Error( + "useInstrumentSession must be used within InstrumentSessionProvider", + ); + } + return context; +}; diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx new file mode 100644 index 00000000..776a27a5 --- /dev/null +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx @@ -0,0 +1,88 @@ +import { render, screen, userEvent, within } from "@atlas/vitest-conf"; +import { InstrumentSessionView } from "./InstrumentSessionView"; +import { InstrumentSessionProvider } from "./InstrumentSessionProvider"; +import { describe, it, expect, vi } from "vitest"; + +function renderComponentWithProvider() { + return render( + + + , + ); +} + +vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { + const actual = + await importOriginal(); + return { + ...actual, + useInstrumentSession: () => ({ + instrumentSession: "cm54321-1", + setInstrumentSession: vi.fn(), + sessionsList: ["cm123-4", "cm567-8"], + }), + }; +}); + +describe("InstrumentSessionView", () => { + beforeEach(() => { + localStorage.clear(); + }); + + it("shows only the current instrument session", () => { + renderComponentWithProvider(); + expect(screen.getByTestId("session-input-button")).toBeInTheDocument(); + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + }); + + it("shows sessions from the provided sessions list when clicked", async () => { + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + expect(screen.getByTestId("visit-field")).toBeInTheDocument(); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + expect(screen.getByText("cm123-4")).toBeInTheDocument(); + expect(screen.getByText("cm567-8")).toBeInTheDocument(); + }); + + it("closes menu when a menu item has been clicked", async () => { + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + + await user.click(screen.getByText("cm123-4")); + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + }); + + it("closes menu when button has been clicked again", async () => { + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + + const backdrop = screen.getByRole("presentation").firstChild; + if (backdrop instanceof Element) { + await user.click(backdrop); + } + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + }); + + it("lets you input a valid session", async () => { + const onSubmit = vi.fn(); + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + + const visitInput = screen.getByTestId("session-input-menu"); + const visitTextBox = within(visitInput).getByRole("textbox"); + await user.click(visitTextBox); + await user.clear(visitTextBox); + await user.type(visitTextBox, "cm1-1"); + await user.keyboard("{Enter}"); + }); +}); diff --git a/apps/i15-1/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx similarity index 50% rename from apps/i15-1/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx rename to packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx index aacdc8b3..a5f26b50 100644 --- a/apps/i15-1/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx @@ -1,25 +1,26 @@ import { useState } from "react"; import { visitToText, VisitInput } from "@diamondlightsource/sci-react-ui"; -import { useInstrumentSession } from "../../context/instrumentSession/useInstrumentSession"; -import { - Divider, - List, - ListItemButton, - ListItemText, - Menu, - MenuItem, -} from "@mui/material"; +import { useInstrumentSession } from "./InstrumentSessionProvider"; +import { visitTextToVisit } from "../../utils/common"; +import * as React from "react"; +import Button from "@mui/material/Button"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import Divider from "@mui/material/Divider"; -function InstrumentSessionView({ sessionsList }: { sessionsList: string[] }) { - const { instrumentSession, setInstrumentSession } = useInstrumentSession(); - - const [anchorEl, setAnchorEl] = useState(null); +export function InstrumentSessionView() { + const { instrumentSession, setInstrumentSession, sessionsList } = + useInstrumentSession(); const [selectedIndex, setSelectedIndex] = useState(1); + + const id = "session-input"; + const buttonId = `${id}-button`; + const menuId = `${id}-menu`; + const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); - const handleClickListItem = (event: React.MouseEvent) => { + const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; - const handleMenuItemClick = ( event: React.MouseEvent, index: number, @@ -28,54 +29,34 @@ function InstrumentSessionView({ sessionsList }: { sessionsList: string[] }) { setAnchorEl(null); setInstrumentSession(event.currentTarget.textContent ?? ""); }; - const handleClose = () => { setAnchorEl(null); }; return (
- - - - - + - e.stopPropagation()}> - { - setInstrumentSession(visitToText(visit)); - setAnchorEl(null); - }} - /> - - {sessionsList.map((option, index) => ( ))} + + e.stopPropagation()}> + { + setInstrumentSession(visitToText(visit)); + setAnchorEl(null); + }} + /> +
); } - -export default InstrumentSessionView; diff --git a/packages/app-shell/src/index.tsx b/packages/app-shell/src/index.tsx index f217afa7..f20c6cda 100644 --- a/packages/app-shell/src/index.tsx +++ b/packages/app-shell/src/index.tsx @@ -1,2 +1,4 @@ export * from "./Router"; export * from "./Layout"; +export * from "./context/instrumentSession/InstrumentSessionProvider"; +export * from "./context/instrumentSession/InstrumentSessionView"; diff --git a/packages/app-shell/src/utils/common.ts b/packages/app-shell/src/utils/common.ts new file mode 100644 index 00000000..e27649f9 --- /dev/null +++ b/packages/app-shell/src/utils/common.ts @@ -0,0 +1,14 @@ +import { type Visit, regexToVisit } from "@diamondlightsource/sci-react-ui"; + +const visitRegex = /^([a-z]{2})([1-9]\d*)-([1-9]\d*)/; +export const visitWithTemplateRegex = new RegExp(`${visitRegex.source}-(.+)$`); + +export function visitTextToVisit(visitid?: string): Visit | null { + if (visitid) { + const parsedVisit = visitRegex.exec(visitid); + if (parsedVisit != null) { + return regexToVisit(parsedVisit); + } + } + return null; +} diff --git a/packages/blueapi-ui/package.json b/packages/blueapi-ui/package.json index ca2fbae9..f0f5e9ef 100644 --- a/packages/blueapi-ui/package.json +++ b/packages/blueapi-ui/package.json @@ -11,6 +11,7 @@ "coverage": "vitest run --coverage" }, "dependencies": { + "@atlas/app-shell": "workspace:*", "@atlas/blueapi": "workspace:*", "@atlas/blueapi-query": "workspace:*", "@jsonforms/core": "3.6.0", @@ -18,7 +19,8 @@ "@jsonforms/react": "3.6.0", "@tanstack/react-query": "^5.90.21", "lucide-react": "^1.24.0", - "react-error-boundary": "^6.0.0" + "react-error-boundary": "^6.0.0", + "react-router-dom": "^7.18.1" }, "devDependencies": { "@atlas/vitest-conf": "workspace:*", diff --git a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx index 8dd2c1ea..2604510c 100644 --- a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx +++ b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx @@ -21,6 +21,14 @@ vi.mock("../RunPlanButton", () => ({ RunPlanButton: RunPlanButton, })); +// mock use instrument session +const mockUseIS = vi.fn(() => ({ + instrumentSession: "cm12345-1", +})); +vi.mock("@atlas/app-shell", () => ({ + useInstrumentSession: () => mockUseIS(), +})); + const plan: Plan = { name: "hi_plan", description: "Says hi to you", @@ -38,7 +46,6 @@ describe("PlanParameters", () => { expect(screen.getByText(plan.name)).toBeInTheDocument(); expect(screen.getByText(plan.description!)).toBeInTheDocument(); expect(screen.getByTestId("jsonforms-sentinel")).toBeInTheDocument(); - expect(screen.getByRole("textbox", { name: "Instrument Session" })); expect(screen.getByRole("button", { name: "Run" })); }); diff --git a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx index a143f22d..28b02df2 100644 --- a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx +++ b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx @@ -8,6 +8,7 @@ import { import { sanitisePlan, type SchemaNode } from "../utils/schema"; import type { Plan } from "@atlas/blueapi"; import { RunPlanButton } from "../RunPlanButton"; +import { useInstrumentSession } from "@atlas/app-shell"; import { ErrorBoundary } from "react-error-boundary"; @@ -33,9 +34,7 @@ export function PlanParameters({ plan }: { plan: Plan }) { const sanitisedPlan = sanitisePlan(plan); const [planParameters, setPlanParameters] = useState({}); - // TODO: Remove InstrumentSession box and state, retrieve from context when submitting. - // See https://github.com/DiamondLightSource/atlas/issues/57 - const [instrumentSession, setInstrumentSession] = useState("cm12345-1"); + const { instrumentSession } = useInstrumentSession(); return ( @@ -66,16 +65,6 @@ export function PlanParameters({ plan }: { plan: Plan }) { /> )} - {/* TODO: Remove InstrumentSession box and state, retrieve from context when submitting. - See https://github.com/DiamondLightSource/atlas/issues/57 */} - - setInstrumentSession(e.target.value)} - > - =6.4.0'} + deprecated: This package is deprecated as of August 2026. plotly.js v4 uses MapLibre for map traces — see https://github.com/maplibre/maplibre-gl-js. '@plotly/point-cluster@3.1.9': resolution: {integrity: sha512-MwaI6g9scKf68Orpr1pHZ597pYx9uP8UEFXLPbsCmuw3a84obwz6pnMXGc90VhgDNeNiLEdlmuK7CPo+5PIxXw==} @@ -5040,6 +5047,13 @@ packages: react: '>=18' react-dom: '>=18' + react-router-dom@7.18.1: + resolution: {integrity: sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + react-router@7.13.1: resolution: {integrity: sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==} engines: {node: '>=20.0.0'} @@ -5050,6 +5064,16 @@ packages: react-dom: optional: true + react-router@7.18.1: + resolution: {integrity: sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-select@5.10.2: resolution: {integrity: sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==} peerDependencies: @@ -6405,7 +6429,7 @@ snapshots: optionalDependencies: dayjs: 1.10.7 - '@diamondlightsource/cs-web-lib@0.10.15(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2))(react-dom@18.3.1(react@18.3.1))(react-gauge-component@2.0.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@diamondlightsource/cs-web-lib@0.10.15(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2))(react-dom@18.3.1(react@18.3.1))(react-gauge-component@2.0.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: '@mui/icons-material': 7.3.11(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@reduxjs/toolkit': 2.12.0(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -6419,7 +6443,7 @@ snapshots: react-grid-layout: 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-plotly.js: 2.6.0(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2))(react@18.3.1) react-redux: 7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router: 7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router: 7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-toastify: 11.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) utf-8-validate: 5.0.10 uuid: 9.0.1 @@ -6428,7 +6452,7 @@ snapshots: - graphql - plotly.js - '@diamondlightsource/cs-web-lib@0.10.15(@mui/material@7.3.11(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(graphql@16.14.2)(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2(postcss@8.5.15)))(react-dom@18.3.1(react@18.3.1))(react-gauge-component@2.0.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@diamondlightsource/cs-web-lib@0.10.15(@mui/material@7.3.11(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(graphql@16.14.2)(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2(postcss@8.5.15)))(react-dom@18.3.1(react@18.3.1))(react-gauge-component@2.0.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: '@mui/icons-material': 7.3.11(@mui/material@7.3.11(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@reduxjs/toolkit': 2.12.0(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -6442,7 +6466,7 @@ snapshots: react-grid-layout: 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-plotly.js: 2.6.0(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2(postcss@8.5.15)))(react@18.3.1) react-redux: 7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router: 7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router: 7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-toastify: 11.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) utf-8-validate: 5.0.10 uuid: 9.0.1 @@ -11438,6 +11462,12 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-router: 7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router-dom@7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router@7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: cookie: 1.1.1 @@ -11446,6 +11476,14 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) + react-router@7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + cookie: 1.1.1 + react: 18.3.1 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-select@5.10.2(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.29.7