From 364ce0b7527d3626ee89cd50e3ec2683505119b4 Mon Sep 17 00:00:00 2001 From: "Daniel S. Hansen" Date: Thu, 31 Jul 2025 12:23:45 -0700 Subject: [PATCH 1/6] Save point, Sidebar prevents navigation without first saving or cancelling current changes --- .../src/components/layout/Canvas/Canvas.jsx | 12 ++++++-- .../layout/Canvas/views/DashboardCanvas.jsx | 26 ++++++++--------- client/src/components/layout/Dashboard.jsx | 28 +++++++++++++++++-- .../src/components/layout/Header/Header.jsx | 3 +- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/client/src/components/layout/Canvas/Canvas.jsx b/client/src/components/layout/Canvas/Canvas.jsx index 7c02fd1..82d1258 100644 --- a/client/src/components/layout/Canvas/Canvas.jsx +++ b/client/src/components/layout/Canvas/Canvas.jsx @@ -12,7 +12,7 @@ import SettingsCanvas from "./views/SettingsCanvas" import { DashboardCanvas } from "./views/DashboardCanvas"; import { DASHBOARD_VIEWS } from "@/constants/viewKeys"; -export default function Canvas({ view, setView, layout, setLayout, onLayoutChange, darkMode }) { +export default function Canvas({ isEditable, setIsEditable, isDelete, setIsDelete, view, setView, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView, darkMode }) { const recipe = useRecipe({ key: "canvas" }); const styles = recipe(); @@ -21,11 +21,19 @@ export default function Canvas({ view, setView, layout, setLayout, onLayoutChang {/* Check if view is a tab inside the Canvas Header */} { DASHBOARD_VIEWS.includes(view) && ( )} {view === "global" && } diff --git a/client/src/components/layout/Canvas/views/DashboardCanvas.jsx b/client/src/components/layout/Canvas/views/DashboardCanvas.jsx index 476b6da..8b7e7be 100644 --- a/client/src/components/layout/Canvas/views/DashboardCanvas.jsx +++ b/client/src/components/layout/Canvas/views/DashboardCanvas.jsx @@ -11,11 +11,11 @@ import { CancelButton } from "../components/CancelButton"; import { SaveButton } from "../components/SaveButton"; import DeleteAllButton from "../components/DeleteAllButton"; -export function DashboardCanvas({ view, setView, layout, setLayout, onLayoutChange }) { - const [isEditable, setIsEditable] = useState(false); // editable=true means static=false (vice versa) - const [isDelete, setIsDelete] = useState(false); +export function DashboardCanvas({ isEditable, setIsEditable, isDelete, setIsDelete, view, setView, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView }) { + // const [isEditable, setIsEditable] = useState(false); // editable=true means static=false (vice versa) + // const [isDelete, setIsDelete] = useState(false); const [newCounter, setNewCounter] = useState(3); - const [originalLayout, setOriginalLayout] = useState([]); + // const [originalLayout, setOriginalLayout] = useState([]); const onAddWidget = () => { const newId = newCounter.toString(); @@ -100,15 +100,15 @@ export function DashboardCanvas({ view, setView, layout, setLayout, onLayoutChan // Prevent navigation away from 'My Dashboard' while in Edit mode onValueChange={(e) => { const nextView = e.value; - - if (view === "widget" && isEditable && nextView !== "widget") { - const confirmed = window.confirm( - "You have unsaved changes. Do you wish to discard them and continue?" - ); - if (!confirmed) return; - onCancel(); - } - setView(nextView); + onChangeView(nextView); + // if (view === "widget" && isEditable && nextView !== "widget") { + // const confirmed = window.confirm( + // "You have unsaved changes. Do you wish to discard them and continue?" + // ); + // if (!confirmed) return; + // onCancel(); + // } + // setView(nextView); }} > { + if (view === "widget" && isEditable) { + const confirmed = window.confirm( + "You have unsaved changes. Discard them and continue?" + ); + if (!confirmed) return; + onCancel(); // Discards the layout + } + setView(nextView); + }; + return ( <> @@ -37,7 +52,9 @@ export default function Dashboard() { darkMode={ darkMode } setDarkMode={ setDarkMode } view={ view } - onChangeView={ setView } + onChangeView={ handleViewChange } + isEditabe={ isEditable } + // onChangeView={ setView } /> @@ -47,7 +64,14 @@ export default function Dashboard() { setView={ setView } layout={ layout } setLayout={ setLayout } + originalLayout={ originalLayout } + setOriginalLayout={ setOriginalLayout } onLayoutChange={( newLayout ) => setLayout( newLayout )} + onChangeView={ handleViewChange } + isEditable={ isEditable } + setIsEditable={ setIsEditable } + isDelete={ isDelete } + setIsDelete={ setIsDelete } /> diff --git a/client/src/components/layout/Header/Header.jsx b/client/src/components/layout/Header/Header.jsx index 79c5f41..d2c3fa4 100644 --- a/client/src/components/layout/Header/Header.jsx +++ b/client/src/components/layout/Header/Header.jsx @@ -8,7 +8,7 @@ const user = { // avatar: "src\\assets\\user_profile.svg" } -export default function Header({ view, setView }) { +export default function Header({ view, setView, onChangeView }) { const recipe = useRecipe({ key: "header" }); const styles = recipe(); @@ -25,6 +25,7 @@ export default function Header({ view, setView }) { avatar={user.avatar} view={ view } setView={ setView } + onChangeView={ onChangeView } /> From 80a0515bb179fbfd382506f16bb5e20c293c58e2 Mon Sep 17 00:00:00 2001 From: "Daniel S. Hansen" Date: Thu, 31 Jul 2025 12:40:46 -0700 Subject: [PATCH 2/6] Add navigation prevention while in Edit mode to Avatar menu options in Header --- .../src/components/layout/Canvas/Canvas.jsx | 3 ++- .../layout/Canvas/views/DashboardCanvas.jsx | 27 ++++++++++--------- client/src/components/layout/Dashboard.jsx | 14 ++++++++++ .../layout/Header/UserAvatarMenu.jsx | 6 ++--- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/client/src/components/layout/Canvas/Canvas.jsx b/client/src/components/layout/Canvas/Canvas.jsx index 82d1258..5e20a67 100644 --- a/client/src/components/layout/Canvas/Canvas.jsx +++ b/client/src/components/layout/Canvas/Canvas.jsx @@ -12,7 +12,7 @@ import SettingsCanvas from "./views/SettingsCanvas" import { DashboardCanvas } from "./views/DashboardCanvas"; import { DASHBOARD_VIEWS } from "@/constants/viewKeys"; -export default function Canvas({ isEditable, setIsEditable, isDelete, setIsDelete, view, setView, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView, darkMode }) { +export default function Canvas({ onCancel, isEditable, setIsEditable, isDelete, setIsDelete, view, setView, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView, darkMode }) { const recipe = useRecipe({ key: "canvas" }); const styles = recipe(); @@ -30,6 +30,7 @@ export default function Canvas({ isEditable, setIsEditable, isDelete, setIsDelet setOriginalLayout={ setOriginalLayout } onLayoutChange={ onLayoutChange } onChangeView={ onChangeView } + onCancel={ onCancel } isEditable={ isEditable } setIsEditable={ setIsEditable } isDelete={ isDelete } diff --git a/client/src/components/layout/Canvas/views/DashboardCanvas.jsx b/client/src/components/layout/Canvas/views/DashboardCanvas.jsx index 8b7e7be..b27c5b5 100644 --- a/client/src/components/layout/Canvas/views/DashboardCanvas.jsx +++ b/client/src/components/layout/Canvas/views/DashboardCanvas.jsx @@ -11,7 +11,7 @@ import { CancelButton } from "../components/CancelButton"; import { SaveButton } from "../components/SaveButton"; import DeleteAllButton from "../components/DeleteAllButton"; -export function DashboardCanvas({ isEditable, setIsEditable, isDelete, setIsDelete, view, setView, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView }) { +export function DashboardCanvas({ onCancel, isEditable, setIsEditable, isDelete, setIsDelete, view, setView, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView }) { // const [isEditable, setIsEditable] = useState(false); // editable=true means static=false (vice versa) // const [isDelete, setIsDelete] = useState(false); const [newCounter, setNewCounter] = useState(3); @@ -55,18 +55,19 @@ export function DashboardCanvas({ isEditable, setIsEditable, isDelete, setIsDele setOriginalLayout([]) // Clear backup layout } - const onCancel = () => { - console.log("canceling changes..."); - if (originalLayout.length) { - setLayout(originalLayout.map(item => ({ - ...item, - static: true - }))); - }; - setIsEditable(false); - setIsDelete(false); - setOriginalLayout([]); - } + // Lifting up to Dashboard.jsx for prop drilling + // const onCancel = () => { + // console.log("canceling changes..."); + // if (originalLayout.length) { + // setLayout(originalLayout.map(item => ({ + // ...item, + // static: true + // }))); + // }; + // setIsEditable(false); + // setIsDelete(false); + // setOriginalLayout([]); + // } const onDeleteWidgets = () => { console.log("Start deleting widgets..."); diff --git a/client/src/components/layout/Dashboard.jsx b/client/src/components/layout/Dashboard.jsx index 28dde76..4a61747 100644 --- a/client/src/components/layout/Dashboard.jsx +++ b/client/src/components/layout/Dashboard.jsx @@ -20,6 +20,19 @@ export default function Dashboard() { { i: '2', x: 6, y: 0, w: 3, h: 3, minH: 3, minW: 1, static: true } ]); + const onCancel = () => { + console.log("canceling changes..."); + if (originalLayout.length) { + setLayout(originalLayout.map(item => ({ + ...item, + static: true + }))); + }; + setIsEditable(false); + setIsDelete(false); + setOriginalLayout([]); + } + // Prevent navigation while in Edit mode without saving or cancelling const handleViewChange = (nextView) => { if (view === "widget" && isEditable) { @@ -68,6 +81,7 @@ export default function Dashboard() { setOriginalLayout={ setOriginalLayout } onLayoutChange={( newLayout ) => setLayout( newLayout )} onChangeView={ handleViewChange } + onCancel={ onCancel } isEditable={ isEditable } setIsEditable={ setIsEditable } isDelete={ isDelete } diff --git a/client/src/components/layout/Header/UserAvatarMenu.jsx b/client/src/components/layout/Header/UserAvatarMenu.jsx index 250542c..3158e89 100644 --- a/client/src/components/layout/Header/UserAvatarMenu.jsx +++ b/client/src/components/layout/Header/UserAvatarMenu.jsx @@ -1,7 +1,7 @@ import { Avatar, Menu, Portal, Box } from "@chakra-ui/react" -export default function UserAvatarMenu({ view, setView, name, avatar }) { +export default function UserAvatarMenu({ view, setView, onChangeView, name, avatar }) { return ( @@ -21,10 +21,10 @@ export default function UserAvatarMenu({ view, setView, name, avatar }) { {/* zIndex: overlay = 1300 */} - setView("account")}> + onChangeView("account")}> Account - setView("settings")}> + onChangeView("settings")}> Settings console.log("Log out triggered")}> From ee03a1d2f4a6dc77ee78738a0920399871011e81 Mon Sep 17 00:00:00 2001 From: "Daniel S. Hansen" Date: Thu, 31 Jul 2025 13:23:11 -0700 Subject: [PATCH 3/6] Remove old comments and deprecated code snippets --- .../layout/Canvas/views/DashboardCanvas.jsx | 27 +------------------ client/src/components/layout/Dashboard.jsx | 1 - .../src/components/layout/Header/Header.jsx | 4 +-- .../layout/Header/UserAvatarMenu.jsx | 2 +- .../src/components/layout/Sidebar/Sidebar.jsx | 9 ++----- 5 files changed, 5 insertions(+), 38 deletions(-) diff --git a/client/src/components/layout/Canvas/views/DashboardCanvas.jsx b/client/src/components/layout/Canvas/views/DashboardCanvas.jsx index b27c5b5..0e11076 100644 --- a/client/src/components/layout/Canvas/views/DashboardCanvas.jsx +++ b/client/src/components/layout/Canvas/views/DashboardCanvas.jsx @@ -11,11 +11,8 @@ import { CancelButton } from "../components/CancelButton"; import { SaveButton } from "../components/SaveButton"; import DeleteAllButton from "../components/DeleteAllButton"; -export function DashboardCanvas({ onCancel, isEditable, setIsEditable, isDelete, setIsDelete, view, setView, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView }) { - // const [isEditable, setIsEditable] = useState(false); // editable=true means static=false (vice versa) - // const [isDelete, setIsDelete] = useState(false); +export function DashboardCanvas({ onCancel, isEditable, setIsEditable, isDelete, setIsDelete, view, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView }) { const [newCounter, setNewCounter] = useState(3); - // const [originalLayout, setOriginalLayout] = useState([]); const onAddWidget = () => { const newId = newCounter.toString(); @@ -55,20 +52,6 @@ export function DashboardCanvas({ onCancel, isEditable, setIsEditable, isDelete, setOriginalLayout([]) // Clear backup layout } - // Lifting up to Dashboard.jsx for prop drilling - // const onCancel = () => { - // console.log("canceling changes..."); - // if (originalLayout.length) { - // setLayout(originalLayout.map(item => ({ - // ...item, - // static: true - // }))); - // }; - // setIsEditable(false); - // setIsDelete(false); - // setOriginalLayout([]); - // } - const onDeleteWidgets = () => { console.log("Start deleting widgets..."); setOriginalLayout(layout); @@ -102,14 +85,6 @@ export function DashboardCanvas({ onCancel, isEditable, setIsEditable, isDelete, onValueChange={(e) => { const nextView = e.value; onChangeView(nextView); - // if (view === "widget" && isEditable && nextView !== "widget") { - // const confirmed = window.confirm( - // "You have unsaved changes. Do you wish to discard them and continue?" - // ); - // if (!confirmed) return; - // onCancel(); - // } - // setView(nextView); }} > diff --git a/client/src/components/layout/Header/Header.jsx b/client/src/components/layout/Header/Header.jsx index d2c3fa4..1c127a7 100644 --- a/client/src/components/layout/Header/Header.jsx +++ b/client/src/components/layout/Header/Header.jsx @@ -8,7 +8,7 @@ const user = { // avatar: "src\\assets\\user_profile.svg" } -export default function Header({ view, setView, onChangeView }) { +export default function Header({ onChangeView }) { const recipe = useRecipe({ key: "header" }); const styles = recipe(); @@ -23,8 +23,6 @@ export default function Header({ view, setView, onChangeView }) { diff --git a/client/src/components/layout/Header/UserAvatarMenu.jsx b/client/src/components/layout/Header/UserAvatarMenu.jsx index 3158e89..b7d29bc 100644 --- a/client/src/components/layout/Header/UserAvatarMenu.jsx +++ b/client/src/components/layout/Header/UserAvatarMenu.jsx @@ -1,7 +1,7 @@ import { Avatar, Menu, Portal, Box } from "@chakra-ui/react" -export default function UserAvatarMenu({ view, setView, onChangeView, name, avatar }) { +export default function UserAvatarMenu({ onChangeView, name, avatar }) { return ( diff --git a/client/src/components/layout/Sidebar/Sidebar.jsx b/client/src/components/layout/Sidebar/Sidebar.jsx index 4d7ee4b..9af13a2 100644 --- a/client/src/components/layout/Sidebar/Sidebar.jsx +++ b/client/src/components/layout/Sidebar/Sidebar.jsx @@ -1,6 +1,5 @@ import { useSlotRecipe, Box, Button, IconButton } from "@chakra-ui/react"; import { ColorModeButton, useColorMode } from "@/components/ui/color-mode"; -// import { Tooltip } from "@/components/ui/tooltip"; import Tooltip from "../Canvas/components/Tooltip.jsx"; import { FaVolcano } from "react-icons/fa6"; import { FaHome } from "react-icons/fa"; @@ -9,7 +8,6 @@ import { FaMapMarkerAlt } from "react-icons/fa"; import { HiUsers } from "react-icons/hi2"; import { VscThreeBars } from "react-icons/vsc"; import { useState, useEffect } from "react"; -import { Seismic } from "../Canvas/views/DashboardCanvasNav/Seismic"; import { DASHBOARD_VIEWS } from "@/constants/viewKeys"; export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) { @@ -35,14 +33,13 @@ export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) { // Track currrent view from both Sidebar and Canvas when 'view' changes useEffect(() => { - // console.log("typeof view =", typeof view); console.log("Current view = ", view); const newIndex = buttons.findIndex((btn) => { if (btn.view === "widget") { - return DASHBOARD_VIEWS.includes(view); // Group match + return DASHBOARD_VIEWS.includes(view); } - return btn.view === view; // Exact match + return btn.view === view; }); if (newIndex !== -1 ) { @@ -52,7 +49,6 @@ export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) { return ( { - // setActiveButton(idx); onChangeView(button.view); // Trigger state change in Dashboard }} bg={activeButton === idx ? "primary":undefined} From bedd616d92325b1ffc8ea97c9c82020f991aa0e4 Mon Sep 17 00:00:00 2001 From: "Daniel S. Hansen" Date: Thu, 31 Jul 2025 14:01:08 -0700 Subject: [PATCH 4/6] Add new add widget behavior. Edit mode is enabled upon 'Add Widget' and user must either Save/Cancel their changes before navigating away from My Dashboard --- .../layout/Canvas/views/DashboardCanvas.jsx | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/client/src/components/layout/Canvas/views/DashboardCanvas.jsx b/client/src/components/layout/Canvas/views/DashboardCanvas.jsx index 0e11076..a6b7d0c 100644 --- a/client/src/components/layout/Canvas/views/DashboardCanvas.jsx +++ b/client/src/components/layout/Canvas/views/DashboardCanvas.jsx @@ -14,42 +14,53 @@ import DeleteAllButton from "../components/DeleteAllButton"; export function DashboardCanvas({ onCancel, isEditable, setIsEditable, isDelete, setIsDelete, view, layout, setLayout, originalLayout, setOriginalLayout, onLayoutChange, onChangeView }) { const [newCounter, setNewCounter] = useState(3); + // Adding a widget triggers Edit mode const onAddWidget = () => { const newId = newCounter.toString(); + setLayout(prev => [ - ...prev, + ...prev, { i: newId, x: (prev.length * 3) % 12, y: Infinity, w: 3, h: 3, + static: false, } ]); setNewCounter(prev => prev + 1); + onEditWidgets(); }; - useEffect(() => { - console.log("static change effect"); - setLayout(prevLayout => - prevLayout.map(item => ({ - ...item, - static: !isEditable // static false means resizable/editable - })) - ); - }, [isEditable]); - const onEditWidgets = () => { console.log("widgets are now editable"); setOriginalLayout(layout); // Backup layout before editing + + // Set all widgets to editable, static: false + setLayout(prevLayout => + prevLayout.map(item => ({ + ...item, + static: false + })) + ); setIsEditable(true); // Enable edit mode } const onSave = () => { console.log("saving widgets..."); + + // Set all widgets back to static: true + setLayout(prevLayout => + prevLayout.map(item => ({ + ...item, + static: true + })) + ); + setIsEditable(false); setIsDelete(false); - setOriginalLayout([]) // Clear backup layout + setOriginalLayout([]); // Clear backup layout } const onDeleteWidgets = () => { From e6b7d8231000ad11c8ae3c7a2ab14aa604d95c28 Mon Sep 17 00:00:00 2001 From: "Daniel S. Hansen" Date: Thu, 31 Jul 2025 14:39:35 -0700 Subject: [PATCH 5/6] Add regional map functionality, and edit global map zoom settings --- client/src/App.jsx | 2 + .../layout/Canvas/views/GlobalMapCanvas.jsx | 4 +- .../layout/Canvas/views/RegionalMapCanvas.jsx | 40 +++++++++---------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/client/src/App.jsx b/client/src/App.jsx index cc8a57a..9935035 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,4 +1,6 @@ import Dashboard from './components/layout/Dashboard.jsx' +import 'leaflet/dist/leaflet.css'; + function App() { return ( diff --git a/client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx b/client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx index 7190735..a4bba7f 100644 --- a/client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx +++ b/client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx @@ -1,11 +1,11 @@ import React from "react"; import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; -import 'leaflet/dist/leaflet.css'; +// import 'leaflet/dist/leaflet.css'; export default function GlobalMapCanvas() { const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude] return ( - + - - - Regional Map Canvas coming soon! - - - - ); +export default function GlobalMapCanvas() { + const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude] + return ( + + + + + CVO — Home of the volcanoligists + + + + ); } \ No newline at end of file From d2716f6f84d1ca69bb0cb08c8eb1bbb8cc4226ba Mon Sep 17 00:00:00 2001 From: "Daniel S. Hansen" Date: Thu, 31 Jul 2025 14:51:25 -0700 Subject: [PATCH 6/6] Fix canvas not taking up full space in Regional and Global map views --- client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx | 1 - .../src/components/layout/Canvas/views/RegionalMapCanvas.jsx | 1 - client/src/components/layout/Dashboard.jsx | 3 ++- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx b/client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx index a4bba7f..b7d47e6 100644 --- a/client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx +++ b/client/src/components/layout/Canvas/views/GlobalMapCanvas.jsx @@ -1,6 +1,5 @@ import React from "react"; import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; -// import 'leaflet/dist/leaflet.css'; export default function GlobalMapCanvas() { const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude] diff --git a/client/src/components/layout/Canvas/views/RegionalMapCanvas.jsx b/client/src/components/layout/Canvas/views/RegionalMapCanvas.jsx index 42963b5..2377dd6 100644 --- a/client/src/components/layout/Canvas/views/RegionalMapCanvas.jsx +++ b/client/src/components/layout/Canvas/views/RegionalMapCanvas.jsx @@ -1,6 +1,5 @@ import React from "react"; import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; -// import 'leaflet/dist/leaflet.css'; export default function GlobalMapCanvas() { const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude] diff --git a/client/src/components/layout/Dashboard.jsx b/client/src/components/layout/Dashboard.jsx index a27f6af..8cded10 100644 --- a/client/src/components/layout/Dashboard.jsx +++ b/client/src/components/layout/Dashboard.jsx @@ -49,7 +49,8 @@ export default function Dashboard() { <>