Merge branch 'main' into 'dashboardCanvas/Widget/add-widgets-menu'

# Conflicts:
#   client/src/components/layout/Canvas/views/DashboardCanvas.jsx
This commit is contained in:
2025-08-04 19:20:13 +00:00
9 changed files with 97 additions and 73 deletions
+2
View File
@@ -1,4 +1,6 @@
import Dashboard from './components/layout/Dashboard.jsx' import Dashboard from './components/layout/Dashboard.jsx'
import 'leaflet/dist/leaflet.css';
function App() { function App() {
return ( return (
+10 -1
View File
@@ -12,7 +12,7 @@ import SettingsCanvas from "./views/SettingsCanvas"
import { DashboardCanvas } from "./views/DashboardCanvas"; import { DashboardCanvas } from "./views/DashboardCanvas";
import { DASHBOARD_VIEWS } from "@/constants/viewKeys"; import { DASHBOARD_VIEWS } from "@/constants/viewKeys";
export default function Canvas({ view, setView, layout, setLayout, onLayoutChange, 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 recipe = useRecipe({ key: "canvas" });
const styles = recipe(); const styles = recipe();
@@ -21,11 +21,20 @@ export default function Canvas({ view, setView, layout, setLayout, onLayoutChang
{/* Check if view is a tab inside the Canvas Header */} {/* Check if view is a tab inside the Canvas Header */}
{ DASHBOARD_VIEWS.includes(view) && ( { DASHBOARD_VIEWS.includes(view) && (
<DashboardCanvas <DashboardCanvas
darkMode={ darkMode }
view={ view } view={ view }
setView={ setView } setView={ setView }
layout={ layout } layout={ layout }
setLayout={ setLayout } setLayout={ setLayout }
originalLayout={ originalLayout }
setOriginalLayout={ setOriginalLayout }
onLayoutChange={ onLayoutChange } onLayoutChange={ onLayoutChange }
onChangeView={ onChangeView }
onCancel={ onCancel }
isEditable={ isEditable }
setIsEditable={ setIsEditable }
isDelete={ isDelete }
setIsDelete={ setIsDelete }
/> />
)} )}
{view === "global" && <GlobalMapCanvas />} {view === "global" && <GlobalMapCanvas />}
@@ -29,42 +29,37 @@ export function DashboardCanvas({ view, setView, layout, setLayout, onLayoutChan
static: false static: false
} }
]); ]);
onEditWidgets();
}; };
useEffect(() => {
console.log("static change effect");
setLayout(prevLayout =>
prevLayout.map(item => ({
...item,
static: !isEditable // static false means resizable/editable
}))
);
}, [isEditable]);
const onEditWidgets = () => { const onEditWidgets = () => {
console.log("widgets are now editable"); console.log("widgets are now editable");
setOriginalLayout(layout); // Backup layout before editing 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 setIsEditable(true); // Enable edit mode
} }
const onSave = () => { const onSave = () => {
console.log("saving widgets..."); console.log("saving widgets...");
setIsEditable(false);
setIsDelete(false);
setOriginalLayout([]) // Clear backup layout
}
const onCancel = () => { // Set all widgets back to static: true
console.log("canceling changes..."); setLayout(prevLayout =>
if (originalLayout.length) { prevLayout.map(item => ({
setLayout(originalLayout.map(item => ({
...item, ...item,
static: true static: true
}))); }))
}; );
setIsEditable(false); setIsEditable(false);
setIsDelete(false); setIsDelete(false);
setOriginalLayout([]); setOriginalLayout([]); // Clear backup layout
} }
const onDeleteWidgets = () => { const onDeleteWidgets = () => {
@@ -101,15 +96,7 @@ export function DashboardCanvas({ view, setView, layout, setLayout, onLayoutChan
// Prevent navigation away from 'My Dashboard' while in Edit mode // Prevent navigation away from 'My Dashboard' while in Edit mode
onValueChange={(e) => { onValueChange={(e) => {
const nextView = e.value; 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);
}} }}
> >
<Flex <Flex
@@ -1,11 +1,10 @@
import React from "react"; import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import 'leaflet/dist/leaflet.css';
export default function GlobalMapCanvas() { export default function GlobalMapCanvas() {
const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude] const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude]
return ( return (
<MapContainer center={position} zoom={13} style={{ height: "100%", width: "100%" }}> <MapContainer center={position} zoom={3} style={{ height: "100%", width: "100%" }}>
<TileLayer <TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors' attribution='&copy; OpenStreetMap contributors'
@@ -1,24 +1,19 @@
import { Box, Text } from '@chakra-ui/react'; import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
export default function RegionalMapCanvas() { export default function GlobalMapCanvas() {
return ( const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude]
<Box p={8} mt={4} ml="auto" mr="auto" width="600px"> return (
<Box <MapContainer center={position} zoom={13} style={{ height: "100%", width: "100%" }}>
bg="bg" <TileLayer
shadow="md" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
borderRadius="md" attribution='&copy; OpenStreetMap contributors'
p={8} />
width="600px" <Marker position={position}>
height="100px" <Popup>
textAlign="center" CVO Home of the volcanoligists
display="flex" </Popup>
alignItems="center" </Marker>
justifyContent="center" </MapContainer>
> );
<Text fontsize="2xl" fontWeight="bold" color="gray.700">
Regional Map Canvas coming soon!
</Text>
</Box>
</Box>
);
} }
+41 -3
View File
@@ -10,15 +10,43 @@ export default function Dashboard() {
const styles = recipe(); const styles = recipe();
const [darkMode, setDarkMode] = useState(false); const [darkMode, setDarkMode] = useState(false);
// Lifted State - defaults to widget dashboard const [isEditable, setIsEditable] = useState(false); // editable=true means static=false (vice versa)
const [isDelete, setIsDelete] = useState(false);
const [originalLayout, setOriginalLayout] = useState([]);
const [view, setView] = useState("widget"); const [view, setView] = useState("widget");
const [layout, setLayout] = useState([]); const [layout, setLayout] = useState([]);
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) {
const confirmed = window.confirm(
"You have unsaved changes. Discard them and continue?"
);
if (!confirmed) return;
onCancel(); // Discards the layout
}
setView(nextView);
};
return ( return (
<> <>
<Grid css={styles} <Grid css={styles}
templateRows="1fr 11fr" templateRows="1fr 11fr"
templateColumns="1fr 20fr" // templateColumns="1fr 20fr"
templateColumns="auto 1fr"
height="100vh" height="100vh"
width="100vw" width="100vw"
> >
@@ -26,6 +54,7 @@ export default function Dashboard() {
<Header <Header
view={ view } view={ view }
setView={ setView } setView={ setView }
onChangeView={ handleViewChange }
/> />
</GridItem> </GridItem>
<GridItem colSpan={1}> <GridItem colSpan={1}>
@@ -33,7 +62,8 @@ export default function Dashboard() {
darkMode={ darkMode } darkMode={ darkMode }
setDarkMode={ setDarkMode } setDarkMode={ setDarkMode }
view={ view } view={ view }
onChangeView={ setView } onChangeView={ handleViewChange }
isEditabe={ isEditable }
/> />
</GridItem> </GridItem>
<GridItem colSpan={1} width="100%" overflow="auto"> <GridItem colSpan={1} width="100%" overflow="auto">
@@ -43,7 +73,15 @@ export default function Dashboard() {
setView={ setView } setView={ setView }
layout={ layout } layout={ layout }
setLayout={ setLayout } setLayout={ setLayout }
originalLayout={ originalLayout }
setOriginalLayout={ setOriginalLayout }
onLayoutChange={( newLayout ) => setLayout( newLayout )} onLayoutChange={( newLayout ) => setLayout( newLayout )}
onChangeView={ handleViewChange }
onCancel={ onCancel }
isEditable={ isEditable }
setIsEditable={ setIsEditable }
isDelete={ isDelete }
setIsDelete={ setIsDelete }
/> />
</GridItem> </GridItem>
</Grid> </Grid>
@@ -8,7 +8,7 @@ const user = {
// avatar: "src\\assets\\user_profile.svg" // avatar: "src\\assets\\user_profile.svg"
} }
export default function Header({ view, setView }) { export default function Header({ onChangeView }) {
const recipe = useRecipe({ key: "header" }); const recipe = useRecipe({ key: "header" });
const styles = recipe(); const styles = recipe();
@@ -23,8 +23,7 @@ export default function Header({ view, setView }) {
<UserAvatarMenu <UserAvatarMenu
name={user.name} name={user.name}
avatar={user.avatar} avatar={user.avatar}
view={ view } onChangeView={ onChangeView }
setView={ setView }
/> />
</Flex> </Flex>
</chakra.div> </chakra.div>
@@ -1,7 +1,7 @@
import { Avatar, Menu, Portal, Box } from "@chakra-ui/react" import { Avatar, Menu, Portal, Box } from "@chakra-ui/react"
export default function UserAvatarMenu({ view, setView, name, avatar }) { export default function UserAvatarMenu({ onChangeView, name, avatar }) {
return ( return (
<Menu.Root> <Menu.Root>
<Menu.Trigger focusRing="none"> <Menu.Trigger focusRing="none">
@@ -21,10 +21,10 @@ export default function UserAvatarMenu({ view, setView, name, avatar }) {
{/* zIndex: overlay = 1300 */} {/* zIndex: overlay = 1300 */}
<Menu.Positioner zIndex="overlay"> <Menu.Positioner zIndex="overlay">
<Menu.Content> <Menu.Content>
<Menu.Item value="account" onClick={() => setView("account")}> <Menu.Item value="account" onClick={() => onChangeView("account")}>
Account Account
</Menu.Item> </Menu.Item>
<Menu.Item value="settings" onClick={() => setView("settings")}> <Menu.Item value="settings" onClick={() => onChangeView("settings")}>
Settings Settings
</Menu.Item> </Menu.Item>
<Menu.Item value="logout" onClick={() => console.log("Log out triggered")}> <Menu.Item value="logout" onClick={() => console.log("Log out triggered")}>
@@ -1,6 +1,5 @@
import { useSlotRecipe, Box, Button, IconButton } from "@chakra-ui/react"; import { useSlotRecipe, Box, Button, IconButton } from "@chakra-ui/react";
import { ColorModeButton, useColorMode } from "@/components/ui/color-mode"; import { ColorModeButton, useColorMode } from "@/components/ui/color-mode";
// import { Tooltip } from "@/components/ui/tooltip";
import Tooltip from "../Canvas/components/Tooltip.jsx"; import Tooltip from "../Canvas/components/Tooltip.jsx";
import { FaVolcano } from "react-icons/fa6"; import { FaVolcano } from "react-icons/fa6";
import { FaHome } from "react-icons/fa"; import { FaHome } from "react-icons/fa";
@@ -9,7 +8,6 @@ import { FaMapMarkerAlt } from "react-icons/fa";
import { HiUsers } from "react-icons/hi2"; import { HiUsers } from "react-icons/hi2";
import { VscThreeBars } from "react-icons/vsc"; import { VscThreeBars } from "react-icons/vsc";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Seismic } from "../Canvas/views/DashboardCanvasNav/Seismic";
import { DASHBOARD_VIEWS } from "@/constants/viewKeys"; import { DASHBOARD_VIEWS } from "@/constants/viewKeys";
export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) { 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 // Track currrent view from both Sidebar and Canvas when 'view' changes
useEffect(() => { useEffect(() => {
// console.log("typeof view =", typeof view);
console.log("Current view = ", view); console.log("Current view = ", view);
const newIndex = buttons.findIndex((btn) => { const newIndex = buttons.findIndex((btn) => {
if (btn.view === "widget") { 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 ) { if (newIndex !== -1 ) {
@@ -52,7 +49,6 @@ export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
return ( return (
<Box <Box
// key={button.name}
css={styles.root} css={styles.root}
width={isOpen ? "210px" : "71px"} width={isOpen ? "210px" : "71px"}
transition="width 0.4s ease" transition="width 0.4s ease"
@@ -76,7 +72,6 @@ export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
key={button.name} key={button.name}
css={styles.buttons} css={styles.buttons}
onClick={() => { onClick={() => {
// setActiveButton(idx);
onChangeView(button.view); // Trigger state change in Dashboard onChangeView(button.view); // Trigger state change in Dashboard
}} }}
bg={activeButton === idx ? "primary":undefined} bg={activeButton === idx ? "primary":undefined}