add: new filtering options were added to the alert changes widget. options are lvl inc, lvl dec, elevated lvls. additionally, updated how the UI looks for the al changes widget settings menu
This commit is contained in:
@@ -1,28 +1,51 @@
|
|||||||
import {useDashboardStore} from "@/store/useDashboardStore.jsx";
|
import { useDashboardStore } from "@/store/useDashboardStore.jsx";
|
||||||
import VdapApi from "../../hooks/useVdapApi.js";
|
import VdapApi from "../../hooks/useVdapApi.js";
|
||||||
import useBuildUrl from "../../hooks/useBuildUrl.js";
|
import useBuildUrl from "../../hooks/useBuildUrl.js";
|
||||||
import { Footprints, Plane, Sailboat, TrendingUp, TrendingDown } from 'lucide-react';
|
import {
|
||||||
import {Box, Collapsible, Flex, Spinner, Text, Timeline, Center} from "@chakra-ui/react";
|
Footprints,
|
||||||
|
Plane,
|
||||||
|
Sailboat,
|
||||||
|
TrendingUp,
|
||||||
|
TrendingDown,
|
||||||
|
} from "lucide-react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Collapsible,
|
||||||
|
Flex,
|
||||||
|
Spinner,
|
||||||
|
Text,
|
||||||
|
Timeline,
|
||||||
|
Center,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { useMemo, useCallback } from "react";
|
import { useMemo, useCallback } from "react";
|
||||||
|
|
||||||
|
export default function AlertChanges({ settings, widgetID }) {
|
||||||
|
// 0) CREATE SELF AWARENESS, by finding this specific widgets object in the state-list of active/displayed widgets
|
||||||
|
const thisWidget = useDashboardStore(
|
||||||
|
useCallback(
|
||||||
|
(state) => state.widgets.find((widget) => widget.id === widgetID),
|
||||||
|
[widgetID],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
export default function AlertChanges({ settings, widgetID}) {
|
// 1) BUILD URL, default is 7 days past
|
||||||
// Find this specific widgets object in the state list of active & displayed widgets
|
const url = useBuildUrl(
|
||||||
const thisWidget = useDashboardStore(useCallback((state) =>
|
"/api/v2/alert_changes",
|
||||||
state.widgets.find((widget) => widget.id === widgetID), [widgetID]
|
settings?.startDate,
|
||||||
));
|
settings?.endDate,
|
||||||
|
);
|
||||||
|
|
||||||
// build url, default is 7 days past
|
// 2) CALL API hook to retrieve data
|
||||||
const url = useBuildUrl('/api/v2/alert_changes', settings?.startDate, settings?.endDate);
|
const { data: apiData, isLoading, error } = VdapApi(url);
|
||||||
|
|
||||||
// call api hook to retrieve data
|
// 3) SORT the data by grouping into volcanoes and sorting form newest to oldest
|
||||||
const {data: apiData, isLoading, error} = VdapApi(url);
|
// doing this 1st bc the filters might change w/ user input but the sorts (currently) wont and thus will
|
||||||
|
// only run when the api retrieves new data
|
||||||
const data = useMemo(() => {
|
const sortedData = useMemo(() => {
|
||||||
if (apiData.length === 0) return [];
|
if (apiData.length === 0) return [];
|
||||||
|
|
||||||
// .reduce() is a built-in array method used to transform an entire array into a single value (like an object)
|
// .reduce() is a built-in array method used to transform an entire array into a single value (like an object)
|
||||||
// here it returns a single massive object w/ volcano names as keys
|
// here we sort the data by volcano, and return it as a single massive object w/ volcano names as keys
|
||||||
const grouped = apiData.reduce((accumulator, item) => {
|
const grouped = apiData.reduce((accumulator, item) => {
|
||||||
if (!accumulator[item.volcano]) accumulator[item.volcano] = [];
|
if (!accumulator[item.volcano]) accumulator[item.volcano] = [];
|
||||||
accumulator[item.volcano].push(item);
|
accumulator[item.volcano].push(item);
|
||||||
@@ -33,33 +56,97 @@ export default function AlertChanges({ settings, widgetID}) {
|
|||||||
const group_arr = Object.entries(grouped);
|
const group_arr = Object.entries(grouped);
|
||||||
|
|
||||||
// sort data newest to oldest within each volcano
|
// sort data newest to oldest within each volcano
|
||||||
const sorted = group_arr.map((item) =>
|
const sorted = group_arr.map((item) => [
|
||||||
[
|
item[0], // volcano name
|
||||||
item[0], // volcano name
|
[...item[1]].sort( // item[1] is the array of al changes for a volcano
|
||||||
[...(item[1])].sort((a, b) =>
|
(a, b) =>
|
||||||
new Date(b.alert_start_date_local) - new Date(a.alert_start_date_local))
|
new Date(b.alert_start_date_local) -
|
||||||
]
|
new Date(a.alert_start_date_local),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// the volcanoes with the most recent issued alert level change appear at the top of the list (newest -> oldest)
|
||||||
|
const sorted_entries = [...sorted].sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b[1][0].alert_start_date_local) -
|
||||||
|
new Date(a[1][0].alert_start_date_local),
|
||||||
);
|
);
|
||||||
|
|
||||||
// volcano with most recent alert level change appears at top of list
|
// additionally, seperates out data into domains within a volcano for sm widgets,
|
||||||
const sorted_entries = [...sorted].sort((a, b) => new Date(b[1][0].alert_start_date_local) - new Date(a[1][0].alert_start_date_local));
|
// to list all most recent al changes w/in a volcano w/in a domain
|
||||||
|
|
||||||
if (thisWidget?.size === "small") {
|
if (thisWidget?.size === "small") {
|
||||||
return (sorted_entries.map((volcano) => {
|
return sorted_entries.map((volcano) => {
|
||||||
const domain = volcano[1].reduce((acc, curr) => {
|
const domain = volcano[1].reduce((acc, curr) => {
|
||||||
if (!acc[curr.alert_scheme_domain]) acc[curr.alert_scheme_domain] = [];
|
if (!acc[curr.alert_scheme_domain])
|
||||||
|
acc[curr.alert_scheme_domain] = [];
|
||||||
acc[curr.alert_scheme_domain].push(curr);
|
acc[curr.alert_scheme_domain].push(curr);
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const domain_arr = Object.entries(domain);
|
const domain_arr = Object.entries(domain);
|
||||||
|
console.log("sm data: ", [volcano[0], domain_arr]);
|
||||||
return [volcano[0], domain_arr];
|
return [volcano[0], domain_arr];
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("lg data: ", sorted_entries);
|
||||||
return sorted_entries;
|
return sorted_entries;
|
||||||
}, [apiData, thisWidget]);
|
}, [apiData, thisWidget]);
|
||||||
|
|
||||||
|
// 4) FILTER according to what is set in the widgets settings
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
if (sortedData.length === 0) return [];
|
||||||
|
let filtered_arr = [];
|
||||||
|
|
||||||
|
// must show at least one inc or dec alert levels, otherwise no data is displayed
|
||||||
|
if (
|
||||||
|
!settings?.filters?.levelIncreases &&
|
||||||
|
!settings?.filters?.levelDecreases
|
||||||
|
)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
// return all data
|
||||||
|
if (
|
||||||
|
settings?.filters?.levelIncreases &&
|
||||||
|
settings?.filters?.levelDecreases &&
|
||||||
|
!settings?.filters.elevatedLevels
|
||||||
|
)
|
||||||
|
return sortedData;
|
||||||
|
|
||||||
|
// only elevated alert levels
|
||||||
|
filtered_arr = settings?.filters.elevatedLevels
|
||||||
|
? sortedData.map((volcano) => [
|
||||||
|
volcano[0],
|
||||||
|
volcano[1].filter(
|
||||||
|
(item) =>
|
||||||
|
item.alert_scheme_highest - Number(item.alert_level_rank) <= 1,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.filter((volcano) => volcano[1].length > 0)
|
||||||
|
: sortedData;
|
||||||
|
|
||||||
|
// only decreases (rm inc)
|
||||||
|
filtered_arr = !settings?.filters.levelIncreases
|
||||||
|
? filtered_arr.map((volcano) => [
|
||||||
|
volcano[0],
|
||||||
|
volcano[1].filter((item) => !(item.change_type === "increase")),
|
||||||
|
])
|
||||||
|
.filter((volcano) => volcano[1].length > 0)
|
||||||
|
: filtered_arr;
|
||||||
|
|
||||||
|
// only increases (rm dec)
|
||||||
|
filtered_arr = !settings?.filters?.levelDecreases
|
||||||
|
? filtered_arr.map((volcano) => [
|
||||||
|
volcano[0],
|
||||||
|
volcano[1].filter((item) => !(item.change_type === "decrease")),
|
||||||
|
])
|
||||||
|
.filter((volcano) => volcano[1].length > 0)
|
||||||
|
: filtered_arr;
|
||||||
|
|
||||||
|
console.log("filtered_arr: ", filtered_arr);
|
||||||
|
return filtered_arr;
|
||||||
|
}, [sortedData, settings]);
|
||||||
|
|
||||||
// Alert colors are now light/dark mode responsive
|
// Alert colors are now light/dark mode responsive
|
||||||
const alertColors = {
|
const alertColors = {
|
||||||
Red: "alertRed",
|
Red: "alertRed",
|
||||||
@@ -68,149 +155,283 @@ export default function AlertChanges({ settings, widgetID}) {
|
|||||||
Green: "alertGreen",
|
Green: "alertGreen",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) return (
|
if (isLoading)
|
||||||
<Flex w="100%" h="100%" align="center" justify="center" bg="transparent">
|
return (
|
||||||
<Spinner size="xl" color="blue.400" thickness="4px"/>
|
<Flex w="100%" h="100%" align="center" justify="center" bg="transparent">
|
||||||
</Flex>
|
<Spinner size="xl" color="blue.400" thickness="4px" />
|
||||||
);
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
if (error) return (
|
if (error)
|
||||||
<Flex w="100%" h="100%" align="center" justify="center" bg="transparent">
|
return (
|
||||||
<Text color="red.400">{error}</Text>
|
<Flex w="100%" h="100%" align="center" justify="center" bg="transparent">
|
||||||
</Flex>
|
<Text color="red.400">{error}</Text>
|
||||||
);
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
if (data.length === 0) return (
|
if (filteredData.length === 0)
|
||||||
<Flex w="100%" h="100%" align="center" justify="center" bg="transparent">
|
return (
|
||||||
<Text textAlign="center" color="widgetTextMuted">No flight level data available for the selected dates.</Text>
|
<Flex w="100%" h="100%" align="center" justify="center" bg="transparent">
|
||||||
</Flex>
|
<Text textAlign="center" color="widgetTextMuted">
|
||||||
);
|
No alert level data available for the selected dates.
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// bg="transparent" to let the glass panel show through
|
<Box
|
||||||
<Box display="flex" flexDirection="column" gap={2} p={2} w="100%" h="100%" bg="transparent" overflowY="auto">
|
display="flex"
|
||||||
{data.map((volcano, index) => {
|
flexDirection="column"
|
||||||
if (thisWidget?.size === "large") return (
|
gap={2}
|
||||||
<Collapsible.Root key={index} w="100%">
|
p={2}
|
||||||
<Collapsible.Context>
|
w="100%"
|
||||||
{(collapsible) => (
|
h="100%"
|
||||||
<>
|
bg="transparent"
|
||||||
<Collapsible.Trigger w="100%">
|
overflowY="auto"
|
||||||
<Flex direction="row"
|
>
|
||||||
|
{filteredData.map((volcano, index) => {
|
||||||
|
if (thisWidget?.size === "large")
|
||||||
|
return (
|
||||||
|
<Collapsible.Root key={index} w="100%">
|
||||||
|
<Collapsible.Context>
|
||||||
|
{(collapsible) => (
|
||||||
|
<>
|
||||||
|
<Collapsible.Trigger w="100%">
|
||||||
|
<Flex
|
||||||
|
direction="row"
|
||||||
|
w="100%"
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
p={1}
|
||||||
|
pl={3}
|
||||||
|
pr={3}
|
||||||
|
borderTopRadius="lg"
|
||||||
|
borderBottomRadius={collapsible.open ? "0" : "lg"}
|
||||||
|
bg="widgetItemBg"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
w="100%"
|
w="100%"
|
||||||
justify="space-between" align="center"
|
minW={0}
|
||||||
p={1} pl={3} pr={3}
|
textAlign="left"
|
||||||
borderTopRadius="lg"
|
>
|
||||||
borderBottomRadius={collapsible.open ? "0" : "lg"}
|
<Text truncate color="text" fontWeight="medium">
|
||||||
bg="widgetItemBg"
|
{volcano[0]}
|
||||||
>
|
</Text>
|
||||||
<Flex direction="column" w="100%" minW={0} textAlign="left">
|
<Text truncate fontSize="xs" color="widgetTextMuted">
|
||||||
<Text truncate color="text" fontWeight="medium">{volcano[0]}</Text>
|
{volcano[1][0]?.alert_start_date_local}
|
||||||
<Text truncate fontSize="xs" color="widgetTextMuted">{volcano[1][0]?.alert_start_date_local}</Text>
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex w="100%" minW={0} justify="right" gap={2}>
|
||||||
|
<Flex w="fit">
|
||||||
|
{volcano[1][0]?.change_type === "increase" ? (
|
||||||
|
<TrendingUp />
|
||||||
|
) : (
|
||||||
|
<TrendingDown />
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Text
|
||||||
|
truncate
|
||||||
|
pl={2}
|
||||||
|
pr={2}
|
||||||
|
borderRadius="lg"
|
||||||
|
bg="widgetBadgeBg"
|
||||||
|
color="text"
|
||||||
|
fontWeight="semibold"
|
||||||
|
>
|
||||||
|
{volcano[1][0]?.alert_level_name}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex w="100%" minW={0} justify="right">
|
</Collapsible.Trigger>
|
||||||
<Text truncate pl={2} pr={2} borderRadius="lg" bg="widgetBadgeBg" color="text" fontWeight="semibold">
|
|
||||||
{volcano[1][0]?.alert_level_name}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Collapsible.Trigger>
|
|
||||||
|
|
||||||
<Collapsible.Content w="100%" p={3} borderBottomRadius="lg" bg="widgetItemExpandedBg">
|
<Collapsible.Content
|
||||||
<Timeline.Root size="md" variant="subtle">
|
w="100%"
|
||||||
{volcano[1]?.map((object, index) => (
|
p={3}
|
||||||
<Timeline.Item key={index} w="100%" h="100%">
|
borderBottomRadius="lg"
|
||||||
<Timeline.Connector >
|
bg="widgetItemExpandedBg"
|
||||||
<Timeline.Indicator outline="none" bg="widgetBadgeBg" color="iconColor" size="lg">
|
>
|
||||||
{object?.alert_scheme_domain === "terrestrial" && <Footprints size={15} />}
|
<Timeline.Root size="md" variant="subtle">
|
||||||
{object?.alert_scheme_domain === "aviation" && <Plane size={15} />}
|
{volcano[1]?.map((object, index) => (
|
||||||
{object?.alert_scheme_domain === "maritime" && <Sailboat size={15} />}
|
<Timeline.Item key={index} w="100%" h="100%">
|
||||||
</Timeline.Indicator>
|
<Timeline.Connector>
|
||||||
<Timeline.Indicator h="50%" outline="none" bg="transparent" color="iconColor" size="lg">
|
<Timeline.Indicator
|
||||||
{object?.change_type === "increase" ? <TrendingUp /> : <TrendingDown />}
|
outline="none"
|
||||||
</Timeline.Indicator>
|
bg="widgetBadgeBg"
|
||||||
</Timeline.Connector>
|
color="iconColor"
|
||||||
<Timeline.Content w="100%" h="100%">
|
size="lg"
|
||||||
<Timeline.Description w="100%" h="100%" gap={1}>
|
>
|
||||||
<Flex direction="column" gap={1}>
|
{object?.alert_scheme_domain ===
|
||||||
<Text w="100%" h="100%" color="widgetTextMuted">{object?.alert_start_date_local}</Text>
|
"terrestrial" && <Footprints size={15} />}
|
||||||
<Text w="100%" h="100%" color={alertColors[object?.alert_level_name] || "text"} fontSize="md" fontWeight="semibold">
|
{object?.alert_scheme_domain === "aviation" && (
|
||||||
{object?.alert_level_name}
|
<Plane size={15} />
|
||||||
</Text>
|
)}
|
||||||
<Flex w="100%" h="20px" direction="row" justify="left" align="center">
|
{object?.alert_scheme_domain === "maritime" && (
|
||||||
<Text color="widgetTextMuted">Alert Scale: </Text>
|
<Sailboat size={15} />
|
||||||
{(Array.from({ length: Number(object?.alert_scheme_highest) - Number(object?.alert_scheme_lowest) + 1}, (v, i) => Number(object?.alert_scheme_lowest) + i))
|
)}
|
||||||
.map((num) => (
|
</Timeline.Indicator>
|
||||||
|
<Timeline.Indicator
|
||||||
|
h="50%"
|
||||||
|
outline="none"
|
||||||
|
bg="transparent"
|
||||||
|
color="iconColor"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
{object?.change_type === "increase" ? (
|
||||||
|
<TrendingUp />
|
||||||
|
) : (
|
||||||
|
<TrendingDown />
|
||||||
|
)}
|
||||||
|
</Timeline.Indicator>
|
||||||
|
</Timeline.Connector>
|
||||||
|
<Timeline.Content w="100%" h="100%">
|
||||||
|
<Timeline.Description w="100%" h="100%" gap={1}>
|
||||||
|
<Flex direction="column" gap={1}>
|
||||||
|
<Text
|
||||||
|
w="100%"
|
||||||
|
h="100%"
|
||||||
|
color="widgetTextMuted"
|
||||||
|
>
|
||||||
|
{object?.alert_start_date_local}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
w="100%"
|
||||||
|
h="100%"
|
||||||
|
color={
|
||||||
|
alertColors[object?.alert_level_name] ||
|
||||||
|
"text"
|
||||||
|
}
|
||||||
|
fontSize="md"
|
||||||
|
fontWeight="semibold"
|
||||||
|
>
|
||||||
|
{object?.alert_level_name}
|
||||||
|
</Text>
|
||||||
|
<Flex
|
||||||
|
w="100%"
|
||||||
|
h="20px"
|
||||||
|
direction="row"
|
||||||
|
justify="left"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<Text color="widgetTextMuted">
|
||||||
|
Alert Scale:{" "}
|
||||||
|
</Text>
|
||||||
|
{Array.from(
|
||||||
|
{
|
||||||
|
length:
|
||||||
|
Number(object?.alert_scheme_highest) -
|
||||||
|
Number(object?.alert_scheme_lowest) +
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
(v, i) =>
|
||||||
|
Number(object?.alert_scheme_lowest) + i,
|
||||||
|
).map((num) => (
|
||||||
<Center
|
<Center
|
||||||
key={num}
|
key={num}
|
||||||
w="20px" h="100%"
|
w="20px"
|
||||||
|
h="100%"
|
||||||
color="text"
|
color="text"
|
||||||
bg={num === Number(object?.alert_level_rank) ? "widgetBadgeBg" : "transparent"}
|
bg={
|
||||||
borderRadius={num === Number(object?.alert_level_rank) ? "xl" : "none"}
|
num ===
|
||||||
|
Number(object?.alert_level_rank)
|
||||||
|
? "widgetBadgeBg"
|
||||||
|
: "transparent"
|
||||||
|
}
|
||||||
|
borderRadius={
|
||||||
|
num ===
|
||||||
|
Number(object?.alert_level_rank)
|
||||||
|
? "xl"
|
||||||
|
: "none"
|
||||||
|
}
|
||||||
textAlign="center"
|
textAlign="center"
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
>
|
>
|
||||||
{num}
|
{num}
|
||||||
</Center>
|
</Center>
|
||||||
))
|
))}
|
||||||
}
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Timeline.Description>
|
||||||
</Timeline.Description>
|
</Timeline.Content>
|
||||||
</Timeline.Content>
|
</Timeline.Item>
|
||||||
</Timeline.Item>
|
))}
|
||||||
))}
|
</Timeline.Root>
|
||||||
</Timeline.Root>
|
</Collapsible.Content>
|
||||||
</Collapsible.Content>
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
</Collapsible.Context>
|
||||||
</Collapsible.Context>
|
</Collapsible.Root>
|
||||||
</Collapsible.Root>
|
);
|
||||||
)
|
else if (thisWidget?.size === "small")
|
||||||
else if (thisWidget?.size === "small") return (
|
return volcano[1]?.map((domain, index) => (
|
||||||
volcano[1]?.map((domain, index) => (
|
|
||||||
<Flex
|
<Flex
|
||||||
key={index}
|
key={index}
|
||||||
direction="row"
|
direction="row"
|
||||||
w="100%"
|
w="100%"
|
||||||
gap={2}
|
gap={2}
|
||||||
justify="space-between" align="center"
|
justify="space-between"
|
||||||
p={1} pl={3} pr={3}
|
align="center"
|
||||||
|
p={1}
|
||||||
|
pl={3}
|
||||||
|
pr={3}
|
||||||
borderRadius="lg"
|
borderRadius="lg"
|
||||||
bg="widgetItemBg"
|
bg="widgetItemBg"
|
||||||
>
|
>
|
||||||
<Flex direction="column" w="100%" minW={0} textAlign="left" color="text">
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
w="100%"
|
||||||
|
minW={0}
|
||||||
|
textAlign="left"
|
||||||
|
color="text"
|
||||||
|
>
|
||||||
<Flex direction="row" w="100%" gap={2} align="center">
|
<Flex direction="row" w="100%" gap={2} align="center">
|
||||||
<Flex color="iconColor">
|
<Flex color="iconColor">
|
||||||
{domain[0] === "aviation" && <Plane size={15}/>}
|
{domain[0] === "aviation" && <Plane size={15} />}
|
||||||
{domain[0] === "terrestrial" && <Footprints size={15}/>}
|
{domain[0] === "terrestrial" && <Footprints size={15} />}
|
||||||
{domain[0] === "maritime" && <Sailboat size={15}/>}
|
{domain[0] === "maritime" && <Sailboat size={15} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Text truncate fontWeight="medium">{volcano[0] || ""}</Text>
|
<Text truncate fontWeight="medium">
|
||||||
|
{volcano[0] || ""}
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Text truncate w="100%" fontSize="xs" color="widgetTextMuted">{domain[1]?.[0]?.alert_start_date_local || ""}</Text>
|
<Text truncate w="100%" fontSize="xs" color="widgetTextMuted">
|
||||||
|
{domain[1]?.[0]?.alert_start_date_local || ""}
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex direction="row" w="100%" minW={0} justify="right" gap={2} color="text">
|
<Flex
|
||||||
|
direction="row"
|
||||||
|
w="100%"
|
||||||
|
minW={0}
|
||||||
|
justify="right"
|
||||||
|
gap={2}
|
||||||
|
color="text"
|
||||||
|
>
|
||||||
<Flex w="25px" justify="right" color="iconColor">
|
<Flex w="25px" justify="right" color="iconColor">
|
||||||
{domain[1]?.[0]?.change_type === "increase" ? <TrendingUp/> : <TrendingDown/>}
|
{domain[1]?.[0]?.change_type === "increase" ? (
|
||||||
|
<TrendingUp />
|
||||||
|
) : (
|
||||||
|
<TrendingDown />
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Text
|
<Text
|
||||||
truncate
|
truncate
|
||||||
pl={2} pr={2}
|
pl={2}
|
||||||
|
pr={2}
|
||||||
borderRadius="lg"
|
borderRadius="lg"
|
||||||
bg="widgetBadgeBg"
|
bg="widgetBadgeBg"
|
||||||
color={alertColors[domain[1]?.[0]?.alert_level_name] || "text"}
|
color={
|
||||||
|
alertColors[domain[1]?.[0]?.alert_level_name] || "text"
|
||||||
|
}
|
||||||
fontWeight="semibold"
|
fontWeight="semibold"
|
||||||
>
|
>
|
||||||
{domain[1]?.[0]?.alert_level_name}
|
{domain[1]?.[0]?.alert_level_name}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
))
|
));
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,127 @@
|
|||||||
import {Flex, Slider, Switch} from "@chakra-ui/react";
|
import {Flex, Slider, Switch, Text, Field, Fieldset, Input, CheckboxCard, CheckboxGroup} from "@chakra-ui/react";
|
||||||
import {useDashboardStore} from "@/store/useDashboardStore.jsx";
|
import {useDashboardStore} from "@/store/useDashboardStore.jsx";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function IvansAlertsSettings({ widgetId, currentSettings }) {
|
export default function IvansAlertsSettings({ widgetId, currentSettings }) {
|
||||||
const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
|
const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
|
||||||
|
const [startError, setStartError] = useState(null);
|
||||||
|
const [endError, setEndError] = useState(null);
|
||||||
|
|
||||||
console.log("currentSettings:", currentSettings);
|
console.log("currentSettings:", currentSettings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection="column" justifyContent="space-between" align="center" gap={2} pb={2}>
|
<Flex direction="column" gap={4} pb={2}>
|
||||||
{/* SLIDER DATE RANGE SELECTION */}
|
<Fieldset.Root>
|
||||||
<Slider.Root
|
<Fieldset.Content>
|
||||||
w="90%" h="100%"
|
{/* SELECTING DATE RANGE */}
|
||||||
min={1} max={14}
|
<Flex direction="column" w="100%" h="100%" justify="center" gap="2">
|
||||||
defaultValue={[1]}
|
<Flex direction="column">
|
||||||
step={1}
|
<Text w="100%" fontWeight="bold">Date Range Selection</Text>
|
||||||
onValueChangeEnd={(e) => {
|
<hr /> {/*line seperator*/}
|
||||||
console.log("change end", e);
|
</Flex>
|
||||||
const day = new Date(Date.now() - (e.value[0])*86400000).toLocaleDateString('en-CA');
|
|
||||||
updateSettings(widgetId, {...currentSettings, startDate: day});
|
{/* start date */}
|
||||||
}}
|
<Field.Root invalid={!!startError}>
|
||||||
>
|
<Flex direction="row" w="100%" h="100%" align="center" justify="center">
|
||||||
<Slider.Label>{currentSettings?.startDate} to {currentSettings?.endDate}</Slider.Label>
|
<Text w="50%" align="center" justify="center" color="gray.700">Start Date</Text>
|
||||||
<Slider.Control>
|
<Flex direction="column" w="100%">
|
||||||
<Slider.Track>
|
<Input type="date" w="100%" borderRadius="lg" value={currentSettings?.startDate || ""}
|
||||||
<Slider.Range />
|
onChange={(e) => {
|
||||||
</Slider.Track>
|
const value = e.target.value;
|
||||||
<Slider.Thumbs />
|
updateSettings(widgetId, {...currentSettings, startDate: value});
|
||||||
<Slider.MarkerGroup p={3}>
|
|
||||||
<Slider.Marker value={1} whiteSpace="nowrap">today</Slider.Marker>
|
const dateSelected = new Date(value);
|
||||||
<Slider.Marker value={7} whiteSpace="nowrap">1 week ago</Slider.Marker>
|
const today = new Date();
|
||||||
<Slider.Marker value={14} whiteSpace="nowrap">2 weeks ago</Slider.Marker>
|
today.setHours(0, 0, 0, 0);
|
||||||
</Slider.MarkerGroup>
|
|
||||||
</Slider.Control>
|
if (value && (dateSelected > today)) setStartError("Can't Choose a Future Date");
|
||||||
</Slider.Root>
|
else setStartError("");
|
||||||
</Flex>
|
}}
|
||||||
|
/>
|
||||||
|
{startError && <Field.ErrorText>{startError}</Field.ErrorText>}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Field.Root>
|
||||||
|
|
||||||
|
{/* end date */}
|
||||||
|
<Field.Root invalid={!!endError}>
|
||||||
|
<Flex direction="row" w="100%" h="100%" align="center" justify="center">
|
||||||
|
<Text w="50%" align="center" justify="center" color="gray.700">End Date</Text>
|
||||||
|
<Flex direction="column" w="100%">
|
||||||
|
<Input type="date" w="100%" borderRadius="lg" value={currentSettings?.endDate || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
updateSettings(widgetId, {...currentSettings, endDate: value});
|
||||||
|
|
||||||
|
const dateSelected = new Date(value);
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (value && (dateSelected > today)) setEndError("Can't Choose a Future Date");
|
||||||
|
else setEndError("");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{endError && <Field.ErrorText>{endError}</Field.ErrorText>}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Field.Root>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* FILTER ON CHANGE TYPE */}
|
||||||
|
<CheckboxGroup w="100%">
|
||||||
|
<Flex direction="column" w="100%" gap={2}>
|
||||||
|
<Flex direction="column" w="100%">
|
||||||
|
<Text w="fit" fontWeight="bold">Filters</Text>
|
||||||
|
<hr /> {/*line seperator*/}
|
||||||
|
</Flex>
|
||||||
|
<CheckboxCard.Root size="sm" variant="surface" borderRadius="lg"
|
||||||
|
defaultChecked={currentSettings?.filters?.levelIncreases}
|
||||||
|
onCheckedChange={e => {
|
||||||
|
updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, levelIncreases: e.checked}})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckboxCard.HiddenInput /> {/* when removed can't check/uncheck box */}
|
||||||
|
<CheckboxCard.Control>
|
||||||
|
<CheckboxCard.Content>
|
||||||
|
<CheckboxCard.Label fontWeight={400}>Level Increases</CheckboxCard.Label>
|
||||||
|
</CheckboxCard.Content>
|
||||||
|
<CheckboxCard.Indicator />
|
||||||
|
</CheckboxCard.Control>
|
||||||
|
</CheckboxCard.Root>
|
||||||
|
|
||||||
|
<CheckboxCard.Root size="sm" variant="surface" borderRadius="lg"
|
||||||
|
defaultChecked={currentSettings?.filters?.levelDecreases}
|
||||||
|
onCheckedChange={e => {
|
||||||
|
updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, levelDecreases: e.checked}})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckboxCard.HiddenInput />
|
||||||
|
<CheckboxCard.Control>
|
||||||
|
<CheckboxCard.Content>
|
||||||
|
<CheckboxCard.Label fontWeight={400}>Level Decreases</CheckboxCard.Label>
|
||||||
|
</CheckboxCard.Content>
|
||||||
|
<CheckboxCard.Indicator />
|
||||||
|
</CheckboxCard.Control>
|
||||||
|
</CheckboxCard.Root>
|
||||||
|
|
||||||
|
<CheckboxCard.Root size="sm" variant="surface" borderRadius="lg"
|
||||||
|
defaultChecked={currentSettings?.filters?.elevatedLevels}
|
||||||
|
onCheckedChange={e => {
|
||||||
|
updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, elevatedLevels: e.checked}})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckboxCard.HiddenInput />
|
||||||
|
<CheckboxCard.Control>
|
||||||
|
<CheckboxCard.Content>
|
||||||
|
<CheckboxCard.Label fontWeight={400}>Only Elevated Levels</CheckboxCard.Label>
|
||||||
|
</CheckboxCard.Content>
|
||||||
|
<CheckboxCard.Indicator />
|
||||||
|
</CheckboxCard.Control>
|
||||||
|
</CheckboxCard.Root>
|
||||||
|
</Flex>
|
||||||
|
</CheckboxGroup>
|
||||||
|
</Fieldset.Content>
|
||||||
|
</Fieldset.Root>
|
||||||
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -99,6 +99,11 @@ export const WIDGET_REGISTRY = {
|
|||||||
defaultSettings: {
|
defaultSettings: {
|
||||||
startDate: getStartDate(),
|
startDate: getStartDate(),
|
||||||
endDate: getEndDate(),
|
endDate: getEndDate(),
|
||||||
|
filters: {
|
||||||
|
levelIncreases: true,
|
||||||
|
levelDecreases: true,
|
||||||
|
elevatedLevels: false, // will filter to show only volcanoes in their 2nd highest to highest al
|
||||||
|
},
|
||||||
},
|
},
|
||||||
allowedSizes: {
|
allowedSizes: {
|
||||||
small: { w: 3, h: 6 },
|
small: { w: 3, h: 6 },
|
||||||
|
|||||||
+1
-1
@@ -353,7 +353,7 @@ const glassPopoverRecipe = defineSlotRecipe({
|
|||||||
borderRadius: "xl",
|
borderRadius: "xl",
|
||||||
p: 3,
|
p: 3,
|
||||||
boxShadow: "lg",
|
boxShadow: "lg",
|
||||||
bg: {base: "rgba(255, 255, 255, 0.85)", _dark: "rgba(20, 22, 25, 0.85)"},
|
bg: {base: "rgba(255, 255, 255, 0.95)", _dark: "rgba(20, 22, 25, 0.95)"},
|
||||||
backdropFilter: "blur(15px)",
|
backdropFilter: "blur(15px)",
|
||||||
WebkitBackdropFilter: "blur(15px)",
|
WebkitBackdropFilter: "blur(15px)",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user