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