refactor, further cleaning up sidebar, home, mydashboard, alertlevelwidget, widget, and more misc.

This commit is contained in:
2026-06-05 11:27:43 -07:00
parent 0715fa61ab
commit a0708fa8ea
12 changed files with 214 additions and 224 deletions
+38 -36
View File
@@ -1,37 +1,39 @@
import GlobalMap from "./views/GlobalMap";
import RegionalMap from "./views/RegionalMap";
import Volcano from "./views/Volcano";
import Admin from "./views/Admin";
import Account from "./views/Account"
import Home from "./views/Home";
import { DASHBOARD_VIEWS, ADMIN_VIEWS, VOLCANO_VIEWS } from "@/constants/viewKeys";
import { chakra } from "@chakra-ui/react";
import Personal from "./views/Personal";
import Security from "./views/Security";
// DEPRECATED
export default function Canvas(props) {
const { view } = props;
return (
<chakra.div width="100%" height="100%">
{/* Check if view is a tab inside the Home Header */}
{ DASHBOARD_VIEWS.includes(view) && (
<Home {...props}/>
)}
{/* Check if view is a tab inside the Volcano Header */}
{ VOLCANO_VIEWS.includes(view) && (
<Volcano {...props}/>
)}
{/* Check if view is a tab inside the Admin Header */}
{ ADMIN_VIEWS.includes(view) && (
<Admin {...props}/>
)}
{view === "global" && <GlobalMap />}
{view === "regional" && <RegionalMap />}
{view === "account" && <Account />}
{view === "settings" && <Personal />}
{view === "personal" && <Personal />}
{view === "security" && <Security />}
</chakra.div>
);
}
// import GlobalMap from "./views/GlobalMap";
// import RegionalMap from "./views/RegionalMap";
// import Volcano from "./views/Volcano";
// import Admin from "./views/Admin";
// import Account from "./views/Account"
// import Home from "./views/Home";
// import { DASHBOARD_VIEWS, ADMIN_VIEWS, VOLCANO_VIEWS } from "@/constants/viewKeys";
// import { chakra } from "@chakra-ui/react";
// import Personal from "./views/Personal";
// import Security from "./views/Security";
//
// export default function Canvas(props) {
// const { view } = props;
//
// return (
// <chakra.div width="100%" height="100%">
// {/* Check if view is a tab inside the Home Header */}
// { DASHBOARD_VIEWS.includes(view) && (
// <Home {...props}/>
// )}
// {/* Check if view is a tab inside the Volcano Header */}
// { VOLCANO_VIEWS.includes(view) && (
// <Volcano {...props}/>
// )}
// {/* Check if view is a tab inside the Admin Header */}
// { ADMIN_VIEWS.includes(view) && (
// <Admin {...props}/>
// )}
// {view === "global" && <GlobalMap />}
// {view === "regional" && <RegionalMap />}
// {view === "account" && <Account />}
// {view === "settings" && <Personal />}
// {view === "personal" && <Personal />}
// {view === "security" && <Security />}
// </chakra.div>
// );
// }
@@ -1,62 +1,60 @@
import Widget from "./Widget";
import { Stack, Box, Field, Input, Button, Text, Flex } from "@chakra-ui/react";
import { Fieldset, NativeSelect, For, Box, Field, Input, Button, Text, Flex } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { useState } from "react";
import { Asterisk } from 'lucide-react';
export default function AlertLevel({id, isEditable, isDelete, DeleteWidget}) {
const { register, handleSubmit, reset, formState: {errors} } = useForm();
const [isSubmit, setIsSubmit] = useState(false);
const onSubmit = (data) => {
console.log("data: ", data);
if (data.volcanoname === '') {
return null;
}
setIsSubmit(true);
reset();
}
const [isSubmit, setIsSubmit] = useState(false);
// Prop Forwarding
const widgetProps = { id, isEditable, isDelete, DeleteWidget, isSubmit, setIsSubmit }
return (
<Widget title="Alert Level" {...widgetProps}>
<Widget title="Alert Level" {...widgetProps} >
{!isSubmit ? (
// DISPLAYING INPUT FIElDS AND LABELS
<Box
as="form"
onSubmit={handleSubmit(onSubmit)}
width="100%"
height="100%"
pt={1}
pl={3}
pr={3}
p={3}
display="flex"
flexDir="column"
gap="10"
>
<Field.Root orientation="horizontal">
<Stack direction="column" gap="3" width="100%">
<Stack direction="row" width="100%">
<Field.Label whiteSpace="nowrap" fontWeight="bold">Country Name</Field.Label>
{/* place select drop down menu here instead of asking for input */}
<Input {...register("countryname")} placeholder="Enter country name"/>
</Stack>
<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 <Asterisk color="tomato" />
</Field.Label>
<Input
border={errors.volcanoname ? "2px solid" : undefined}
borderColor={errors.volcanoname ? "tomato" : undefined}
{...register("volcanoname", { required: 'This field is required.' })}
placeholder="Enter volcano name"/>
</Stack>
{errors.volcanoname && <Box cursor="default" userSelect="none" position="absolute" top="40px" left="8.5rem" style={{ color: 'tomato' }}>{errors.volcanoname.message}</Box>}
</Flex>
</Stack>
</Field.Root>
<Button type="submit" size="sm">Submit</Button>
<Fieldset.Root size="md" maxW="md">
<Fieldset.Content>
<Field.Root>
<Field.Label fontWeight="semibold">Volcano</Field.Label>
<Input name="volcanoname" />
</Field.Root>
<Field.Root>
<Field.Label fontWeight="semibold">Country</Field.Label>
<NativeSelect.Root>
<NativeSelect.Field name="country">
<For each={["United Kingdom", "Canada", "United States"]}>
{(item) => (
<option key={item} value={item}>
{item}
</option>
)}
</For>
</NativeSelect.Field>
<NativeSelect.Indicator />
</NativeSelect.Root>
</Field.Root>
</Fieldset.Content>
<Button type="submit" size="sm">Submit</Button>
</Fieldset.Root>
</Box>
):(
// DISPLAYING ALERT LEVEL DATA
@@ -1,7 +1,6 @@
import { Box, Checkbox, Separator , CloseButton, Text, chakra, Field, IconButton, Icon, Input, Fieldset, Flex } from "@chakra-ui/react";
import { useState } from "react";
import { ChevronDown } from 'lucide-react';
import { ChevronUp } from 'lucide-react';
import { ChevronDown, ChevronUp, Settings } from 'lucide-react';
export default function MapSettingsButton() {
const [open, setOpen] = useState(false);
@@ -1,7 +1,7 @@
import { Input, InputGroup } from "@chakra-ui/react";
import { Search } from 'lucide-react';
export function SearchBar({ placeholder="Enter a volcano to search", size="sm", width="210px" }) {
export function SearchBar({ placeholder, size, width, ...rest }) {
return (
<InputGroup startElement={<Search />}>
<Input
@@ -9,23 +9,24 @@ export default function Widget(props) {
const styles = recipe();
return (
<>
<chakra.div css={styles.header} cursor={isEditable ? "move":"default"}>
<Flex flexDirection="row" justifyContent="space-between" alignItems="center">
<Text className="widget-handle" w="100%" display="flex" alignItems="center" height="32px" fontWeight="bold">{title}</Text>
{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>
<Flex height="100%" mb="2">
<Flex flexDirection="column" w="100%" h="auto">
{/* HEADER */}
<Flex css={styles.header} cursor={isEditable ? "move":"default"} flexDirection="row" w="100%" h="auto" justifyContent="space-between" alignItems="center">
<Text className="widget-handle" w="100%" display="flex" alignItems="center" height="32px" fontWeight="bold">{title}</Text>
{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>
{/* CHILDREN */}
<Flex w="100%" h="auto">
{children}
</Flex>
</>
</Flex>
);
}
+4 -4
View File
@@ -1,4 +1,4 @@
import { HStack, Flex, Tabs } from "@chakra-ui/react";
import { HStack, Flex, Tabs, Button } from "@chakra-ui/react";
import { useState } from "react";
import MyDashboard from "./HomeTabs/MyDashboard";
import { SearchBar } from "../CanvasComponents/SearchBar";
@@ -149,8 +149,8 @@ export default function Home() {
<HStack width="258px" justifyContent="flex-end">
{isEditable ? (
<>
<SaveButton onSave={ onSave } />
<CancelButton onCancel={ onCancel } />
<Button onClick={onSave} borderRadius="md" bg="primary" _hover={{bg: "primaryLt"}}>Save</Button>
<Button onClick={onCancel} borderRadius="md">Cancel</Button>
{isDelete ? (
<DeleteAllButton onDeleteAll={ DeleteAll } />
):(null)
@@ -158,7 +158,7 @@ export default function Home() {
</>
):(
<>
<SearchBar />
<SearchBar placeholder="Enter a volcano to search" size="sm" width="210px" />
<SettingsButton {...settingsButtonProps}/>
</>
)}
@@ -1,5 +1,5 @@
import GridLayout, { WidthProvider } from 'react-grid-layout';
import { useSlotRecipe, chakra, Flex } from '@chakra-ui/react';
import { useSlotRecipe, chakra, Flex, SimpleGrid } from '@chakra-ui/react';
import CustomResizeHandle from '../../CanvasComponents/CustomResizeHandle';
import FirstWidgetButton from '../../CanvasComponents/FirstWidgetButton';
@@ -44,7 +44,7 @@ export default function MyDashboard({layout, onLayoutChange, isEditable, isDelet
className="layout"
layout={layout}
cols={16} // Total columns in grid
rowHeight={30} // Height of a single row in px
rowHeight={40} // Height of a single row in px
isBounded={true} // Total width of the grid
margin={[15, 5]} // [horizontal, vertical] gap
onLayoutChange={onLayoutChange}
+4 -13
View File
@@ -10,20 +10,13 @@ import { SocialMedia } from './VolcanoTabs/SocialMedia';
import { SearchBar } from "../CanvasComponents/SearchBar";
import { SettingsButton } from "../CanvasComponents/SettingsButton";
export default function Volcano({ view, onChangeView }) {
export default function Volcano() {
return (
<Tabs.Root
key={view}
value={view}
variant="line"
width="100%"
height="100%"
lazyMount
// Prevent navigation away from 'My Dashboard' while in Edit mode
onValueChange={(e) => {
const nextView = e.value;
onChangeView(nextView);
}}
>
<Flex
as="header"
@@ -31,14 +24,12 @@ export default function Volcano({ view, onChangeView }) {
top="0"
zIndex="dropdown" // zIndex: 1100
bg="base200"
pl={5}
pr={2}
pt={3}
p={5}
align="center"
justify="space-between"
width="100%"
>
{/* HEADER */}
{/* SUB HEADER */}
<CanvasHeaderTabs tabs={VOLCANO_TABS} />
<HStack width="258px" justifyContent="flex-end">
<SearchBar />
@@ -46,7 +37,7 @@ export default function Volcano({ view, onChangeView }) {
</HStack>
</Flex>
{/* CANVAS */}
{/* SUB CONTENT */}
<Tabs.Content value="volcanohome">
<VolcanoHome />
</Tabs.Content>
+94 -92
View File
@@ -1,93 +1,95 @@
import Header from './Header/Header.jsx';
import Sidebar from './Sidebar/Sidebar.jsx';
import Canvas from './Canvas/Canvas.jsx';
import { Grid, GridItem, useRecipe, useDialog } from '@chakra-ui/react';
import { useState } from 'react';
import DialogPopup from './Canvas/CanvasComponents/DialogPopup.jsx';
// DEPRECATED
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 [originalWidgets, setOriginalWidgets] = useState([]);
const [view, setView] = useState("mydashboard");
const [layout, setLayout] = useState([]);
const [widgetArray, setWidgetArray] = useState([]);
const dialog = useDialog();
const beginEditIfNeeded = () => {
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([]);
}
// Prevent navigation while in Edit mode without saving or cancelling
const handleViewChange = (nextView) => {
console.log("1. handling view change")
if (view === "mydashboard" && isEditable) {
console.log("2. currently editing");
dialog.setOpen(true);
return;
// const confirmed = window.confirm(
// "You have unsaved changes. Discard them and continue?"
// );
// if (!confirmed) return;
// onCancel();
}
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, originalWidgets, setOriginalWidgets, beginEditIfNeeded
};
const sidebarProps = { view, onChangeView: handleViewChange, darkMode, setDarkMode };
const headerProps = { onChangeView: handleViewChange };
return (
<>
<DialogPopup dialog={dialog} />
<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>
</>
);
}
// import Header from './Header/Header.jsx';
// import Sidebar from './Sidebar/Sidebar.jsx';
// import Canvas from './Canvas/Canvas.jsx';
// import { Grid, GridItem, useRecipe, useDialog } from '@chakra-ui/react';
// import { useState } from 'react';
// import DialogPopup from './Canvas/CanvasComponents/DialogPopup.jsx';
//
// 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 [originalWidgets, setOriginalWidgets] = useState([]);
// const [view, setView] = useState("mydashboard");
// const [layout, setLayout] = useState([]);
// const [widgetArray, setWidgetArray] = useState([]);
// const dialog = useDialog();
//
// const beginEditIfNeeded = () => {
// 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([]);
// }
//
// // Prevent navigation while in Edit mode without saving or cancelling
// const handleViewChange = (nextView) => {
// console.log("1. handling view change")
// if (view === "mydashboard" && isEditable) {
// console.log("2. currently editing");
// dialog.setOpen(true);
// return;
//
// // const confirmed = window.confirm(
// // "You have unsaved changes. Discard them and continue?"
// // );
// // if (!confirmed) return;
// // onCancel();
// }
// 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, originalWidgets, setOriginalWidgets, beginEditIfNeeded
// };
// const sidebarProps = { view, onChangeView: handleViewChange, darkMode, setDarkMode };
// const headerProps = { onChangeView: handleViewChange };
//
// return (
// <>
// <DialogPopup dialog={dialog} />
// <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>
// </>
// );
// }
+3 -7
View File
@@ -8,7 +8,6 @@ import { Mountain, House, Earth, MapPin, ShieldUser, Menu, CircleUserRound, KeyR
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();
@@ -37,8 +36,7 @@ export default function Sidebar() {
];
const toggleSidebar = () => {
if (isOpen) setIsOpen(false);
else setIsOpen(true);
setIsOpen((prev) => !prev);
};
return (
@@ -49,7 +47,7 @@ export default function Sidebar() {
{buttons.map((button, idx) => {
const Icon = button.icon;
return (
return ( // TODO: don't use idx as a key
<Tooltip key={button.name} tooltipkey={idx} content={button.name} sidebarIsOpen={isOpen}>
<Button
as={NavLink}
@@ -76,9 +74,7 @@ export default function Sidebar() {
)
})}
<ColorModeButton
onClick={() => {
toggleColorMode();
}}
onClick={() => toggleColorMode()}
position="absolute"
bottom="2"
/>
+1
View File
@@ -2,6 +2,7 @@ import { Provider } from '@/components/ui/provider'
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import 'leaflet/dist/leaflet.css';
import './css/index.css';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
+17 -17
View File
@@ -134,12 +134,12 @@ const config = defineConfig({
"::-webkit-scrollbar-track": {
background: "transparent",
},
'.leaflet-tile': {
_dark: {
filter: "invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%)",
}
},
'.leaflet-control-zoom-in .leaflet-control-zoom-out .leaflet-control-attribution .leaflet-container': {
// '.leaflet-tile': {
// _dark: {
// filter: "invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%)",
// }
// },
'.leaflet-control-zoom-in, .leaflet-control-zoom-out, .leaflet-control-attribution, .leaflet-container': {
_dark: {
filter: "invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%)",
}
@@ -150,20 +150,20 @@ const config = defineConfig({
}
},
'.leaflet-container': {
fontFamily: "georgia, system-ui, sans-serif !important",
fontFamily: "system-ui, sans-serif !important",
_dark: {
filter: "invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%)",
}
},
".leaflet-popup-content": {
fontFamily: "georgia, system-ui, sans-serif !important"
},
".leaflet-control a": {
fontFamily: "georgia, system-ui, sans-serif !important"
},
".leaflet-tooltip": {
fontFamily: "georgia, system-ui, sans-serif !important"
},
// ".leaflet-popup-content": {
// fontFamily: "georgia, system-ui, sans-serif !important"
// },
// ".leaflet-control a": {
// fontFamily: "georgia, system-ui, sans-serif !important"
// },
// ".leaflet-tooltip": {
// fontFamily: "georgia, system-ui, sans-serif !important"
// },
".react-grid-item": {
transition: "none !important"
},
@@ -185,7 +185,7 @@ const config = defineConfig({
},
tokens: {
fonts: {
body: { value: "georgia, system-ui, sans-serif" },
body: { value: "system-ui, sans-serif" },
},
},
recipes: {