Merge branch 'main' into 'dashboardCanvas/Widget/add-widgets-menu'
# Conflicts: # client/src/components/layout/Canvas/views/DashboardCanvas.jsx
This commit is contained in:
@@ -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 (
|
||||||
|
|||||||
@@ -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='© OpenStreetMap contributors'
|
attribution='© 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='© 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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user