some routes added

This commit is contained in:
2026-06-04 15:30:01 -07:00
parent dd70ec077c
commit fd3cdc69ad
24 changed files with 636 additions and 874 deletions
+2 -1
View File
@@ -6,6 +6,7 @@
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"]
}
},
"jsx": "react"
}
}
+116 -624
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -13,7 +13,6 @@
"@chakra-ui/react": "^3.22.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@tailwindcss/vite": "^4.1.11",
"framer-motion": "^12.23.0",
"leaflet": "^1.9.4",
"lucide-react": "^0.539.0",
@@ -22,10 +21,9 @@
"react-dom": "^19.1.0",
"react-grid-layout": "^1.5.2",
"react-hook-form": "^7.62.0",
"react-icons": "^5.5.0",
"react-leaflet": "^5.0.0",
"react-resizable": "^3.0.5",
"tailwindcss": "^4.1.11",
"react-router-dom": "^7.16.0",
"uuid": "^11.1.0"
},
"devDependencies": {
+51 -2
View File
@@ -1,10 +1,59 @@
import Dashboard from './components/Dashboard.jsx'
import 'leaflet/dist/leaflet.css';
import { Routes, Route } from 'react-router-dom';
import { useRecipe, Grid, GridItem, useDialog } from "@chakra-ui/react";
import { createContext } from "react";
import Login from "@/Login.jsx";
import Sidebar from "./components/Sidebar/Sidebar.jsx";
import DialogPopup from "@/components/Canvas/CanvasComponents/DialogPopup.jsx";
import Header from "@/components/Header/Header.jsx";
import Home from "./components/Canvas/views/Home.jsx";
import RegionalMap from "./components/Canvas/views/RegionalMap.jsx";
import Volcano from "@/components/Canvas/views/Volcano.jsx";
function AppFoundation({children}) {
const recipe = useRecipe({ key: "dashboard" });
const styles = recipe();
const dialog = useDialog();
return (
<>
<DialogPopup dialog={dialog} />
<Grid css={styles}
templateRows="1fr 11fr"
templateColumns="auto 1fr"
height="100vh"
width="100vw"
>
<GridItem rowSpan={1} colSpan={2}>
<Header />
</GridItem>
<GridItem colSpan={1}>
<Sidebar />
</GridItem>
<GridItem colSpan={1} width="100%" overflow="auto">
{children}
</GridItem>
</Grid>
</>
)
}
// function ProtectedRoute({ user, children }) {
// if (user) return children;
// else return <Navigate to="/login" replace />;
// }
function App() {
const sidebarContext = createContext(["Home", "Global Map", "Regional Map", "Volcano", "Admin"]);
// const [user, setUser] = useState(null);
return (
<Dashboard />
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/home" element={<AppFoundation> <Home/> </AppFoundation>} />
<Route path="/regional-map" element={<AppFoundation> <RegionalMap/> </AppFoundation>} />
<Route path="/volcano" element={<AppFoundation> <Volcano/> </AppFoundation>} />
</Routes>
);
}
+91
View File
@@ -0,0 +1,91 @@
import { useContext } from 'react';
import { useForm } from 'react-hook-form';
import {
Button,
Field,
Input,
Stack,
useRecipe,
Center,
Text,
Flex,
Heading,
Highlight,
GridItem,
Grid
} from "@chakra-ui/react"
import { PasswordInput } from "@/components/ui/password-input.jsx";
export default function Login( signedIn, setSignedIn) {
const recipe = useRecipe({ key: "dashboard" }); // styling recipes option
const styles = recipe(); // use style
const {
register,
handleSubmit,
formState: { errors, isSubmitSuccessful },
} = useForm({
defaultValues: {
username: "",
password: "",
}
});
const onSubmit = (data) => {
console.log(data);
// setUser(data.username);
};
return (
<Flex css={styles} width="100%" height="100%">
<Grid
templateRows="1fr 11fr"
height="100vh"
width="100vw"
>
<GridItem rowSpan={1} >
<Flex width="100%" height="100%" align="center" >
<Heading size="4xl" fontWeight="bold" pl="1rem" cursor="default" >
<Highlight query="A" styles={{ color: 'red.600'}}>
VDAP
</Highlight>
</Heading>
</Flex>
</GridItem>
<GridItem rowSpan={11} >
<Flex width="100%" height="100%" justify="center" align="center" direction="column" >
<Center background="white" width="40%" height="50%" borderRadius="1rem">
<form onSubmit={handleSubmit(onSubmit)}>
<Stack gap="4" align="center" maxW="sm">
<Text textStyle="2xl" fontWeight="bold" cursor="default">Sign In</Text>
<Field.Root invalid={!!errors.username}>
<Input
placeholder="username"
{...register("username", {required: "Required"})}
/>
<Field.ErrorText>{errors.username?.message}</Field.ErrorText>
</Field.Root>
<Field.Root invalid={!!errors.password}>
<PasswordInput
placeholder="password"
{...register("password", {required: "Required"})}
/>
<Field.ErrorText>{errors.password?.message}</Field.ErrorText>
</Field.Root>
<Button
type="submit"
disabled={isSubmitSuccessful}
>
Submit
</Button>
</Stack>
</form>
</Center>
</Flex>
</GridItem>
</Grid>
</Flex>
);
}
@@ -2,7 +2,7 @@ import Widget from "./Widget";
import { Stack, Box, Field, Input, Button, Text, Flex } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { useState } from "react";
import { LuAsterisk } from "react-icons/lu";
import { Asterisk } from 'lucide-react';
export default function AlertLevel({id, isEditable, isDelete, DeleteWidget}) {
const { register, handleSubmit, reset, formState: {errors} } = useForm();
@@ -44,7 +44,7 @@ export default function AlertLevel({id, isEditable, isDelete, DeleteWidget}) {
<Flex position="relative" dir="column" gap="0.5" width="100%">
<Stack direction="row" width="100%">
<Field.Label display="flex" whiteSpace="nowrap" fontWeight="bold">
Volcano Name <LuAsterisk color="tomato" />
Volcano Name <Asterisk color="tomato" />
</Field.Label>
<Input
border={errors.volcanoname ? "2px solid" : undefined}
@@ -1,4 +1,4 @@
import { RiExpandDiagonal2Line } from "react-icons/ri";
import { MoveDiagonal2 } from 'lucide-react';
import React from "react";
const CustomResizeHandle = React.forwardRef((props, ref) => {
@@ -19,7 +19,7 @@ const CustomResizeHandle = React.forwardRef((props, ref) => {
zIndex: "1",
}}
>
<RiExpandDiagonal2Line />
<MoveDiagonal2 />
</div>
);
});
@@ -1,8 +1,10 @@
import { Box, Menu, Portal } from '@chakra-ui/react'
import WidgetMenu from './WidgetMenu';
import AlertLevel from "@/components/Canvas/CanvasComponents/AlertLevelWidget.jsx";
export default function FirstWidgetButton({ appendWidget }) {
const triggerItem = (<>
const triggerItem = (
<>
<Menu.Trigger asChild>
<Box
p={6}
@@ -27,10 +29,30 @@ export default function FirstWidgetButton({ appendWidget }) {
Add Your First Widget
</Box>
</Menu.Trigger>
</>);
</>
);
const positioning = { placement: "bottom-middle" };
const WidgetMenuProps = { appendWidget, triggerItem, positioning };
// const WidgetMenuProps = { appendWidget, triggerItem, positioning };
return (
<WidgetMenu {...WidgetMenuProps} />
// <WidgetMenu {...WidgetMenuProps} />
<Menu.Root positioning={positioning}>
{triggerItem}
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item
value="AlertLevel"
onClick={() => appendWidget(AlertLevel, 4, 7, 4, 7, 8, 14)} // width and height are passed through here for each widget
>
Alert Level
</Menu.Item>
<Menu.Item value="CO2">CO2</Menu.Item>
<Menu.Item value="Ivans">IVANS</Menu.Item>
<Menu.Item value="PlumeHeights">Plume Heights</Menu.Item>
<Menu.Item value="AlertLevelChanges">Alert Level Changes</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
);
}
@@ -1,8 +1,7 @@
import { Box, Checkbox, Separator , CloseButton, Text, chakra, Field, IconButton, Icon, Input, Fieldset, Flex } from "@chakra-ui/react";
import { useState } from "react";
import { FiSettings } from "react-icons/fi";
import { IoIosArrowDown } from "react-icons/io";
import { IoIosArrowUp } from "react-icons/io";
import { ChevronDown } from 'lucide-react';
import { ChevronUp } from 'lucide-react';
export default function MapSettingsButton() {
const [open, setOpen] = useState(false);
@@ -16,7 +15,7 @@ export default function MapSettingsButton() {
</Box>
):(
<IconButton boxShadow="0 0 0 2px rgba(119, 119, 119, 0.4)" size="sm" variant="subtle" onClick={() => setOpen(true)}>
<FiSettings />
<Settings />
</IconButton>
);
}
@@ -57,7 +56,7 @@ function Lightning() {
<Checkbox.Label >Lightning</Checkbox.Label>
</Checkbox.Root>
<Icon pt="1" size="md" variant="none" onClick={() => setOpen(!open)}>
{open ? (<IoIosArrowUp />):(<IoIosArrowDown />)}
{open ? (<ChevronUp />):(<ChevronDown />)}
</Icon>
{open && (<LightningOptions />)}
</Box>
@@ -1,9 +1,9 @@
import { Input, InputGroup } from "@chakra-ui/react";
import { FiSearch } from "react-icons/fi"
import { Search } from 'lucide-react';
export function SearchBar({ placeholder="Enter a volcano to search", size="sm", width="210px" }) {
return (
<InputGroup startElement={<FiSearch />}>
<InputGroup startElement={<Search />}>
<Input
placeholder={placeholder}
size={size}
@@ -1,16 +1,16 @@
import { IconButton, Menu, Portal } from "@chakra-ui/react";
import { IoIosArrowBack } from "react-icons/io";
import { FiSettings } from "react-icons/fi";
import { ArrowLeft } from 'lucide-react';
import { Settings } from 'lucide-react';
import WidgetMenu from "./WidgetMenu";
export function SettingsButton({ onEditWidgets, appendWidget}) {
const triggerItem = (<> <Menu.TriggerItem> <IoIosArrowBack /> Add Widget</Menu.TriggerItem> </>);
const triggerItem = (<> <Menu.TriggerItem> <ArrowLeft /> Add Widget</Menu.TriggerItem> </>);
const WidgetMenuProps = { appendWidget, triggerItem }
return (
<Menu.Root>
<Menu.Trigger asChild >
<IconButton variant="ghost" size="md" borderRadius="xl">
<FiSettings />
<Settings />
</IconButton>
</Menu.Trigger>
<Portal>
@@ -1,11 +1,9 @@
import { useState, useRef } from "react";
import { chakra, useSlotRecipe } from "@chakra-ui/react";
import { chakra } from "@chakra-ui/react";
export default function Tooltip ({tooltipkey, content, sidebarIsOpen, children}) {
const [isVisible, setIsVisible] = useState(false);
const recipe = useSlotRecipe({ key: "sidebar" });
const styles = recipe();
const timeoutId = useRef(null);
return (
@@ -30,7 +28,6 @@ export default function Tooltip ({tooltipkey, content, sidebarIsOpen, children})
>
{children}
{isVisible && !sidebarIsOpen && (
<>
<chakra.div
fontSize="xs"
position="absolute"
@@ -45,7 +42,6 @@ export default function Tooltip ({tooltipkey, content, sidebarIsOpen, children})
>
{content}
</chakra.div>
</>
)}
</chakra.div>
)
@@ -1,37 +1,30 @@
import { useSlotRecipe, chakra, Flex, CloseButton, Text, IconButton } from "@chakra-ui/react"
import { GrUndo } from "react-icons/gr";
import { Undo } from 'lucide-react';
export default function Widget(props) {
// deconstructing props
const { id, title, isEditable, isDelete, DeleteWidget, isSubmit, setIsSubmit, children } = props;
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>
</>
):(
<>
<Flex flexDirection="row" justifyContent="space-between" alignItems="center">
<Text className="widget-handle" w="100%" display="flex" alignItems="center" height="32px" fontWeight="bold">{title}</Text>
{isSubmit ? (
<IconButton onClick={() => setIsSubmit(false)} size="xs" bg="base300" cursor="pointer"><GrUndo fill="text" /></IconButton>
):(null)}
</Flex>
</>
{isDelete ? (
<CloseButton onClick={() => DeleteWidget(id)} size="xs" _hover={{bg: "tomato", color: "white"}}/>
):(
isSubmit ? (
<IconButton onClick={() => setIsSubmit(false)} size="xs" bg="base300" cursor="pointer"><Undo fill="text" /></IconButton>
):null
)}
</Flex>
</chakra.div>
<chakra.div height="100%" mb="2">
<Flex height="100%" mb="2">
{children}
</chakra.div>
</Flex>
</>
);
}
+57 -40
View File
@@ -1,4 +1,5 @@
import { HStack, Flex, Tabs } from "@chakra-ui/react";
import { useState } from "react";
import MyDashboard from "./HomeTabs/MyDashboard";
import { SearchBar } from "../CanvasComponents/SearchBar";
import { SettingsButton } from "../CanvasComponents/SettingsButton";
@@ -13,13 +14,16 @@ import { v4 as uuid } from 'uuid';
import { CanvasHeaderTabs } from '../CanvasComponents/CanvasHeaderTabs';
import { DASHBOARD_TABS } from "@/constants/viewKeys";
export default function Home(props) {
// deconstructing props
const { view, layout, setLayout, setOriginalLayout, onLayoutChange, onChangeView, onCancel, setOriginalWidgets,
isDelete, setIsDelete, beginEditIfNeeded, isEditable, setIsEditable, widgetArray, setWidgetArray } = props;
export default function Home() {
const [isEditable, setIsEditable] = useState(false); // editable=true means static=false (vice versa)
const [isDelete, setIsDelete] = useState(false);
const [originalLayout, setOriginalLayout] = useState([]);
const [originalWidgets, setOriginalWidgets] = useState([]);
const [layout, setLayout] = useState([]);
const [widgetArray, setWidgetArray] = useState([]);
function appendWidget(name, w, h, minw, minh, maxw, maxh) {
beginEditIfNeeded(); // Save state before anything else
function appendWidget(name, w, h, min_w, minh, max_w, max_h) {
saveLayoutState(); // Save state before anything else
const newID = uuid(); // could be replaced with something else like guid
const COLS = 16;
@@ -30,7 +34,7 @@ export default function Home(props) {
...widgetArray,
{i: newID, widget: name }
]);
// onAddWidget(newID, w, h, minw, minh, maxw, maxh); // updating array containing info on layout of widgets
// onAddWidget(newID, w, h, min_w, minh, max_w, max_h); // updating array containing info on layout of widgets
setLayout(prev => [
...prev,
@@ -40,10 +44,10 @@ export default function Home(props) {
y: 0,
w: w,
h: h,
minW: minw,
minW: min_w,
minH: minh,
maxW: maxw,
maxH: maxh,
maxW: max_w,
maxH: max_h,
static: false,
resizeHandles: ['se']
},
@@ -67,7 +71,7 @@ export default function Home(props) {
const onEditWidgets = () => {
console.log("Start deleting widgets...");
beginEditIfNeeded(); // Save state before anything else
saveLayoutState(); // Save state before anything else
setLayout(prevLayout =>
prevLayout.map(item => ({
...item,
@@ -96,39 +100,52 @@ export default function Home(props) {
else return;
}
const saveLayoutState = () => {
if ( !isEditable ) {
setOriginalLayout(layout);
setOriginalWidgets(widgetArray);
}
};
const onCancel = () => {
console.log("canceling changes...");
console.log(originalWidgets);
if (originalLayout.length) {
setLayout(originalLayout.map(item => ({
...item,
static: true
})));
}
setWidgetArray(originalWidgets);
setIsEditable(false);
setIsDelete(false);
setOriginalLayout([]);
setOriginalWidgets([]);
}
const onLayoutChange = (newLayout) => setLayout(newLayout);
// Prop Forwarding
const MyDashboardProps = { layout, onLayoutChange, isEditable, beginEditIfNeeded, isDelete, DeleteWidget, widgetArray, appendWidget };
const MyDashboardProps = { layout, onLayoutChange, isEditable, saveLayoutState, isDelete, DeleteWidget, widgetArray, appendWidget };
const settingsButtonProps = { onEditWidgets, appendWidget };
return (
/* HEADER */
<Tabs.Root
width="100%"
height="100%"
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"
pl={5}
pr={2}
pt={3}
pb={2}
align="center"
justify="space-between"
width="100%"
>
<CanvasHeaderTabs tabs={DASHBOARD_TABS} />
/* SUB HEADER */
<Tabs.Root width="100%" height="100%" lazyMount variant="line">
<Flex as="header" position="sticky" top="0" zIndex="dropdown" bg="base200" p={5} align="center" justify="space-between" width="100%">
{/* TABS */}
{/* <CanvasHeaderTabs tabs={DASHBOARD_TABS} />*/} {/* TODO: rename to SubHeaderTabs */}
<Tabs.List>
{DASHBOARD_TABS.map(({ value, label }) =>
<Tabs.Trigger key={value} value={value} fontSize="lg">
{ label }
</Tabs.Trigger>
)}
</Tabs.List>
{/* EDIT MODE BUTTONS */}
<HStack width="258px" justifyContent="flex-end">
{isEditable ? (
<>
@@ -4,7 +4,6 @@ import CustomResizeHandle from '../../CanvasComponents/CustomResizeHandle';
import FirstWidgetButton from '../../CanvasComponents/FirstWidgetButton';
export default function MyDashboard({layout, onLayoutChange, isEditable, isDelete, DeleteWidget, widgetArray, appendWidget}) {
console.log("rendering MyDashboard");
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
@@ -12,9 +11,6 @@ export default function MyDashboard({layout, onLayoutChange, isEditable, isDelet
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 => {
@@ -23,7 +19,7 @@ export default function MyDashboard({layout, onLayoutChange, isEditable, isDelet
...widgetMap.get(item.i)
};
});
};
}
// Show large 'Add Widget' button when dashboard is empty
if ( !layout.length ) {
@@ -43,7 +39,6 @@ export default function MyDashboard({layout, onLayoutChange, isEditable, isDelet
// Show responsive grid of widgets
return (
<chakra.div width="100%">
<ResponsiveGridLayout
useCSSTransforms={false} // remove the sliding animation of the widgets
className="layout"
@@ -63,12 +58,17 @@ export default function MyDashboard({layout, onLayoutChange, isEditable, isDelet
const Widget = item.widget;
return (
<Flex key={item.i} css={styles.root} boxShadow="md" height="100%" >
<Widget id={item.i} isEditable={isEditable} isDelete={isDelete} DeleteWidget={DeleteWidget} appendWidget={appendWidget}/>
<Widget
id={item.i}
isEditable={isEditable}
isDelete={isDelete}
DeleteWidget={DeleteWidget}
appendWidget={appendWidget}
/>
</Flex>
);
})
}
</ResponsiveGridLayout>
</chakra.div>
);
}
+10 -6
View File
@@ -1,5 +1,7 @@
import { chakra, useRecipe, Box, Button, Stack, Text, Flex } from "@chakra-ui/react"
import { useRecipe, Button, Stack, Text, Flex } from "@chakra-ui/react"
import UserAvatarMenu from "./UserAvatarMenu.jsx";
import { useNavigate } from "react-router-dom";
import { NavLink } from "react-router-dom";
//TODO: Dynamically populate per each user
const user = {
@@ -8,13 +10,16 @@ const user = {
// avatar: "src\\assets\\user_profile.svg"
}
export default function Header({ onChangeView }) {
export default function Header() {
const navigate = useNavigate();
const recipe = useRecipe({ key: "header" });
const styles = recipe();
return (
<chakra.div css={styles}>
<Button onClick={() => onChangeView("mydashboard")} color="color" variant="plain" textStyle="5xl" fontWeight="bold">VDAP</Button>
<Flex css={styles}>
<Button as={NavLink} to={"/home"} 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>
@@ -23,10 +28,9 @@ export default function Header({ onChangeView }) {
<UserAvatarMenu
name={user.name}
avatar={user.avatar}
onChangeView={ onChangeView }
/>
</Flex>
</chakra.div>
</Flex>
);
}
@@ -1,7 +1,8 @@
import { Avatar, Menu, Portal, Box } from "@chakra-ui/react"
import { Avatar, Menu, Portal, Box } from "@chakra-ui/react";
import { NavLink } from "react-router-dom";
export default function UserAvatarMenu({ onChangeView, name, avatar }) {
export default function UserAvatarMenu({ name, avatar }) {
return (
<Menu.Root>
<Menu.Trigger focusRing="none">
@@ -21,7 +22,7 @@ export default function UserAvatarMenu({ onChangeView, name, avatar }) {
{/* zIndex: overlay = 1300 */}
<Menu.Positioner zIndex="overlay">
<Menu.Content>
<Menu.Item value="settings" onClick={() => onChangeView("personal")}>
<Menu.Item value="settings" as={NavLink} to={"/settings"}>
Settings
</Menu.Item>
<Menu.Item value="logout" onClick={() => console.log("Log out triggered")}>
+33 -62
View File
@@ -1,89 +1,61 @@
import { useSlotRecipe, Box, Button, IconButton } from "@chakra-ui/react";
import { ColorModeButton, useColorMode } from "@/components/ui/color-mode";
import { useState } from "react";
import { NavLink } from "react-router-dom";
import Tooltip from "../Canvas/CanvasComponents/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 { MdOutlineSecurity } from "react-icons/md";
import { IoPersonCircleSharp } from "react-icons/io5";
import { useState, useEffect, useMemo } from "react";
import { DASHBOARD_VIEWS } from "@/constants/viewKeys";
import { KeyRound } from 'lucide-react';
import { Mountain, House, Earth, MapPin, ShieldUser, Menu, CircleUserRound, KeyRound } from 'lucide-react';
export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
export default function Sidebar() {
const { toggleColorMode } = useColorMode();
const [isOpen, setIsOpen] = useState(true);
const [activeButton, setActiveButton] = useState(0)
const recipe = useSlotRecipe({ key: "sidebar" });
const styles = recipe();
const { toggleColorMode } = useColorMode();
const buttons = useMemo(() => {
if (["settings", "personal", "security"].includes(view)) {
return [
{icon: IoPersonCircleSharp, name: "Personal Info", view: "personal"},
{icon: KeyRound, name: "Security", view: "security"},
// const buttons = useMemo(() => {
// if (["settings", "personal", "security"].includes(view)) {
// return [
// {icon: CircleUserRound, name: "Personal Info", url: "/personal"},
// {icon: KeyRound, name: "Security", url: "/security"},
// ];
// }
// return [
// {icon: House, name: "Home", url: "/mydashboard"},
// {icon: Earth, name: "Global Map", url: "/global"},
// {icon: MapPin, name: "Regional Map", url: "/regional"},
// {icon: Mountain, name: "Volcano", url: "/volcanohome"},
// {icon: ShieldUser, name: "Admin", url: "/medataentry"}
// ];
// }, [view]);
const buttons = [
{icon: House, name: "Home", url: "/home"},
{icon: Earth, name: "Global Map", url: "/GlobalMap"},
{icon: MapPin, name: "Regional Map", url: "/regional-map"},
{icon: Mountain, name: "Volcano", url: "/volcano"},
{icon: ShieldUser, name: "Admin", url: "/medataentry"}
];
}
return [
{icon: FaHome, name: "Home", view: "mydashboard"},
{icon: FaGlobeAmericas, name: "Global Map", view: "global"},
{icon: FaMapMarkerAlt, name: "Regional Map", view: "regional"},
{icon: FaVolcano, name: "Volcano", view: "volcanohome"},
{icon: HiUsers, name: "Admin", view: "medataentry"}
];
}, [view]);
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 === "mydashboard") {
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 />
<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">
<Menu />
</IconButton>
{buttons.map((button, idx) => {
const Icon = button.icon;
return (
<Tooltip key={button.name} tooltipkeykey={idx} content={button.name} sidebarIsOpen={isOpen}>
<Tooltip key={button.name} tooltipkey={idx} content={button.name} sidebarIsOpen={isOpen}>
<Button
as={NavLink}
to={button.url}
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 />
@@ -105,7 +77,6 @@ export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
})}
<ColorModeButton
onClick={() => {
setDarkMode(!darkMode);
toggleColorMode();
}}
position="absolute"
+3 -2
View File
@@ -4,7 +4,8 @@ import { ClientOnly, IconButton, Skeleton, Span } from '@chakra-ui/react'
import { ThemeProvider, useTheme } from 'next-themes'
import * as React from 'react'
import { LuMoon, LuSun } from 'react-icons/lu'
import { Moon, Sun } from 'lucide-react';
export function ColorModeProvider(props) {
return (
@@ -32,7 +33,7 @@ export function useColorModeValue(light, dark) {
export function ColorModeIcon() {
const { colorMode } = useColorMode()
return colorMode === 'dark' ? <LuMoon /> : <LuSun />
return colorMode === 'dark' ? <Moon /> : <Sun />
}
export const ColorModeButton = React.forwardRef(
+122
View File
@@ -0,0 +1,122 @@
'use client'
import {
Box,
HStack,
IconButton,
Input,
InputGroup,
Stack,
mergeRefs,
useControllableState,
} from '@chakra-ui/react'
import * as React from 'react'
import { Eye, EyeClosed } from 'lucide-react';
// import { LuEye, LuEyeOff } from 'react-icons/lu'
export const PasswordInput = React.forwardRef(
function PasswordInput(props, ref) {
const {
rootProps,
defaultVisible,
visible: visibleProp,
onVisibleChange,
visibilityIcon = { on: <Eye />, off: <EyeClosed /> },
...rest
} = props
const [visible, setVisible] = useControllableState({
value: visibleProp,
defaultValue: defaultVisible || false,
onChange: onVisibleChange,
})
const inputRef = React.useRef(null)
return (
<InputGroup
endElement={
<VisibilityTrigger
disabled={rest.disabled}
onPointerDown={(e) => {
if (rest.disabled) return
if (e.button !== 0) return
e.preventDefault()
setVisible(!visible)
}}
>
{visible ? visibilityIcon.off : visibilityIcon.on}
</VisibilityTrigger>
}
{...rootProps}
>
<Input
{...rest}
ref={mergeRefs(ref, inputRef)}
type={visible ? 'text' : 'password'}
/>
</InputGroup>
)
},
)
const VisibilityTrigger = React.forwardRef(
function VisibilityTrigger(props, ref) {
return (
<IconButton
tabIndex={-1}
ref={ref}
me='-2'
aspectRatio='square'
size='sm'
variant='ghost'
height='calc(100% - {spacing.2})'
aria-label='Toggle password visibility'
{...props}
/>
)
},
)
export const PasswordStrengthMeter = React.forwardRef(
function PasswordStrengthMeter(props, ref) {
const { max = 4, value, ...rest } = props
const percent = (value / max) * 100
const { label, colorPalette } = getColorPalette(percent)
return (
<Stack align='flex-end' gap='1' ref={ref} {...rest}>
<HStack width='full' {...rest}>
{Array.from({ length: max }).map((_, index) => (
<Box
key={index}
height='1'
flex='1'
rounded='sm'
data-selected={index < value ? '' : undefined}
layerStyle='fill.subtle'
colorPalette='gray'
_selected={{
colorPalette,
layerStyle: 'fill.solid',
}}
/>
))}
</HStack>
{label && <HStack textStyle='xs'>{label}</HStack>}
</Stack>
)
},
)
function getColorPalette(percent) {
switch (true) {
case percent < 33:
return { label: 'Low', colorPalette: 'red' }
case percent < 66:
return { label: 'Medium', colorPalette: 'orange' }
default:
return { label: 'High', colorPalette: 'green' }
}
}
+3 -1
View File
@@ -2,10 +2,12 @@
import { ChakraProvider, defaultSystem } from '@chakra-ui/react'
import { ColorModeProvider } from './color-mode'
import { system } from '@/theme.js';
export function Provider(props) {
return (
<ChakraProvider value={defaultSystem}>
<ChakraProvider value={system}>
<ColorModeProvider {...props} />
</ChakraProvider>
)
+8 -8
View File
@@ -1,19 +1,19 @@
import { ChakraProvider } from '@chakra-ui/react';
import { ColorModeProvider } from '@/components/ui/color-mode';
import { Provider } from '@/components/ui/provider'
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './css/index.css';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import App from './App.jsx';
import { system } from './theme.js';
createRoot(document.getElementById('root')).render(
<StrictMode>
<ChakraProvider value={system}>
<ColorModeProvider>
<Provider>
<BrowserRouter>
<App />
</ColorModeProvider>
</ChakraProvider>
</StrictMode>,
</BrowserRouter>
</Provider>
</StrictMode>
)
+1
View File
@@ -49,6 +49,7 @@ const sidebarRecipe = defineSlotRecipe({
buttons: {
bg: "transparent",
_hover: {bg: "{primaryLt}", color: "{textInverted}"},
"&.active": {bg: "{primary}", color: "{textInverted}"},
fontSize: "xl",
color: {base: "{text}"},
variant: "ghost",
+4 -2
View File
@@ -1,9 +1,11 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import jsconfigPaths from "vite-jsconfig-paths"
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), jsconfigPaths()],
plugins: [
react(),
jsconfigPaths()
],
})