Rename and Restructure

+ took out extra 'layout' directory.
+ 'Canvas' postfix removed from all files in the views directory.
+ "WidgetCanvas" renamed to "MyDashboard"
This commit is contained in:
2025-08-05 12:57:18 -07:00
parent 03a335710f
commit ff95f2cbc0
27 changed files with 20 additions and 20 deletions
@@ -1,27 +0,0 @@
import { Box } from "@chakra-ui/react";
import GlobalMapCanvas from "./views/GlobalMapCanvas";
import RegionalMapCanvas from "./views/RegionalMapCanvas";
import VolcanoCanvas from "./views/VolcanoCanvas";
import AdminCanvas from "./views/AdminCanvas";
import AccountCanvas from "./views/AccountCanvas"
import SettingsCanvas from "./views/SettingsCanvas"
import { HomeCanvas } from "./views/HomeCanvas";
import { DASHBOARD_VIEWS } from "@/constants/viewKeys";
export default function Canvas({...props}) {
const { view } = props;
return (
<>
{/* Check if view is a tab inside the Canvas Header */}
{ DASHBOARD_VIEWS.includes(view) && (
<HomeCanvas width="100%" height="100%" {...props}/>
)}
{view === "global" && <GlobalMapCanvas />}
{view === "regional" && <RegionalMapCanvas />}
{view === "volcano" && <VolcanoCanvas />}
{view === "admin" && <AdminCanvas />}
{view === "account" && <AccountCanvas />}
{view === "settings" && <SettingsCanvas />}
</>
);
}
@@ -1,35 +0,0 @@
import { IoIosArrowBack } from "react-icons/io";
import { Menu, Portal } from "@chakra-ui/react";
import { v4 as uuid } from 'uuid';
import AlertLevel from "./AlertLevelWidget";
export default function AddWidgetMenu({onAddWidget, widgetArray, setWidgetArray}) {
function appendWidget(name) {
const newID = uuid(); // could be replaced with something else like guid
setWidgetArray([ // updating widget array containing info on the types of widgets
...widgetArray,
{i: newID, widget: name}
]);
onAddWidget(newID); // updating array containing info on layout of widgets
}
return (
<Menu.Root>
<Menu.TriggerItem>
<IoIosArrowBack /> Add Widget
</Menu.TriggerItem>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item
value="AlertLevel"
onClick={() => appendWidget(AlertLevel)}
>
Alert Level
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
@@ -1,10 +0,0 @@
import Widget from "./Widget";
import { Text } from "@chakra-ui/react";
export default function AlertLevel({id, isEditable, isDelete, DeleteWidget}) {
return (
<Widget id={id} title="Alert Level" isEditable={isEditable} isDelete={isDelete} DeleteWidget={DeleteWidget}>
<Text>Input volcano and country</Text>
</Widget>
);
}
@@ -1,7 +0,0 @@
import { Button } from "@chakra-ui/react";
export function CancelButton ({onCancel}) {
return (
<Button onClick={onCancel} borderRadius="md">Cancel</Button>
)
}
@@ -1,9 +0,0 @@
import { Button } from "@chakra-ui/react";
export default function DeleteAllButton ({onDeleteAll}) {
return (
<Button onClick={onDeleteAll} color="#FFFFFF" bg="#EF4444" _hover={{ bg: "bg.error", color: "#EF4444"}}>
Delete All
</Button>
)
}
@@ -1,7 +0,0 @@
import { Button } from "@chakra-ui/react";
export function SaveButton ({onSave}) {
return (
<Button onClick={onSave} borderRadius="md" bg="primary" _hover={{bg: "primaryLt"}}>Save</Button>
)
}
@@ -1,16 +0,0 @@
import { Input, InputGroup } from "@chakra-ui/react";
import { FiSearch } from "react-icons/fi"
export function SearchBar({ placeholder="Enter a volcano to search", size="sm", width="210px" }) {
return (
<InputGroup startElement={<FiSearch />}>
<Input
placeholder={placeholder}
size={size}
width={width}
borderRadius="full"
bg="base100"
/>
</InputGroup>
);
}
@@ -1,35 +0,0 @@
import { IconButton, Menu, Portal } from "@chakra-ui/react";
import { FiSettings } from "react-icons/fi";
import AddWidgetMenu from "./AddWidgetMenu";
export function SettingsButton({onAddWidget, onEditWidgets, onDeleteWidgets, widgetArray, setWidgetArray}) {
return (
<Menu.Root>
<Menu.Trigger asChild >
<IconButton variant="ghost" size="md" borderRadius="xl">
<FiSettings />
</IconButton>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<AddWidgetMenu onAddWidget={onAddWidget} setWidgetArray={setWidgetArray} widgetArray={widgetArray}/>
<Menu.Item onClick={onEditWidgets} value ="editwidget">Edit Widgets</Menu.Item>
<Menu.Item
onClick={onDeleteWidgets}
color="fg.error"
_hover={{ bg: "bg.error", color: "fg.error "}}
>
Delete Widgets
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
);
}
@@ -1,52 +0,0 @@
import { useState, useRef } from "react";
import { chakra, useSlotRecipe } from "@chakra-ui/react";
export default function Tooltip ({tooltipkey, content, sidebarIsOpen, children}) {
const [isVisible, setIsVisible] = useState(!sidebarIsOpen);
const recipe = useSlotRecipe({ key: "sidebar" });
const styles = recipe();
const timeoutId = useRef(null);
return (
<chakra.div
width={sidebarIsOpen ? "194px" : "55px"}
transition="width 0.4s ease"
onMouseEnter={() => { timeoutId.current = setTimeout(() => setIsVisible(true), 1100); }}
onMouseLeave={() => {
if (timeoutId.current) {
clearTimeout(timeoutId.current);
timeoutId.current = null;
}
setIsVisible(false);
}}
onClick={() => {
if (timeoutId.current) {
clearTimeout(timeoutId.current);
timeoutId.current = null;
}
setIsVisible(false);
}}
>
{children}
{isVisible && !sidebarIsOpen && (
<>
<chakra.div
fontSize="xs"
position="absolute"
bg="gray.700"
color="white"
left="65px"
px={2}
py={1}
borderRadius="md"
whiteSpace="nowrap"
zIndex={10}
>
{content}
</chakra.div>
</>
)}
</chakra.div>
)
}
@@ -1,29 +0,0 @@
import { useSlotRecipe, chakra, Flex, CloseButton, Text } from "@chakra-ui/react"
export default function Widget({ id, title, isEditable, isDelete, DeleteWidget, children }) {
const recipe = useSlotRecipe({ key: "widget" });
const styles = recipe();
return (
<>
<chakra.div css={styles.header} cursor={isEditable ? "move":"default"}>
{isDelete ? (
<>
<Flex flexDirection="row" justifyContent="space-between" alignItems="center">
<Text className="widget-handle" w="100%" fontWeight="bold">{title}</Text>
<CloseButton onClick={() => DeleteWidget(id)} size="xs" _hover={{bg: "tomato", color: "white"}}/>
</Flex>
</>
):(
<>
<Text className="widget-handle" fontWeight="bold">{title}</Text>
</>
)}
</chakra.div>
<chakra.div css={styles.canvas} >
{children}
</chakra.div>
</>
);
}
@@ -1,75 +0,0 @@
import { Box, Flex, Field, Input, Stack, Heading, Button } from "@chakra-ui/react"
import { useForm } from 'react-hook-form';
export default function AccountCanvas() {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm();
const onSubmit = (data) => {
console.log("Form submitted:", data);
}
console.log(watch("example")); // watch input value by passing the name of it
return (
<Flex
height="100vh"
direction="column"
align="start"
justify="start"
bg="gray.50"
pt={6}
pl={6}
>
<Heading as="h1" mb={2}>Account</Heading>
<Box
as="form"
onSubmit={handleSubmit(onSubmit)}
border="1px solid"
borderColor="gray.300"
borderRadius="md"
padding={4}
bg="white"
boxShadow="md"
width="100%"
maxW="sm"
>
{/* <Text fontSize="xl" fontWeight="bold" mb={6}>
Account
</Text> */}
<Stack gap="6" maxW="sm" css={{ "--field-label-width": "96px" }}>
<Field.Root orientation="horizontal">
<Field.Label>User Name</Field.Label>
<Input {...register("username")} placeholder="David Johnston" flex="1" />
</Field.Root>
<Field.Root orientation="horizontal">
<Field.Label> First Name</Field.Label>
<Input {...register("firstname")} placeholder="David" flex="1" />
</Field.Root>
<Field.Root orientation="horizontal">
<Field.Label>Last Name</Field.Label>
<Input {...register("lastname")} placeholder="Johnston" flex="1" />
</Field.Root>
<Field.Root orientation="horizontal">
<Field.Label>Email</Field.Label>
<Input {...register("email")} placeholder="djohntson@usgs.gov" flex="1" />
</Field.Root>
<Button type="submit">
Save
</Button>
</Stack>
</Box>
</Flex>
)
}
@@ -1,24 +0,0 @@
import { Box, Text } from '@chakra-ui/react';
export default function AdminCanvas() {
return (
<Box p={8} mt={4} ml="auto" mr="auto" width="600px">
<Box
bg="bg"
shadow="md"
borderRadius="md"
p={8}
width="600px"
height="100px"
textAlign="center"
display="flex"
alignItems="center"
justifyContent="center"
>
<Text fontsize="2xl" fontWeight="bold" color="gray.700">
Admin Canvas coming soon!
</Text>
</Box>
</Box>
);
}
@@ -1,22 +0,0 @@
import { Box, Text } from "@chakra-ui/react";
export function DailyActivity() {
return(
<Box p={8} mt={4} ml="auto" mr="auto" width="600px">
<Box
bg="bg"
shadow="md"
borderRadius="md"
p={8}
width="600px"
height="100px"
textAlign="center"
display="flex"
alignItems="center"
justifyContent="center"
>
<Text fontsize="2xl" fontWeight="bold" color="gray.700">Daily Activity dashboard coming soon.</Text>
</Box>
</Box>
);
}
@@ -1,22 +0,0 @@
import { Box, Text } from "@chakra-ui/react";
export function Gas() {
return(
<Box p={8} mt={4} ml="auto" mr="auto" width="600px">
<Box
bg="bg"
shadow="md"
borderRadius="md"
p={8}
width="600px"
height="100px"
textAlign="center"
display="flex"
alignItems="center"
justifyContent="center"
>
<Text fontsize="2xl" fontWeight="bold" color="gray.700">Gas dashboard coming soon.</Text>
</Box>
</Box>
);
}
@@ -1,22 +0,0 @@
import { Box, Text } from "@chakra-ui/react";
export function RemoteSensing() {
return(
<Box p={8} mt={4} ml="auto" mr="auto" width="600px">
<Box
bg="bg"
shadow="md"
borderRadius="md"
p={8}
width="600px"
height="100px"
textAlign="center"
display="flex"
alignItems="center"
justifyContent="center"
>
<Text fontsize="2xl" fontWeight="bold" color="gray.700">Remote Sensing dashboard coming soon.</Text>
</Box>
</Box>
);
}
@@ -1,22 +0,0 @@
import { Box, Text } from "@chakra-ui/react";
export function Seismic() {
return(
<Box p={8} mt={4} ml="auto" mr="auto" width="600px">
<Box
bg="bg"
shadow="md"
borderRadius="md"
p={8}
width="600px"
height="100px"
textAlign="center"
display="flex"
alignItems="center"
justifyContent="center"
>
<Text fontsize="2xl" fontWeight="bold" color="gray.700">Seismic dashboard coming soon.</Text>
</Box>
</Box>
);
}
@@ -1,50 +0,0 @@
import GridLayout, { WidthProvider } from 'react-grid-layout';
import { useSlotRecipe, chakra, Flex } from '@chakra-ui/react';
export default function WidgetCanvas({layout, onLayoutChange, isEditable, isDelete, DeleteWidget, widgetArray}) {
console.log("rendering widgetcanvas");
const recipe = useSlotRecipe({ key: "widget" });
const styles = recipe(); // using root recipe for widget below
const ResponsiveGridLayout = WidthProvider(GridLayout); // canvas width now adjusts to window size
const handleDragStart = () => { document.body.style.userSelect = "none"; }; // don't allow text highlighting when dragging
const handleDragStop = () => { document.body.style.userSelect = ""; }; // allow text highlighting all other times
let merged = [];
console.log("widgetArray: ", widgetArray);
console.log("layout: ", layout);
if (widgetArray.length && layout.length) {
const widgetMap = new Map(widgetArray.map(item => [item.i, item]));
merged = layout.map(item => {
return {
...item,
...widgetMap.get(item.i)
};
});
};
return (
<chakra.div width="100%">
<ResponsiveGridLayout
className="layout"
layout={layout}
cols={12} // Total columns in grid
rowHeight={30} //Height of a single row in px
isBounded={true} // Total width of the grid
margin={[10, 10]} // [horizontal, vertical] gap
onLayoutChange={onLayoutChange}
draggableHandle=".widget-handle" // Make only the header draggable
onDragStart={handleDragStart}
onDragStop={handleDragStop}
>
{merged.map((item) => {
const Widget = item.widget;
return (
<Flex key={item.i} css={styles.root} height="100%" >
<Widget id={item.i} isEditable={isEditable} isDelete={isDelete} DeleteWidget={DeleteWidget}/>
</Flex>
)
})}
</ResponsiveGridLayout>
</chakra.div>
);
}
@@ -1,19 +0,0 @@
import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
export default function GlobalMapCanvas() {
const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude]
return (
<MapContainer center={position} zoom={3} style={{ height: "100%", width: "100%" }}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors'
/>
<Marker position={position}>
<Popup>
CVO Home of the volcanoligists
</Popup>
</Marker>
</MapContainer>
);
}
@@ -1,170 +0,0 @@
import { HStack, Flex, Tabs } from "@chakra-ui/react";
import { useState } from "react";
import WidgetCanvas from "./DashboardCanvasNav/WidgetCanvas";
import { SearchBar } from "../components/SearchBar";
import { SettingsButton } from "../components/SettingsButton";
import { DailyActivity } from "./DashboardCanvasNav/DailyActivity";
import { Gas } from "./DashboardCanvasNav/Gas";
import { Seismic } from "./DashboardCanvasNav/Seismic";
import { RemoteSensing } from "./DashboardCanvasNav/RemoteSensing";
import { CancelButton } from "../components/CancelButton";
import { SaveButton } from "../components/SaveButton";
import DeleteAllButton from "../components/DeleteAllButton";
export function HomeCanvas({...props}) {
// deconstructing props
const { width, height, view, layout, setLayout, setOriginalLayout, onLayoutChange, onChangeView, onCancel,
isDelete, setIsDelete, isEditable, setIsEditable, widgetArray, setWidgetArray } = props;
const onAddWidget = (ID) => {
setLayout(prev => [
...prev,
{
i: ID,
x: (prev.length * 3) % 12,
y: Infinity,
w: 3,
h: 3,
static: false
}
]);
onEditWidgets();
};
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
}
const onDeleteWidgets = () => {
console.log("Start deleting widgets...");
setOriginalLayout(layout);
setIsEditable(true);
setIsDelete(true);
}
const DeleteWidget = (ID) => {
console.log("Deleting widget with id: ", ID);
setLayout(layout => layout.filter(w => w.i !== ID));
// add removal for widgetArray here
}
const DeleteAll = () => {
const confirm = window.confirm(
"Are you sure you want to delete all widgets?"
);
if (confirm) {
setLayout([]);
setWidgetArray([]);
onSave();
}
else return;
}
// Prop Forwarding
const widgetCanvasProps = { layout, onLayoutChange, isEditable, isDelete, DeleteWidget, widgetArray };
const settingsButtonProps = { onAddWidget, onEditWidgets, onDeleteWidgets, setWidgetArray, widgetArray };
return (
/* HEADER */
<Tabs.Root
width={width}
height={height}
lazyMount
value={ view }
variant="line"
// Prevent navigation away from 'My Dashboard' while in Edit mode
onValueChange={(e) => {
const nextView = e.value;
onChangeView(nextView);
}}
>
<Flex
as="header"
position="sticky"
top="0"
zIndex="dropdown" // zIndex: 1100
bg="base200"
px={6}
pt={3}
align="center"
justify="space-between"
>
<Tabs.List>
<Tabs.Trigger value="widget" fontSize="lg">
My Dashboard
</Tabs.Trigger>
<Tabs.Trigger value="gas" fontSize="lg">
Gas
</Tabs.Trigger>
<Tabs.Trigger value="seismic" fontSize="lg">
Seismic
</Tabs.Trigger>
<Tabs.Trigger value="remote" fontSize="lg">
Remote Sensing
</Tabs.Trigger>
<Tabs.Trigger value="daily" fontSize="lg">
Daily Activity
</Tabs.Trigger>
</Tabs.List>
<HStack >
{isEditable ? (
<>
<SaveButton onSave={ onSave } />
<CancelButton onCancel={ onCancel } />
{isDelete ? (
<DeleteAllButton onDeleteAll={ DeleteAll } />
):(null)
}
</>
):(
<>
<SearchBar />
<SettingsButton {...settingsButtonProps}/>
</>
)}
</HStack>
</Flex>
{/* CANVAS */}
<Tabs.Content value="widget">
<WidgetCanvas {...widgetCanvasProps}/>
</Tabs.Content>
<Tabs.Content value="gas">
<Gas />
</Tabs.Content>
<Tabs.Content value="seismic">
<Seismic />
</Tabs.Content>
<Tabs.Content value="remote">
<RemoteSensing />
</Tabs.Content>
<Tabs.Content value="daily">
<DailyActivity />
</Tabs.Content>
</Tabs.Root>
);
}
@@ -1,19 +0,0 @@
import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
export default function GlobalMapCanvas() {
const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude]
return (
<MapContainer center={position} zoom={13} style={{ height: "100%", width: "100%" }}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors'
/>
<Marker position={position}>
<Popup>
CVO Home of the volcanoligists
</Popup>
</Marker>
</MapContainer>
);
}
@@ -1,24 +0,0 @@
import { Box, Text } from '@chakra-ui/react';
export default function SettingsCanvas() {
return (
<Box p={8} mt={4} ml="auto" mr="auto" width="600px">
<Box
bg="bg"
shadow="md"
borderRadius="md"
p={8}
width="600px"
height="100px"
textAlign="center"
display="flex"
alignItems="center"
justifyContent="center"
>
<Text fontsize="2xl" fontWeight="bold" color="gray.700">
Settings Canvas coming soon!
</Text>
</Box>
</Box>
);
}
@@ -1,24 +0,0 @@
import { Box, Text } from '@chakra-ui/react';
export default function VolcanoCanvas() {
return (
<Box p={8} mt={4} ml="auto" mr="auto" width="600px">
<Box
bg="bg"
shadow="md"
borderRadius="md"
p={8}
width="600px"
height="100px"
textAlign="center"
display="flex"
alignItems="center"
justifyContent="center"
>
<Text fontsize="2xl" fontWeight="bold" color="gray.700">
Volcano Canvas coming soon!
</Text>
</Box>
</Box>
);
}
@@ -1,72 +0,0 @@
import Header from './Header/Header.jsx';
import Sidebar from './Sidebar/Sidebar.jsx';
import Canvas from './Canvas/Canvas.jsx';
import { Grid, GridItem, useRecipe } from '@chakra-ui/react';
import { useState } from 'react';
export default function Dashboard() {
const recipe = useRecipe({ key: "dashboard" });
const styles = recipe();
const [darkMode, setDarkMode] = useState(false);
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 [layout, setLayout] = useState([]);
const [widgetArray, setWidgetArray] = 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);
};
const onLayoutChange = (newLayout) => setLayout(newLayout);
// Prop Forwarding
const canvasProps = { view, layout, setLayout, setOriginalLayout, onLayoutChange, onChangeView: handleViewChange,
onCancel, isEditable, setIsEditable, isDelete, setIsDelete, widgetArray, setWidgetArray
};
const sidebarProps = { view, onChangeView: handleViewChange, darkMode, setDarkMode };
const headerProps = { onChangeView: handleViewChange };
return (
<Grid css={styles}
templateRows="1fr 11fr"
// templateColumns="1fr 20fr"
templateColumns="auto 1fr"
height="100vh"
width="100vw"
>
<GridItem rowSpan={1} colSpan={2}>
<Header {...headerProps}/>
</GridItem>
<GridItem colSpan={1}>
<Sidebar {...sidebarProps}/>
</GridItem>
<GridItem colSpan={1} width="100%" overflow="auto">
<Canvas {...canvasProps}/>
</GridItem>
</Grid>
);
}
@@ -1,32 +0,0 @@
import { chakra, useRecipe, Box, Button, Stack, Text, Flex } from "@chakra-ui/react"
import UserAvatarMenu from "./UserAvatarMenu.jsx";
//TODO: Dynamically populate per each user
const user = {
name: "venessa kuchenik",
email: "vkuchenik@contractor.usgs.gov",
// avatar: "src\\assets\\user_profile.svg"
}
export default function Header({ onChangeView }) {
const recipe = useRecipe({ key: "header" });
const styles = recipe();
return (
<chakra.div css={styles}>
<Button color="color" variant="plain" textStyle="5xl" fontWeight="bold">VDAP</Button>
<Flex align="center" gap={3} pr={6}>
<Stack gap="0" textAlign="center" cursor="default">
<Text color="color" fontWeight="bold" textStyle="lg">{user.name}</Text>
<Text color="fg.muted" textStyle="sm">{user.email}</Text>
</Stack>
<UserAvatarMenu
name={user.name}
avatar={user.avatar}
onChangeView={ onChangeView }
/>
</Flex>
</chakra.div>
);
}
@@ -1,38 +0,0 @@
import { Avatar, Menu, Portal, Box } from "@chakra-ui/react"
export default function UserAvatarMenu({ onChangeView, name, avatar }) {
return (
<Menu.Root>
<Menu.Trigger focusRing="none">
<Box
rounded="full"
tabIndex={0}
_hover={{ boxShadow: "0 0 0 2px rgb(102, 122, 182)" }}
cursor="pointer"
>
<Avatar.Root shape="full">
<Avatar.Fallback name={name} />
<Avatar.Image src={avatar} />
</Avatar.Root>
</Box>
</Menu.Trigger>
<Portal>
{/* zIndex: overlay = 1300 */}
<Menu.Positioner zIndex="overlay">
<Menu.Content>
<Menu.Item value="account" onClick={() => onChangeView("account")}>
Account
</Menu.Item>
<Menu.Item value="settings" onClick={() => onChangeView("settings")}>
Settings
</Menu.Item>
<Menu.Item value="logout" onClick={() => console.log("Log out triggered")}>
Logout
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
);
}
@@ -1,106 +0,0 @@
import { useSlotRecipe, Box, Button, IconButton } from "@chakra-ui/react";
import { ColorModeButton, useColorMode } from "@/components/ui/color-mode";
import Tooltip from "../Canvas/components/Tooltip.jsx";
import { FaVolcano } from "react-icons/fa6";
import { FaHome } from "react-icons/fa";
import { FaGlobeAmericas } from "react-icons/fa";
import { FaMapMarkerAlt } from "react-icons/fa";
import { HiUsers } from "react-icons/hi2";
import { VscThreeBars } from "react-icons/vsc";
import { useState, useEffect } from "react";
import { DASHBOARD_VIEWS } from "@/constants/viewKeys";
export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
const [isOpen, setIsOpen] = useState(true);
const [activeButton, setActiveButton] = useState(0)
const recipe = useSlotRecipe({ key: "sidebar" });
const styles = recipe();
const { toggleColorMode } = useColorMode();
// this list of btns will come from the db (not every usr will have access to admin)
const buttons = [
{icon: FaHome, name: "Home", view: "widget"},
{icon: FaGlobeAmericas, name: "Global Map", view: "global"},
{icon: FaMapMarkerAlt, name: "Regional Map", view: "regional"},
{icon: FaVolcano, name: "Volcano", view: "volcano"},
{icon: HiUsers, name: "Admin", view: "admin"}
];
const toggleSidebar = () => {
if (isOpen) setIsOpen(false);
else setIsOpen(true);
};
// Track currrent view from both Sidebar and Canvas when 'view' changes
useEffect(() => {
console.log("Current view = ", view);
const newIndex = buttons.findIndex((btn) => {
if (btn.view === "widget") {
return DASHBOARD_VIEWS.includes(view);
}
return btn.view === view;
});
if (newIndex !== -1 ) {
setActiveButton(newIndex);
}
}, [view]);
return (
<Box
css={styles.root}
width={isOpen ? "210px" : "71px"}
transition="width 0.4s ease"
position="relative"
>
<IconButton
aria-label="Toggle Sidebar"
variant="plain"
size="md"
onClick={toggleSidebar}
width="54px"
>
<VscThreeBars />
</IconButton>
{buttons.map((button, idx) => {
const Icon = button.icon;
return (
<Tooltip key={button.name} tooltipkeykey={idx} content={button.name} sidebarIsOpen={isOpen}>
<Button
key={button.name}
css={styles.buttons}
onClick={() => {
onChangeView(button.view); // Trigger state change in Dashboard
}}
bg={activeButton === idx ? "primary":undefined}
color={activeButton === idx ? "#FFFFFF":undefined}
justifyContent="flex-start"
>
<Icon />
<span
style={{
opacity: isOpen ? 1 : 0,
transition: "opacity 0.4s ease",
overflow: "hidden",
whiteSpace: "nowrap",
display: "inline-block",
lineHeight: 1.2
}}
>
{button.name}
</span>
</Button>
</Tooltip>
)
})}
<ColorModeButton
onClick={
() => {
setDarkMode(!darkMode);
toggleColorMode();
}}
position="absolute"
bottom="2"/>
</Box>
);
}