Refactor made changes to file structures
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { Flex, Tabs, HStack } from '@chakra-ui/react';
|
||||
import { MEDataEntry } from './tabs/MEDataEntry.jsx';
|
||||
import { ProjectCollector } from './tabs/ProjectCollector.jsx';
|
||||
import { NASApplication } from './tabs/NASApplication.jsx';
|
||||
import { IVANS } from './tabs/IVANS.jsx'
|
||||
import { SearchBar } from "../../components/dashboard/SearchBar.jsx";
|
||||
import { SettingsButton } from "../../components/dashboard/SettingsButton.jsx";
|
||||
import { CanvasHeaderTabs } from '../../components/dashboard/CanvasHeaderTabs.jsx';
|
||||
import { ADMIN_TABS } from '@/constants/viewKeys.js';
|
||||
|
||||
// TODO - refactor this. Currently Admin doesn't use onEditWidgets or appendWidget
|
||||
export default function Admin({ view, onChangeView, onEditWidgets, appendWidget }) {
|
||||
const settingsButtonProps = { onEditWidgets, appendWidget };
|
||||
|
||||
return (
|
||||
/* HEADER */
|
||||
<Tabs.Root
|
||||
width="100%"
|
||||
height="100%"
|
||||
lazyMount
|
||||
value={ view }
|
||||
variant="line"
|
||||
// Prevent navigation away from 'My Dashboard' while in Edit mode
|
||||
onValueChange={(e) => {
|
||||
const nextView = e.value;
|
||||
onChangeView(nextView);
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
as="header"
|
||||
position="sticky"
|
||||
top="0"
|
||||
zIndex="dropdown" // zIndex: 1100
|
||||
bg="base200"
|
||||
pl={5}
|
||||
pr={2}
|
||||
pt={3}
|
||||
pb={2}
|
||||
align="center"
|
||||
justify="space-between"
|
||||
width="100%"
|
||||
>
|
||||
{/* HEADER */}
|
||||
<CanvasHeaderTabs tabs={ADMIN_TABS} />
|
||||
<HStack width="258px" justifyContent="flex-end">
|
||||
<SearchBar />
|
||||
<SettingsButton />
|
||||
</HStack>
|
||||
</Flex>
|
||||
|
||||
{/* CANVAS */}
|
||||
<Tabs.Content value="medataentry">
|
||||
<MEDataEntry />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="projectcollector">
|
||||
<ProjectCollector />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="nasapplication">
|
||||
<NASApplication />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="ivans">
|
||||
<IVANS />
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export function IVANS() {
|
||||
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">IVANS dashboard coming soon.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
import { useState } from 'react';
|
||||
import { Box, Button, ButtonGroup, Text, Input, List, Field, Stack, SimpleGrid, HStack, Table, Checkbox, createListCollection } from "@chakra-ui/react"
|
||||
import DropDownMenu from "../../../components/dashboard/DropDownMenu.jsx"
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
const startYear = 1970;
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const yearItems = Array.from(
|
||||
{ length: currentYear - startYear + 1 },
|
||||
(_, i) => {
|
||||
const year = startYear + i;
|
||||
return { label: String(year), value: String(year) };
|
||||
}
|
||||
);
|
||||
|
||||
const years = createListCollection({
|
||||
items: yearItems.reverse(),
|
||||
});
|
||||
|
||||
const quarters = createListCollection({
|
||||
items: [
|
||||
{ label: "Q1", value: "quarter_1" },
|
||||
{ label: "Q2", value: "quarter_2" },
|
||||
{ label: "Q3", value: "quarter_3" },
|
||||
{ label: "Q4", value: "quarter_4" },
|
||||
],
|
||||
})
|
||||
|
||||
const sections = createListCollection({
|
||||
items: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Com", value: "com" },
|
||||
{ label: "Eng", value: "eng" },
|
||||
{ label: "Gas", value: "gas" },
|
||||
{ label: "Geophys", value: "geophys" },
|
||||
{ label: "GRSP", value: "grsp" },
|
||||
{ label: "IT", value: "it" },
|
||||
],
|
||||
})
|
||||
|
||||
// Sample entries for visual reference - remove before flight
|
||||
const items = [
|
||||
{
|
||||
id: "r1",
|
||||
section: "Com",
|
||||
volcano: "Nevado del Ruiz",
|
||||
country: "COL",
|
||||
start: "2024-01-15",
|
||||
end: "2024-01-30",
|
||||
type: "Training",
|
||||
vpi10: "Yes",
|
||||
downstream: 1200,
|
||||
male: 600,
|
||||
female: 600,
|
||||
total: 1200,
|
||||
},
|
||||
{
|
||||
id: "r2",
|
||||
section: "Gas",
|
||||
volcano: "Etna",
|
||||
country: "ITA",
|
||||
start: "2024-02-01",
|
||||
end: "2024-02-20",
|
||||
type: "Assistance",
|
||||
vpi10: "No",
|
||||
downstream: 900,
|
||||
male: 450,
|
||||
female: 450,
|
||||
total: 900,
|
||||
},
|
||||
{
|
||||
id: "r3",
|
||||
section: "Eng",
|
||||
volcano: "Kilauea",
|
||||
country: "USA",
|
||||
start: "2024-03-10",
|
||||
end: "2024-03-25",
|
||||
type: "Support",
|
||||
vpi10: "Yes",
|
||||
downstream: 2000,
|
||||
male: 1000,
|
||||
female: 1000,
|
||||
total: 2000,
|
||||
},
|
||||
{
|
||||
id: "r4",
|
||||
section: "Geophys",
|
||||
volcano: "Fuji",
|
||||
country: "JPN",
|
||||
start: "2024-04-05",
|
||||
end: "2024-04-18",
|
||||
type: "Monitoring",
|
||||
vpi10: "Yes",
|
||||
downstream: 1500,
|
||||
male: 700,
|
||||
female: 800,
|
||||
total: 1500,
|
||||
},
|
||||
{
|
||||
id: "r5",
|
||||
section: "IT",
|
||||
volcano: "Merapi",
|
||||
country: "IDN",
|
||||
start: "2024-05-02",
|
||||
end: "2024-05-12",
|
||||
type: "Deployment",
|
||||
vpi10: "No",
|
||||
downstream: 1100,
|
||||
male: 550,
|
||||
female: 550,
|
||||
total: 1100,
|
||||
},
|
||||
{
|
||||
id: "r6",
|
||||
section: "Gas",
|
||||
volcano: "Ranier",
|
||||
country: "US",
|
||||
start: "2024-07-02",
|
||||
end: "2027-05-12",
|
||||
type: "Deployment",
|
||||
vpi10: "Yes",
|
||||
downstream: 1200,
|
||||
male: 570,
|
||||
female: 570,
|
||||
total: 1400,
|
||||
},
|
||||
];
|
||||
|
||||
export function MEDataEntry() {
|
||||
const [value, setValue] = useState("");
|
||||
const [selection, setSelection] = useState([]);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm();
|
||||
|
||||
const onSubmit = (data) => {
|
||||
console.log("Form submitted:", data);
|
||||
}
|
||||
|
||||
const rows = items.map((item) => (
|
||||
<Table.Row
|
||||
key={item.id}
|
||||
data-selected={selection.includes(item.id) ? "" : undefined }
|
||||
>
|
||||
<Table.Cell>
|
||||
<Checkbox.Root
|
||||
size="sm"
|
||||
mt="0.5"
|
||||
aria-label="Select row"
|
||||
checked={selection.includes(item.id)}
|
||||
onCheckedChange={(changes) => {
|
||||
setSelection((prev) =>
|
||||
changes.checked
|
||||
? [...prev, item.id]
|
||||
: prev.filter((id) => id !== item.id ),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Checkbox.HiddenInput />
|
||||
<Checkbox.Control>
|
||||
<Checkbox.Indicator />
|
||||
</Checkbox.Control>
|
||||
</Checkbox.Root>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{item.section}</Table.Cell>
|
||||
<Table.Cell>{item.volcano}</Table.Cell>
|
||||
<Table.Cell>{item.country}</Table.Cell>
|
||||
<Table.Cell>{item.start}</Table.Cell>
|
||||
<Table.Cell>{item.end}</Table.Cell>
|
||||
<Table.Cell>{item.type}</Table.Cell>
|
||||
<Table.Cell>{item.vpi10}</Table.Cell>
|
||||
<Table.Cell isNumeric>{item.downstream}</Table.Cell>
|
||||
<Table.Cell isNumeric>{item.male}</Table.Cell>
|
||||
<Table.Cell isNumeric>{item.female}</Table.Cell>
|
||||
<Table.Cell isNumeric>{item.total}</Table.Cell>
|
||||
</Table.Row>
|
||||
))
|
||||
|
||||
return(
|
||||
<Box>
|
||||
<HStack spacing={6} m={2}>
|
||||
<DropDownMenu collection={years} placeholder="Year" width="130px" />
|
||||
<DropDownMenu collection={quarters} placeholder="Quarter" width="130px" />
|
||||
<DropDownMenu collection={sections} placeholder="Section" width="130px" />
|
||||
</HStack>
|
||||
<Text fontSize="xs" pl={2} pt={2}>
|
||||
Use Volcano names as found in GVP and dashboard. (e.g. Nevado del Ruiz, Chiles-Cerro Negro).
|
||||
</Text>
|
||||
<List.Root as="ul" styleType="circle" fontSize="xs" pl={6}>
|
||||
<List.Item>Edit entry.</List.Item>
|
||||
<List.Item>Delete entry.</List.Item>
|
||||
<List.Item>Copy entry to form. Then hitting 'Save' will create a new entry.</List.Item>
|
||||
</List.Root>
|
||||
|
||||
<Box m={4} mt={1} border="1px solid" borderRadius="sm" borderColor="gray.500">
|
||||
<Box m={4} border="1px solid">
|
||||
<Box pl={1} bg="gray.200" border="1px solid" borderColor="gray.400">
|
||||
Number of people benefitting from VDAP activities, disaggregate by sex
|
||||
</Box>
|
||||
|
||||
{/* <Box overflowX="auto"> */}
|
||||
<Table.ScrollArea height="240px">
|
||||
<Table.Root variant="outline" overflow="initial" size="sm" stickyHeader interactive>
|
||||
<Table.Header>
|
||||
<Table.Row bg="bg.subtle">
|
||||
<Table.ColumnHeader w="40px"/>
|
||||
<Table.ColumnHeader>Section</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>Volcano</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>Country</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>Start Date of Assistance</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>End Date of Assistance</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>Assistance Type</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>VPI10</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>Downstream Population</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>Male</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>Female</Table.ColumnHeader>
|
||||
<Table.ColumnHeader>Total</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
||||
<Table.Body>
|
||||
{rows}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Table.ScrollArea>
|
||||
{/* </Box> */}
|
||||
</Box>
|
||||
{/* Row actions aligned right */}
|
||||
<HStack justify="flex-end" p={3} pt={0} borderBottom="1px solid" borderColor="gray.300" spacing={3} >
|
||||
<ButtonGroup variant="surface" size="sm">
|
||||
<Button >EDIT</Button>
|
||||
<Button>COPY</Button>
|
||||
<Button variant="solid" colorPalette="red">DELETE</Button>
|
||||
</ButtonGroup>
|
||||
</HStack>
|
||||
|
||||
{/* Form Section */}
|
||||
<Box
|
||||
as="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
m={4}
|
||||
border="1px solid"
|
||||
>
|
||||
<Box pl={1} bg="gray.200" border="1px solid" borderColor="gray.400" overflow="hidden">
|
||||
Fill in Form
|
||||
</Box>
|
||||
|
||||
<Stack p={2} gap="2" css={{ "--field-label-width": "80px" }}>
|
||||
<HStack spacing={6} align="flex-start" w="100%" flexWrap="wrap">
|
||||
<Field.Root orientation="horizontal" flex="1" minW="260px">
|
||||
<Field.Label>Country:</Field.Label>
|
||||
<Input {...register("country")} placeholder="US" />
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root orientation="horizontal" flex="1" minW="260px">
|
||||
<Field.Label>Start Date of Assistance:</Field.Label>
|
||||
<Input {...register("startdate", { valueAsDate: true })} placeholder="9/6/1987" />
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root orientation="horizontal" flex="1">
|
||||
<Field.Label>VPI10 (LandScan 2014):</Field.Label>
|
||||
<Input {...register("lastname")} placeholder="CatPic.jpg" />
|
||||
</Field.Root>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing={6} align="flex-start" w="100%" flexWrap="wrap">
|
||||
<Field.Root orientation="horizontal" flex="1" minW="260px">
|
||||
<Field.Label>Volcano:</Field.Label>
|
||||
<Input {...register("volcano")} placeholder="St Helens" />
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root orientation="horizontal" flex="1" minW="260px">
|
||||
<Field.Label>End Date of Assistance:</Field.Label>
|
||||
<Input {...register("enddate", { valueAsDate: true })} placeholder="7/14/2054" />
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root orientation="horizontal" flex="1">
|
||||
<Field.Label>Downstream Affected Population:</Field.Label>
|
||||
<Input {...register("downstream", { valueAsNumber: true })} placeholder="1337" />
|
||||
</Field.Root>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing={6} align="flex-start" w="100%" flexWrap="wrap">
|
||||
<Field.Root orientation="horizontal" flex="1" minW="260px">
|
||||
<Field.Label>Male Population:</Field.Label>
|
||||
<Input {...register("male", { valueAsNumber: true })} placeholder="1776" />
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root orientation="horizontal" flex="1" minW="260px">
|
||||
<Field.Label>Female Population:</Field.Label>
|
||||
<Input {...register("female", { valueAsNumber: true })} placeholder="1892" />
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root orientation="horizontal" flex="1">
|
||||
<Field.Label>Total Population:</Field.Label>
|
||||
<Input {...register("total", { valueAsNumber: true })} placeholder="3668" />
|
||||
</Field.Root>
|
||||
</HStack>
|
||||
|
||||
<Field.Root orientation="horizontal" flex="1" pt={2}>
|
||||
<Field.Label>Assistance Type:</Field.Label>
|
||||
<Input {...register("assistance")} placeholder="Monetary" flex="1" />
|
||||
</Field.Root>
|
||||
|
||||
<HStack alignSelf="flex-start" pt={2}>
|
||||
<ButtonGroup variant="surface" size="sm">
|
||||
<Button type="submit" variant="solid" colorPalette="green" >
|
||||
Add
|
||||
</Button>
|
||||
<Button onClick={() => reset()}>
|
||||
Reset
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</HStack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export function NASApplication() {
|
||||
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">NAS Application dashboard coming soon.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export function ProjectCollector() {
|
||||
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">Project Collector dashboard coming soon.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Box, Flex, Field, Input, Stack, Heading, Button } from "@chakra-ui/react"
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
export default function Account() {
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export default function Personal() {
|
||||
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">
|
||||
Personal Info Canvas coming soon!
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export default function Security() {
|
||||
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">
|
||||
Security Canvas coming soon!
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { HStack, Flex, Box } from "@chakra-ui/react";
|
||||
import { NavLink, Outlet, useLocation } from "react-router-dom";
|
||||
import { SearchBar } from "../../components/dashboard/SearchBar.jsx";
|
||||
import { SettingsButton } from "../../components/dashboard/SettingsButton.jsx";
|
||||
|
||||
export default function Home() {
|
||||
const location = useLocation();
|
||||
const isPersonalDashboard = location.pathname === "/home" || location.pathname === "/home/";
|
||||
|
||||
// 1. CSS making this look like how we want it, TODO turn to theme?
|
||||
const tabStyles = {
|
||||
fontSize: "lg",
|
||||
fontWeight: "normal",
|
||||
color: "fg.muted",
|
||||
px: 4, // Horizontal padding
|
||||
py: 2, // Vertical padding
|
||||
borderBottom: "2px solid transparent",
|
||||
mb: "-2px", // 🔥 The trick: Pulls the active border down to overlap the container's line
|
||||
transition: "all 0.2s",
|
||||
_hover: { color: "fg" },
|
||||
_activeLink: {
|
||||
color: "color", // Or whatever your primary text color is
|
||||
borderColor: "currentColor", // The active underline
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" height="100%" width="100%">
|
||||
|
||||
{/* 2. 🛠️ SUB HEADER: Matches your original padding and background */}
|
||||
<Flex as="header" position="sticky" top="0" zIndex="dropdown" bg="base200" p={5} align="center" justify="space-between" width="100%">
|
||||
|
||||
{/* TABS CONTAINER - Replicates the Tabs.List line container */}
|
||||
<Flex borderBottom="2px solid" borderColor="border.muted" gap={2}>
|
||||
<Box as={NavLink} to="/home" end css={tabStyles}>My Dashboard</Box>
|
||||
<Box as={NavLink} to="/home/gas" css={tabStyles}>Gas</Box>
|
||||
<Box as={NavLink} to="/home/seismic" css={tabStyles}>Seismic</Box>
|
||||
<Box as={NavLink} to="/home/remote" css={tabStyles}>Remote Sensing</Box>
|
||||
<Box as={NavLink} to="/home/daily" css={tabStyles}>Daily Activity</Box>
|
||||
</Flex>
|
||||
|
||||
{/* EDIT MODE BUTTONS & CONTROLS */}
|
||||
<HStack width="258px" justifyContent="flex-end">
|
||||
{isPersonalDashboard ? (
|
||||
<>
|
||||
<SearchBar placeholder="Enter a volcano to search" size="sm" width="210px" />
|
||||
{/* This will eventually trigger your Zustand store's edit mode! */}
|
||||
<SettingsButton />
|
||||
</>
|
||||
) : (
|
||||
<SearchBar placeholder="Enter a volcano to search" size="sm" width="210px" />
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
</Flex>
|
||||
|
||||
{/* 3. 🛠️ CANVAS AREA */}
|
||||
<Flex flex="1" overflow="auto">
|
||||
<Outlet />
|
||||
</Flex>
|
||||
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Box, Text, Flex } from "@chakra-ui/react";
|
||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
||||
import { useDashboardStore } from "@/store/useDashboardStore.jsx";
|
||||
|
||||
// React-Grid-Layout requires this wrapper to automatically calculate screen width
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
|
||||
export default function MyDashboard() {
|
||||
// 1. ☁️ Connect to the Cloud (Zustand)
|
||||
const widgets = useDashboardStore(state => state.widgets);
|
||||
const isEditing = useDashboardStore(state => state.isEditing);
|
||||
const updateLayoutPositions = useDashboardStore(state => state.updateLayoutPositions);
|
||||
|
||||
// 2. 🛠️ Format the data for RGL
|
||||
// RGL expects a very specific array format for its layout prop
|
||||
const rglLayout = widgets.map(w => ({
|
||||
i: w.id,
|
||||
x: w.x,
|
||||
y: w.y,
|
||||
w: w.w,
|
||||
h: w.h,
|
||||
isResizable: false // 🚫 The magic lock! No resizing allowed globally.
|
||||
}));
|
||||
|
||||
return (
|
||||
<Box width="100%" height="100%">
|
||||
<ResponsiveGridLayout
|
||||
className="layout"
|
||||
layouts={{ lg: rglLayout }} // We feed it the formatted layout map
|
||||
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
||||
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }} // Our 12-column master grid
|
||||
rowHeight={30}
|
||||
isDraggable={isEditing} // Only allows dragging when your Settings tools toggle this
|
||||
isResizable={false} // Double lock-down
|
||||
onLayoutChange={(newLayout) => updateLayoutPositions(newLayout)} // Fires when user drops a widget
|
||||
compactType="vertical" // Auto-packs widgets to the top
|
||||
>
|
||||
{/* 3. 🎨 Paint the Widgets */}
|
||||
{widgets.map(widget => (
|
||||
<Box
|
||||
key={widget.id}
|
||||
bg="white"
|
||||
borderRadius="md"
|
||||
boxShadow={isEditing ? "outline" : "sm"} // Visual cue when editing
|
||||
border={isEditing ? "2px dashed gray" : "1px solid"}
|
||||
borderColor={isEditing ? "gray.400" : "gray.200"}
|
||||
overflow="hidden"
|
||||
cursor={isEditing ? "grab" : "default"}
|
||||
>
|
||||
{/* 🚧 Temporary Placeholder!
|
||||
Eventually, we will swap this block out for a dynamic component loader
|
||||
that looks at widget.type and loads the correct graph or table.
|
||||
*/}
|
||||
<Flex width="100%" height="100%" align="center" justify="center" direction="column">
|
||||
<Text fontWeight="bold" color="gray.600">{widget.type}</Text>
|
||||
<Text fontSize="sm" color="gray.400">Size: {widget.size}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</ResponsiveGridLayout>
|
||||
|
||||
{/* Empty State Helper */}
|
||||
{widgets.length === 0 && (
|
||||
<Flex width="100%" height="200px" align="center" justify="center">
|
||||
<Text color="gray.400">Your dashboard is empty. Click Settings to add widgets.</Text>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
|
||||
import MapButtonControls from "../../components/maps/MapButtonControls.jsx";
|
||||
import MapSettingsButton from "../../components/maps/MapSettingsButton.jsx";
|
||||
|
||||
export default function GlobalMap() {
|
||||
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'
|
||||
/>
|
||||
<MapButtonControls settingsBtn = {<MapSettingsButton />} position="topright" />
|
||||
<Marker position={position}>
|
||||
<Popup>
|
||||
CVO — Home of the volcanoligists
|
||||
</Popup>
|
||||
</Marker>
|
||||
</MapContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
|
||||
|
||||
export default function RegionalMap() {
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Box, Text, Heading, DataList, Flex, HStack, Tabs } from '@chakra-ui/react';
|
||||
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
|
||||
import { CanvasHeaderTabs } from '../../components/dashboard/CanvasHeaderTabs.jsx';
|
||||
import { VOLCANO_TABS } from '@/constants/viewKeys.js';
|
||||
import { VolcanoHome } from './tabs/VolcanoHome.jsx';
|
||||
import { PartnerData } from './tabs/PartnerData.jsx';
|
||||
import { Reports } from './tabs/Reports.jsx';
|
||||
import { OfficialInstitutions } from './tabs/OfficialInstitutions.jsx';
|
||||
import { SocialMedia } from './tabs/SocialMedia.jsx';
|
||||
import { SearchBar } from "../../components/dashboard/SearchBar.jsx";
|
||||
import { SettingsButton } from "../../components/dashboard/SettingsButton.jsx";
|
||||
|
||||
export default function Volcano() {
|
||||
return (
|
||||
<Tabs.Root
|
||||
variant="line"
|
||||
width="100%"
|
||||
height="100%"
|
||||
lazyMount
|
||||
>
|
||||
<Flex
|
||||
as="header"
|
||||
position="sticky"
|
||||
top="0"
|
||||
zIndex="dropdown" // zIndex: 1100
|
||||
bg="base200"
|
||||
p={5}
|
||||
align="center"
|
||||
justify="space-between"
|
||||
width="100%"
|
||||
>
|
||||
{/* SUB HEADER */}
|
||||
<CanvasHeaderTabs tabs={VOLCANO_TABS} />
|
||||
<HStack width="258px" justifyContent="flex-end">
|
||||
<SearchBar />
|
||||
<SettingsButton />
|
||||
</HStack>
|
||||
</Flex>
|
||||
|
||||
{/* SUB CONTENT */}
|
||||
<Tabs.Content value="volcanohome">
|
||||
<VolcanoHome />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="partner">
|
||||
<PartnerData />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="reports">
|
||||
<Reports />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="official">
|
||||
<OfficialInstitutions />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="social">
|
||||
<SocialMedia />
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export function OfficialInstitutions() {
|
||||
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">Official Institutions dashboard coming soon.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export function PartnerData() {
|
||||
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">Partner Data dashboard coming soon.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export function Reports() {
|
||||
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">Reports dashboard coming soon.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
|
||||
export function SocialMedia() {
|
||||
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">Social Media dashboard coming soon.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Box, Text, Heading, DataList, HStack } from '@chakra-ui/react';
|
||||
import React from "react";
|
||||
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
|
||||
|
||||
export function VolcanoHome() {
|
||||
const position = [46.2, -122.18]; // Mt. St. Helens
|
||||
const stats = [
|
||||
{ label: "Elevation", value: "2,539 (m) 8,330 (ft)", helpText: "Volcano Elevation" },
|
||||
{ label: "Volcano Type", value: "Stratovolcano", helpText: "Volcano Type" },
|
||||
{ label: "Composition", value: "Basalt to Ryodacite", helpText: "Volcano Composition" },
|
||||
{ label: "Recent Eruptions", value: "1980, 2004-2008", helpText: "Last Eruption" },
|
||||
{ label: "Threat Potential", value: "Very High", helpText: "Threat Potential" },
|
||||
|
||||
]
|
||||
|
||||
const historyText = `
|
||||
Mount St. Helens, located in the Cascade Range of Washington State, is one of the most
|
||||
active and well-known volcanoes in the United States. It famously erupted on May 18, 1980,
|
||||
in one of the most significant volcanic events in U.S. history, which caused widespread
|
||||
devastation and loss of life. The eruption reduced the mountain’s summit and created a large
|
||||
crater. Subsequent dome-building eruptions continued through the 2000s, and the volcano
|
||||
remains closely monitored today.
|
||||
`;
|
||||
|
||||
return (
|
||||
<Box width="100%" px={6} py={2}>
|
||||
<Heading textAlign="center" mb={6} size="xl" fontWeight="bold">
|
||||
Mount St. Helens - United States CVO
|
||||
</Heading>
|
||||
|
||||
<Box height="550px" borderRadius="md" overflow="hidden" boxShadow="md" mb={6}>
|
||||
<MapContainer center={position} zoom={10} 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>
|
||||
</Box>
|
||||
|
||||
<HStack align="start" spacing={10} mt={4}>
|
||||
<Box width="300px" height="200px" overflowY="auto" p={4} border="1px solid" borderColor="gray.200" borderRadius="md" bg="gray.50" boxShadow="sm">
|
||||
<DataList.Root orientation="horizontal">
|
||||
{stats.map((item) => (
|
||||
<DataList.Item key={item.label}>
|
||||
<DataList.ItemLabel>
|
||||
{item.label}
|
||||
</DataList.ItemLabel>
|
||||
<DataList.ItemValue>
|
||||
{item.value}
|
||||
</DataList.ItemValue>
|
||||
</DataList.Item>
|
||||
))}
|
||||
</DataList.Root>
|
||||
</Box>
|
||||
|
||||
<Box flex="1" height="200px" overflowY="auto" p={4} border="1px solid" borderColor="gray.200" borderRadius="md" bg="gray.50" boxShadow="sm">
|
||||
<Heading textAlign="center" pb={2}>Historical Summary</Heading>
|
||||
<Text>
|
||||
{ historyText}
|
||||
</Text>
|
||||
</Box>
|
||||
</HStack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user