some routes added
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
},
|
||||||
|
"jsx": "react"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+116
-624
File diff suppressed because it is too large
Load Diff
+1
-3
@@ -13,7 +13,6 @@
|
|||||||
"@chakra-ui/react": "^3.22.0",
|
"@chakra-ui/react": "^3.22.0",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
|
||||||
"framer-motion": "^12.23.0",
|
"framer-motion": "^12.23.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
@@ -22,10 +21,9 @@
|
|||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-grid-layout": "^1.5.2",
|
"react-grid-layout": "^1.5.2",
|
||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.62.0",
|
||||||
"react-icons": "^5.5.0",
|
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"tailwindcss": "^4.1.11",
|
"react-router-dom": "^7.16.0",
|
||||||
"uuid": "^11.1.0"
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+51
-2
@@ -1,10 +1,59 @@
|
|||||||
import Dashboard from './components/Dashboard.jsx'
|
|
||||||
import 'leaflet/dist/leaflet.css';
|
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() {
|
function App() {
|
||||||
|
const sidebarContext = createContext(["Home", "Global Map", "Regional Map", "Volcano", "Admin"]);
|
||||||
|
// const [user, setUser] = useState(null);
|
||||||
|
|
||||||
return (
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 { Stack, Box, Field, Input, Button, Text, Flex } from "@chakra-ui/react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { LuAsterisk } from "react-icons/lu";
|
import { Asterisk } from 'lucide-react';
|
||||||
|
|
||||||
export default function AlertLevel({id, isEditable, isDelete, DeleteWidget}) {
|
export default function AlertLevel({id, isEditable, isDelete, DeleteWidget}) {
|
||||||
const { register, handleSubmit, reset, formState: {errors} } = useForm();
|
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%">
|
<Flex position="relative" dir="column" gap="0.5" width="100%">
|
||||||
<Stack direction="row" width="100%">
|
<Stack direction="row" width="100%">
|
||||||
<Field.Label display="flex" whiteSpace="nowrap" fontWeight="bold">
|
<Field.Label display="flex" whiteSpace="nowrap" fontWeight="bold">
|
||||||
Volcano Name <LuAsterisk color="tomato" />
|
Volcano Name <Asterisk color="tomato" />
|
||||||
</Field.Label>
|
</Field.Label>
|
||||||
<Input
|
<Input
|
||||||
border={errors.volcanoname ? "2px solid" : undefined}
|
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";
|
import React from "react";
|
||||||
|
|
||||||
const CustomResizeHandle = React.forwardRef((props, ref) => {
|
const CustomResizeHandle = React.forwardRef((props, ref) => {
|
||||||
@@ -19,7 +19,7 @@ const CustomResizeHandle = React.forwardRef((props, ref) => {
|
|||||||
zIndex: "1",
|
zIndex: "1",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RiExpandDiagonal2Line />
|
<MoveDiagonal2 />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,36 +1,58 @@
|
|||||||
import { Box, Menu, Portal } from '@chakra-ui/react'
|
import { Box, Menu, Portal } from '@chakra-ui/react'
|
||||||
import WidgetMenu from './WidgetMenu';
|
import WidgetMenu from './WidgetMenu';
|
||||||
|
import AlertLevel from "@/components/Canvas/CanvasComponents/AlertLevelWidget.jsx";
|
||||||
|
|
||||||
export default function FirstWidgetButton({ appendWidget }) {
|
export default function FirstWidgetButton({ appendWidget }) {
|
||||||
const triggerItem = (<>
|
const triggerItem = (
|
||||||
<Menu.Trigger asChild>
|
<>
|
||||||
<Box
|
<Menu.Trigger asChild>
|
||||||
p={6}
|
<Box
|
||||||
m="auto"
|
p={6}
|
||||||
mt={6}
|
m="auto"
|
||||||
align="center"
|
mt={6}
|
||||||
textAlign="center"
|
align="center"
|
||||||
justify="center"
|
textAlign="center"
|
||||||
width="400px"
|
justify="center"
|
||||||
borderRadius="md"
|
width="400px"
|
||||||
boxShadow="md"
|
borderRadius="md"
|
||||||
border="2px dashed"
|
boxShadow="md"
|
||||||
borderColor="gray.300"
|
border="2px dashed"
|
||||||
bg="gray.50"
|
borderColor="gray.300"
|
||||||
cursor="pointer"
|
bg="gray.50"
|
||||||
_hover = {{
|
cursor="pointer"
|
||||||
bg: "gray.100",
|
_hover = {{
|
||||||
borderColor: "gray.400",
|
bg: "gray.100",
|
||||||
boxShadow: "lg",
|
borderColor: "gray.400",
|
||||||
}}
|
boxShadow: "lg",
|
||||||
>
|
}}
|
||||||
Add Your First Widget
|
>
|
||||||
</Box>
|
Add Your First Widget
|
||||||
</Menu.Trigger>
|
</Box>
|
||||||
</>);
|
</Menu.Trigger>
|
||||||
|
</>
|
||||||
|
);
|
||||||
const positioning = { placement: "bottom-middle" };
|
const positioning = { placement: "bottom-middle" };
|
||||||
const WidgetMenuProps = { appendWidget, triggerItem, positioning };
|
// const WidgetMenuProps = { appendWidget, triggerItem, positioning };
|
||||||
return (
|
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 { Box, Checkbox, Separator , CloseButton, Text, chakra, Field, IconButton, Icon, Input, Fieldset, Flex } from "@chakra-ui/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { FiSettings } from "react-icons/fi";
|
import { ChevronDown } from 'lucide-react';
|
||||||
import { IoIosArrowDown } from "react-icons/io";
|
import { ChevronUp } from 'lucide-react';
|
||||||
import { IoIosArrowUp } from "react-icons/io";
|
|
||||||
|
|
||||||
export default function MapSettingsButton() {
|
export default function MapSettingsButton() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -16,7 +15,7 @@ export default function MapSettingsButton() {
|
|||||||
</Box>
|
</Box>
|
||||||
):(
|
):(
|
||||||
<IconButton boxShadow="0 0 0 2px rgba(119, 119, 119, 0.4)" size="sm" variant="subtle" onClick={() => setOpen(true)}>
|
<IconButton boxShadow="0 0 0 2px rgba(119, 119, 119, 0.4)" size="sm" variant="subtle" onClick={() => setOpen(true)}>
|
||||||
<FiSettings />
|
<Settings />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -57,7 +56,7 @@ function Lightning() {
|
|||||||
<Checkbox.Label >Lightning</Checkbox.Label>
|
<Checkbox.Label >Lightning</Checkbox.Label>
|
||||||
</Checkbox.Root>
|
</Checkbox.Root>
|
||||||
<Icon pt="1" size="md" variant="none" onClick={() => setOpen(!open)}>
|
<Icon pt="1" size="md" variant="none" onClick={() => setOpen(!open)}>
|
||||||
{open ? (<IoIosArrowUp />):(<IoIosArrowDown />)}
|
{open ? (<ChevronUp />):(<ChevronDown />)}
|
||||||
</Icon>
|
</Icon>
|
||||||
{open && (<LightningOptions />)}
|
{open && (<LightningOptions />)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Input, InputGroup } from "@chakra-ui/react";
|
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" }) {
|
export function SearchBar({ placeholder="Enter a volcano to search", size="sm", width="210px" }) {
|
||||||
return (
|
return (
|
||||||
<InputGroup startElement={<FiSearch />}>
|
<InputGroup startElement={<Search />}>
|
||||||
<Input
|
<Input
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
size={size}
|
size={size}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { IconButton, Menu, Portal } from "@chakra-ui/react";
|
import { IconButton, Menu, Portal } from "@chakra-ui/react";
|
||||||
import { IoIosArrowBack } from "react-icons/io";
|
import { ArrowLeft } from 'lucide-react';
|
||||||
import { FiSettings } from "react-icons/fi";
|
import { Settings } from 'lucide-react';
|
||||||
import WidgetMenu from "./WidgetMenu";
|
import WidgetMenu from "./WidgetMenu";
|
||||||
|
|
||||||
export function SettingsButton({ onEditWidgets, appendWidget}) {
|
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 }
|
const WidgetMenuProps = { appendWidget, triggerItem }
|
||||||
return (
|
return (
|
||||||
<Menu.Root>
|
<Menu.Root>
|
||||||
<Menu.Trigger asChild >
|
<Menu.Trigger asChild >
|
||||||
<IconButton variant="ghost" size="md" borderRadius="xl">
|
<IconButton variant="ghost" size="md" borderRadius="xl">
|
||||||
<FiSettings />
|
<Settings />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Menu.Trigger>
|
</Menu.Trigger>
|
||||||
<Portal>
|
<Portal>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { useState, useRef } from "react";
|
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}) {
|
export default function Tooltip ({tooltipkey, content, sidebarIsOpen, children}) {
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const recipe = useSlotRecipe({ key: "sidebar" });
|
|
||||||
const styles = recipe();
|
|
||||||
const timeoutId = useRef(null);
|
const timeoutId = useRef(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,22 +28,20 @@ export default function Tooltip ({tooltipkey, content, sidebarIsOpen, children})
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{isVisible && !sidebarIsOpen && (
|
{isVisible && !sidebarIsOpen && (
|
||||||
<>
|
<chakra.div
|
||||||
<chakra.div
|
fontSize="xs"
|
||||||
fontSize="xs"
|
position="absolute"
|
||||||
position="absolute"
|
bg="gray.700"
|
||||||
bg="gray.700"
|
color="white"
|
||||||
color="white"
|
left="65px"
|
||||||
left="65px"
|
px={2}
|
||||||
px={2}
|
py={1}
|
||||||
py={1}
|
borderRadius="md"
|
||||||
borderRadius="md"
|
whiteSpace="nowrap"
|
||||||
whiteSpace="nowrap"
|
zIndex="overlay" // zIndex = 1300
|
||||||
zIndex="overlay" // zIndex = 1300
|
>
|
||||||
>
|
{content}
|
||||||
{content}
|
</chakra.div>
|
||||||
</chakra.div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</chakra.div>
|
</chakra.div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,37 +1,30 @@
|
|||||||
import { useSlotRecipe, chakra, Flex, CloseButton, Text, IconButton } from "@chakra-ui/react"
|
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) {
|
export default function Widget(props) {
|
||||||
// deconstructing props
|
// deconstructing props
|
||||||
const { id, title, isEditable, isDelete, DeleteWidget, isSubmit, setIsSubmit, children } = props;
|
const { id, title, isEditable, isDelete, DeleteWidget, isSubmit, setIsSubmit, children } = props;
|
||||||
|
|
||||||
const recipe = useSlotRecipe({ key: "widget" });
|
const recipe = useSlotRecipe({ key: "widget" });
|
||||||
const styles = recipe();
|
const styles = recipe();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<chakra.div css={styles.header} cursor={isEditable ? "move":"default"}>
|
<chakra.div css={styles.header} cursor={isEditable ? "move":"default"}>
|
||||||
{isDelete ? (
|
<Flex flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||||
<>
|
<Text className="widget-handle" w="100%" display="flex" alignItems="center" height="32px" fontWeight="bold">{title}</Text>
|
||||||
<Flex flexDirection="row" justifyContent="space-between" alignItems="center">
|
{isDelete ? (
|
||||||
<Text className="widget-handle" w="100%" fontWeight="bold">{title}</Text>
|
<CloseButton onClick={() => DeleteWidget(id)} size="xs" _hover={{bg: "tomato", color: "white"}}/>
|
||||||
<CloseButton onClick={() => DeleteWidget(id)} size="xs" _hover={{bg: "tomato", color: "white"}}/>
|
):(
|
||||||
</Flex>
|
isSubmit ? (
|
||||||
</>
|
<IconButton onClick={() => setIsSubmit(false)} size="xs" bg="base300" cursor="pointer"><Undo fill="text" /></IconButton>
|
||||||
):(
|
):null
|
||||||
<>
|
)}
|
||||||
<Flex flexDirection="row" justifyContent="space-between" alignItems="center">
|
</Flex>
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</chakra.div>
|
</chakra.div>
|
||||||
<chakra.div height="100%" mb="2">
|
<Flex height="100%" mb="2">
|
||||||
{children}
|
{children}
|
||||||
</chakra.div>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { HStack, Flex, Tabs } from "@chakra-ui/react";
|
import { HStack, Flex, Tabs } from "@chakra-ui/react";
|
||||||
|
import { useState } from "react";
|
||||||
import MyDashboard from "./HomeTabs/MyDashboard";
|
import MyDashboard from "./HomeTabs/MyDashboard";
|
||||||
import { SearchBar } from "../CanvasComponents/SearchBar";
|
import { SearchBar } from "../CanvasComponents/SearchBar";
|
||||||
import { SettingsButton } from "../CanvasComponents/SettingsButton";
|
import { SettingsButton } from "../CanvasComponents/SettingsButton";
|
||||||
@@ -13,14 +14,17 @@ import { v4 as uuid } from 'uuid';
|
|||||||
import { CanvasHeaderTabs } from '../CanvasComponents/CanvasHeaderTabs';
|
import { CanvasHeaderTabs } from '../CanvasComponents/CanvasHeaderTabs';
|
||||||
import { DASHBOARD_TABS } from "@/constants/viewKeys";
|
import { DASHBOARD_TABS } from "@/constants/viewKeys";
|
||||||
|
|
||||||
export default function Home(props) {
|
export default function Home() {
|
||||||
// deconstructing props
|
const [isEditable, setIsEditable] = useState(false); // editable=true means static=false (vice versa)
|
||||||
const { view, layout, setLayout, setOriginalLayout, onLayoutChange, onChangeView, onCancel, setOriginalWidgets,
|
const [isDelete, setIsDelete] = useState(false);
|
||||||
isDelete, setIsDelete, beginEditIfNeeded, isEditable, setIsEditable, widgetArray, setWidgetArray } = props;
|
const [originalLayout, setOriginalLayout] = useState([]);
|
||||||
|
const [originalWidgets, setOriginalWidgets] = useState([]);
|
||||||
|
const [layout, setLayout] = useState([]);
|
||||||
|
const [widgetArray, setWidgetArray] = useState([]);
|
||||||
|
|
||||||
|
function appendWidget(name, w, h, min_w, minh, max_w, max_h) {
|
||||||
|
saveLayoutState(); // Save state before anything else
|
||||||
|
|
||||||
function appendWidget(name, w, h, minw, minh, maxw, maxh) {
|
|
||||||
beginEditIfNeeded(); // Save state before anything else
|
|
||||||
|
|
||||||
const newID = uuid(); // could be replaced with something else like guid
|
const newID = uuid(); // could be replaced with something else like guid
|
||||||
const COLS = 16;
|
const COLS = 16;
|
||||||
const W = w ?? 3;
|
const W = w ?? 3;
|
||||||
@@ -30,7 +34,7 @@ export default function Home(props) {
|
|||||||
...widgetArray,
|
...widgetArray,
|
||||||
{i: newID, widget: name }
|
{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 => [
|
setLayout(prev => [
|
||||||
...prev,
|
...prev,
|
||||||
@@ -40,10 +44,10 @@ export default function Home(props) {
|
|||||||
y: 0,
|
y: 0,
|
||||||
w: w,
|
w: w,
|
||||||
h: h,
|
h: h,
|
||||||
minW: minw,
|
minW: min_w,
|
||||||
minH: minh,
|
minH: minh,
|
||||||
maxW: maxw,
|
maxW: max_w,
|
||||||
maxH: maxh,
|
maxH: max_h,
|
||||||
static: false,
|
static: false,
|
||||||
resizeHandles: ['se']
|
resizeHandles: ['se']
|
||||||
},
|
},
|
||||||
@@ -67,7 +71,7 @@ export default function Home(props) {
|
|||||||
|
|
||||||
const onEditWidgets = () => {
|
const onEditWidgets = () => {
|
||||||
console.log("Start deleting widgets...");
|
console.log("Start deleting widgets...");
|
||||||
beginEditIfNeeded(); // Save state before anything else
|
saveLayoutState(); // Save state before anything else
|
||||||
setLayout(prevLayout =>
|
setLayout(prevLayout =>
|
||||||
prevLayout.map(item => ({
|
prevLayout.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -96,39 +100,52 @@ export default function Home(props) {
|
|||||||
else return;
|
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
|
// 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 };
|
const settingsButtonProps = { onEditWidgets, appendWidget };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
/* HEADER */
|
/* SUB HEADER */
|
||||||
<Tabs.Root
|
<Tabs.Root width="100%" height="100%" lazyMount variant="line">
|
||||||
width="100%"
|
<Flex as="header" position="sticky" top="0" zIndex="dropdown" bg="base200" p={5} align="center" justify="space-between" width="100%">
|
||||||
height="100%"
|
{/* TABS */}
|
||||||
lazyMount
|
{/* <CanvasHeaderTabs tabs={DASHBOARD_TABS} />*/} {/* TODO: rename to SubHeaderTabs */}
|
||||||
value={ view }
|
<Tabs.List>
|
||||||
variant="line"
|
{DASHBOARD_TABS.map(({ value, label }) =>
|
||||||
// Prevent navigation away from 'My Dashboard' while in Edit mode
|
<Tabs.Trigger key={value} value={value} fontSize="lg">
|
||||||
onValueChange={(e) => {
|
{ label }
|
||||||
const nextView = e.value;
|
</Tabs.Trigger>
|
||||||
onChangeView(nextView);
|
)}
|
||||||
}}
|
</Tabs.List>
|
||||||
>
|
|
||||||
<Flex
|
{/* EDIT MODE BUTTONS */}
|
||||||
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} />
|
|
||||||
<HStack width="258px" justifyContent="flex-end">
|
<HStack width="258px" justifyContent="flex-end">
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import CustomResizeHandle from '../../CanvasComponents/CustomResizeHandle';
|
|||||||
import FirstWidgetButton from '../../CanvasComponents/FirstWidgetButton';
|
import FirstWidgetButton from '../../CanvasComponents/FirstWidgetButton';
|
||||||
|
|
||||||
export default function MyDashboard({layout, onLayoutChange, isEditable, isDelete, DeleteWidget, widgetArray, appendWidget}) {
|
export default function MyDashboard({layout, onLayoutChange, isEditable, isDelete, DeleteWidget, widgetArray, appendWidget}) {
|
||||||
console.log("rendering MyDashboard");
|
|
||||||
const recipe = useSlotRecipe({ key: "widget" });
|
const recipe = useSlotRecipe({ key: "widget" });
|
||||||
const styles = recipe(); // using root recipe for widget below
|
const styles = recipe(); // using root recipe for widget below
|
||||||
const ResponsiveGridLayout = WidthProvider(GridLayout); // canvas width now adjusts to window size
|
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
|
const handleDragStop = () => { document.body.style.userSelect = ""; }; // allow text highlighting all other times
|
||||||
let merged = [];
|
let merged = [];
|
||||||
|
|
||||||
console.log("widgetArray: ", widgetArray);
|
|
||||||
console.log("layout: ", layout);
|
|
||||||
|
|
||||||
if (widgetArray.length && layout.length) {
|
if (widgetArray.length && layout.length) {
|
||||||
const widgetMap = new Map(widgetArray.map(item => [item.i, item]));
|
const widgetMap = new Map(widgetArray.map(item => [item.i, item]));
|
||||||
merged = layout.map(item => {
|
merged = layout.map(item => {
|
||||||
@@ -23,7 +19,7 @@ export default function MyDashboard({layout, onLayoutChange, isEditable, isDelet
|
|||||||
...widgetMap.get(item.i)
|
...widgetMap.get(item.i)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
// Show large 'Add Widget' button when dashboard is empty
|
// Show large 'Add Widget' button when dashboard is empty
|
||||||
if ( !layout.length ) {
|
if ( !layout.length ) {
|
||||||
@@ -36,39 +32,43 @@ export default function MyDashboard({layout, onLayoutChange, isEditable, isDelet
|
|||||||
bg="gray.150"
|
bg="gray.150"
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
>
|
>
|
||||||
<FirstWidgetButton appendWidget={ appendWidget } />
|
<FirstWidgetButton appendWidget={ appendWidget } />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show responsive grid of widgets
|
// Show responsive grid of widgets
|
||||||
return (
|
return (
|
||||||
<chakra.div width="100%">
|
<ResponsiveGridLayout
|
||||||
<ResponsiveGridLayout
|
useCSSTransforms={false} // remove the sliding animation of the widgets
|
||||||
useCSSTransforms={false} // remove the sliding animation of the widgets
|
className="layout"
|
||||||
className="layout"
|
layout={layout}
|
||||||
layout={layout}
|
cols={16} // Total columns in grid
|
||||||
cols={16} // Total columns in grid
|
rowHeight={30} // Height of a single row in px
|
||||||
rowHeight={30} // Height of a single row in px
|
isBounded={true} // Total width of the grid
|
||||||
isBounded={true} // Total width of the grid
|
margin={[15, 5]} // [horizontal, vertical] gap
|
||||||
margin={[15, 5]} // [horizontal, vertical] gap
|
onLayoutChange={onLayoutChange}
|
||||||
onLayoutChange={onLayoutChange}
|
draggableHandle=".widget-handle" // Make only the header draggable
|
||||||
draggableHandle=".widget-handle" // Make only the header draggable
|
onDragStart={handleDragStart}
|
||||||
onDragStart={handleDragStart}
|
onDragStop={handleDragStop}
|
||||||
onDragStop={handleDragStop}
|
position="relative"
|
||||||
position="relative"
|
resizeHandle={<CustomResizeHandle isVisable={isEditable} />}
|
||||||
resizeHandle={<CustomResizeHandle isVisable={isEditable} />}
|
>
|
||||||
>
|
{ merged.map((item) => {
|
||||||
{ merged.map((item) => {
|
const Widget = item.widget;
|
||||||
const Widget = item.widget;
|
return (
|
||||||
return (
|
<Flex key={item.i} css={styles.root} boxShadow="md" height="100%" >
|
||||||
<Flex key={item.i} css={styles.root} boxShadow="md" height="100%" >
|
<Widget
|
||||||
<Widget id={item.i} isEditable={isEditable} isDelete={isDelete} DeleteWidget={DeleteWidget} appendWidget={appendWidget}/>
|
id={item.i}
|
||||||
</Flex>
|
isEditable={isEditable}
|
||||||
);
|
isDelete={isDelete}
|
||||||
})
|
DeleteWidget={DeleteWidget}
|
||||||
}
|
appendWidget={appendWidget}
|
||||||
</ResponsiveGridLayout>
|
/>
|
||||||
</chakra.div>
|
</Flex>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ResponsiveGridLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 UserAvatarMenu from "./UserAvatarMenu.jsx";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
|
||||||
//TODO: Dynamically populate per each user
|
//TODO: Dynamically populate per each user
|
||||||
const user = {
|
const user = {
|
||||||
@@ -8,13 +10,16 @@ const user = {
|
|||||||
// avatar: "src\\assets\\user_profile.svg"
|
// avatar: "src\\assets\\user_profile.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Header({ onChangeView }) {
|
export default function Header() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const recipe = useRecipe({ key: "header" });
|
const recipe = useRecipe({ key: "header" });
|
||||||
const styles = recipe();
|
const styles = recipe();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<chakra.div css={styles}>
|
<Flex css={styles}>
|
||||||
<Button onClick={() => onChangeView("mydashboard")} color="color" variant="plain" textStyle="5xl" fontWeight="bold">VDAP</Button>
|
<Button as={NavLink} to={"/home"} color="color" variant="plain" textStyle="5xl" fontWeight="bold">
|
||||||
|
VDAP
|
||||||
|
</Button>
|
||||||
<Flex align="center" gap={3} pr={6}>
|
<Flex align="center" gap={3} pr={6}>
|
||||||
<Stack gap="0" textAlign="center" cursor="default">
|
<Stack gap="0" textAlign="center" cursor="default">
|
||||||
<Text color="color" fontWeight="bold" textStyle="lg">{user.name}</Text>
|
<Text color="color" fontWeight="bold" textStyle="lg">{user.name}</Text>
|
||||||
@@ -23,10 +28,9 @@ export default function Header({ onChangeView }) {
|
|||||||
<UserAvatarMenu
|
<UserAvatarMenu
|
||||||
name={user.name}
|
name={user.name}
|
||||||
avatar={user.avatar}
|
avatar={user.avatar}
|
||||||
onChangeView={ onChangeView }
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</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 (
|
return (
|
||||||
<Menu.Root>
|
<Menu.Root>
|
||||||
<Menu.Trigger focusRing="none">
|
<Menu.Trigger focusRing="none">
|
||||||
@@ -21,7 +22,7 @@ export default function UserAvatarMenu({ onChangeView, name, avatar }) {
|
|||||||
{/* zIndex: overlay = 1300 */}
|
{/* zIndex: overlay = 1300 */}
|
||||||
<Menu.Positioner zIndex="overlay">
|
<Menu.Positioner zIndex="overlay">
|
||||||
<Menu.Content>
|
<Menu.Content>
|
||||||
<Menu.Item value="settings" onClick={() => onChangeView("personal")}>
|
<Menu.Item value="settings" as={NavLink} to={"/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,89 +1,61 @@
|
|||||||
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 { useState } from "react";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
import Tooltip from "../Canvas/CanvasComponents/Tooltip.jsx";
|
import Tooltip from "../Canvas/CanvasComponents/Tooltip.jsx";
|
||||||
import { FaVolcano } from "react-icons/fa6";
|
import { Mountain, House, Earth, MapPin, ShieldUser, Menu, CircleUserRound, KeyRound } from 'lucide-react';
|
||||||
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';
|
|
||||||
|
|
||||||
export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
|
export default function Sidebar() {
|
||||||
|
const { toggleColorMode } = useColorMode();
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
const [activeButton, setActiveButton] = useState(0)
|
const [activeButton, setActiveButton] = useState(0)
|
||||||
const recipe = useSlotRecipe({ key: "sidebar" });
|
const recipe = useSlotRecipe({ key: "sidebar" });
|
||||||
const styles = recipe();
|
const styles = recipe();
|
||||||
const { toggleColorMode } = useColorMode();
|
|
||||||
|
|
||||||
const buttons = useMemo(() => {
|
// const buttons = useMemo(() => {
|
||||||
if (["settings", "personal", "security"].includes(view)) {
|
// if (["settings", "personal", "security"].includes(view)) {
|
||||||
return [
|
// return [
|
||||||
{icon: IoPersonCircleSharp, name: "Personal Info", view: "personal"},
|
// {icon: CircleUserRound, name: "Personal Info", url: "/personal"},
|
||||||
{icon: KeyRound, name: "Security", view: "security"},
|
// {icon: KeyRound, name: "Security", url: "/security"},
|
||||||
];
|
// ];
|
||||||
}
|
// }
|
||||||
return [
|
// return [
|
||||||
{icon: FaHome, name: "Home", view: "mydashboard"},
|
// {icon: House, name: "Home", url: "/mydashboard"},
|
||||||
{icon: FaGlobeAmericas, name: "Global Map", view: "global"},
|
// {icon: Earth, name: "Global Map", url: "/global"},
|
||||||
{icon: FaMapMarkerAlt, name: "Regional Map", view: "regional"},
|
// {icon: MapPin, name: "Regional Map", url: "/regional"},
|
||||||
{icon: FaVolcano, name: "Volcano", view: "volcanohome"},
|
// {icon: Mountain, name: "Volcano", url: "/volcanohome"},
|
||||||
{icon: HiUsers, name: "Admin", view: "medataentry"}
|
// {icon: ShieldUser, name: "Admin", url: "/medataentry"}
|
||||||
];
|
// ];
|
||||||
}, [view]);
|
// }, [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"}
|
||||||
|
];
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
if (isOpen) setIsOpen(false);
|
if (isOpen) setIsOpen(false);
|
||||||
else setIsOpen(true);
|
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 (
|
return (
|
||||||
<Box
|
<Box css={styles.root} width={isOpen ? "210px" : "71px"} transition="width 0.4s ease" position="relative">
|
||||||
css={styles.root}
|
<IconButton aria-label="Toggle Sidebar" variant="plain" size="md" onClick={toggleSidebar} width="54px">
|
||||||
width={isOpen ? "210px" : "71px"}
|
<Menu />
|
||||||
transition="width 0.4s ease"
|
|
||||||
position="relative"
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
aria-label="Toggle Sidebar"
|
|
||||||
variant="plain"
|
|
||||||
size="md"
|
|
||||||
onClick={toggleSidebar}
|
|
||||||
width="54px"
|
|
||||||
>
|
|
||||||
<VscThreeBars />
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
{buttons.map((button, idx) => {
|
{buttons.map((button, idx) => {
|
||||||
const Icon = button.icon;
|
const Icon = button.icon;
|
||||||
return (
|
return (
|
||||||
<Tooltip key={button.name} tooltipkeykey={idx} content={button.name} sidebarIsOpen={isOpen}>
|
<Tooltip key={button.name} tooltipkey={idx} content={button.name} sidebarIsOpen={isOpen}>
|
||||||
<Button
|
<Button
|
||||||
|
as={NavLink}
|
||||||
|
to={button.url}
|
||||||
key={button.name}
|
key={button.name}
|
||||||
css={styles.buttons}
|
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"
|
justifyContent="flex-start"
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
@@ -105,7 +77,6 @@ export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
|
|||||||
})}
|
})}
|
||||||
<ColorModeButton
|
<ColorModeButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDarkMode(!darkMode);
|
|
||||||
toggleColorMode();
|
toggleColorMode();
|
||||||
}}
|
}}
|
||||||
position="absolute"
|
position="absolute"
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { ClientOnly, IconButton, Skeleton, Span } from '@chakra-ui/react'
|
|||||||
import { ThemeProvider, useTheme } from 'next-themes'
|
import { ThemeProvider, useTheme } from 'next-themes'
|
||||||
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { LuMoon, LuSun } from 'react-icons/lu'
|
import { Moon, Sun } from 'lucide-react';
|
||||||
|
|
||||||
|
|
||||||
export function ColorModeProvider(props) {
|
export function ColorModeProvider(props) {
|
||||||
return (
|
return (
|
||||||
@@ -32,7 +33,7 @@ export function useColorModeValue(light, dark) {
|
|||||||
|
|
||||||
export function ColorModeIcon() {
|
export function ColorModeIcon() {
|
||||||
const { colorMode } = useColorMode()
|
const { colorMode } = useColorMode()
|
||||||
return colorMode === 'dark' ? <LuMoon /> : <LuSun />
|
return colorMode === 'dark' ? <Moon /> : <Sun />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ColorModeButton = React.forwardRef(
|
export const ColorModeButton = React.forwardRef(
|
||||||
|
|||||||
@@ -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' }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
import { ChakraProvider, defaultSystem } from '@chakra-ui/react'
|
import { ChakraProvider, defaultSystem } from '@chakra-ui/react'
|
||||||
import { ColorModeProvider } from './color-mode'
|
import { ColorModeProvider } from './color-mode'
|
||||||
|
import { system } from '@/theme.js';
|
||||||
|
|
||||||
|
|
||||||
export function Provider(props) {
|
export function Provider(props) {
|
||||||
return (
|
return (
|
||||||
<ChakraProvider value={defaultSystem}>
|
<ChakraProvider value={system}>
|
||||||
<ColorModeProvider {...props} />
|
<ColorModeProvider {...props} />
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-8
@@ -1,19 +1,19 @@
|
|||||||
import { ChakraProvider } from '@chakra-ui/react';
|
import { Provider } from '@/components/ui/provider'
|
||||||
import { ColorModeProvider } from '@/components/ui/color-mode';
|
|
||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import './css/index.css';
|
import './css/index.css';
|
||||||
import 'react-grid-layout/css/styles.css';
|
import 'react-grid-layout/css/styles.css';
|
||||||
import 'react-resizable/css/styles.css';
|
import 'react-resizable/css/styles.css';
|
||||||
import App from './App.jsx';
|
import App from './App.jsx';
|
||||||
import { system } from './theme.js';
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ChakraProvider value={system}>
|
<Provider>
|
||||||
<ColorModeProvider>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</ColorModeProvider>
|
</BrowserRouter>
|
||||||
</ChakraProvider>
|
</Provider>
|
||||||
</StrictMode>,
|
</StrictMode>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const sidebarRecipe = defineSlotRecipe({
|
|||||||
buttons: {
|
buttons: {
|
||||||
bg: "transparent",
|
bg: "transparent",
|
||||||
_hover: {bg: "{primaryLt}", color: "{textInverted}"},
|
_hover: {bg: "{primaryLt}", color: "{textInverted}"},
|
||||||
|
"&.active": {bg: "{primary}", color: "{textInverted}"},
|
||||||
fontSize: "xl",
|
fontSize: "xl",
|
||||||
color: {base: "{text}"},
|
color: {base: "{text}"},
|
||||||
variant: "ghost",
|
variant: "ghost",
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from '@vitejs/plugin-react-swc'
|
||||||
import jsconfigPaths from "vite-jsconfig-paths"
|
import jsconfigPaths from "vite-jsconfig-paths"
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), jsconfigPaths()],
|
plugins: [
|
||||||
|
react(),
|
||||||
|
jsconfigPaths()
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user