add: volcano drop down filter w/ multiselect capabilities in widget settings. UI only! No functionality has been built yet

This commit is contained in:
2026-11-05 15:39:27 -08:00
parent 189d6e2d1d
commit e50638ab55
3 changed files with 369 additions and 194 deletions
+170 -158
View File
@@ -155,6 +155,16 @@ export default function AlertChanges({ settings, widgetID }) {
Green: "alertGreen", Green: "alertGreen",
}; };
const options = {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short"
};
if (isLoading) if (isLoading)
return ( return (
<Flex w="100%" h="100%" align="center" justify="center" bg="transparent"> <Flex w="100%" h="100%" align="center" justify="center" bg="transparent">
@@ -195,172 +205,174 @@ export default function AlertChanges({ settings, widgetID }) {
<Collapsible.Root key={index} w="100%"> <Collapsible.Root key={index} w="100%">
<Collapsible.Context> <Collapsible.Context>
{(collapsible) => ( {(collapsible) => (
<> <Collapsible.Trigger w="100%">
<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 <Flex
direction="row" direction="column"
w="100%" w="100%"
justify="space-between" minW={0}
align="center" textAlign="left"
p={1}
pl={3}
pr={3}
borderTopRadius="lg"
borderBottomRadius={collapsible.open ? "0" : "lg"}
bg="widgetItemBg"
> >
<Flex <Text truncate color="text" fontWeight="medium">
direction="column" {volcano[0]}
w="100%" </Text>
minW={0} <Text truncate fontSize="xs" color="widgetTextMuted">
textAlign="left" {new Date(volcano[1][0]?.alert_start_date_local)
.toLocaleString('en-US', options)
}
</Text>
</Flex>
<Flex w="100%" minW={0} justify="right" gap={2}>
<Text
truncate
pl={2}
pr={2}
borderRadius="lg"
bg="widgetBadgeBg"
color="text"
fontWeight="semibold"
> >
<Text truncate color="text" fontWeight="medium"> {volcano[1][0]?.alert_level_name}
{volcano[0]} </Text>
</Text> <Flex w="fit">
<Text truncate fontSize="xs" color="widgetTextMuted"> {volcano[1][0]?.change_type === "increase" ? (
{volcano[1][0]?.alert_start_date_local} <TrendingUp />
</Text> ) : (
</Flex> <TrendingDown />
<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>
</Collapsible.Trigger> </Flex>
</Collapsible.Trigger>
<Collapsible.Content
w="100%"
p={3}
borderBottomRadius="lg"
bg="widgetItemExpandedBg"
>
<Timeline.Root size="md" variant="subtle">
{volcano[1]?.map((object, index) => (
<Timeline.Item key={index} w="100%" h="100%">
<Timeline.Connector>
<Timeline.Indicator
outline="none"
bg="widgetBadgeBg"
color="iconColor"
size="lg"
>
{object?.alert_scheme_domain ===
"terrestrial" && <Footprints size={15} />}
{object?.alert_scheme_domain === "aviation" && (
<Plane size={15} />
)}
{object?.alert_scheme_domain === "maritime" && (
<Sailboat size={15} />
)}
</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
key={num}
w="20px"
h="100%"
color="text"
bg={
num ===
Number(object?.alert_level_rank)
? "widgetBadgeBg"
: "transparent"
}
borderRadius={
num ===
Number(object?.alert_level_rank)
? "xl"
: "none"
}
textAlign="center"
fontSize="sm"
fontWeight="bold"
>
{num}
</Center>
))}
</Flex>
</Flex>
</Timeline.Description>
</Timeline.Content>
</Timeline.Item>
))}
</Timeline.Root>
</Collapsible.Content>
</>
)} )}
</Collapsible.Context> </Collapsible.Context>
<Collapsible.Content
w="100%"
p={3}
borderBottomRadius="lg"
bg="widgetItemExpandedBg"
>
<Timeline.Root size="md" variant="subtle">
{volcano[1]?.map((object, index) => (
<Timeline.Item key={index} w="100%" h="100%">
<Timeline.Connector>
<Timeline.Indicator
outline="none"
bg="widgetBadgeBg"
color="iconColor"
size="lg"
>
{object?.alert_scheme_domain ===
"terrestrial" && <Footprints size={15} />}
{object?.alert_scheme_domain === "aviation" && (
<Plane size={15} />
)}
{object?.alert_scheme_domain === "maritime" && (
<Sailboat size={15} />
)}
</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"
>
{new Date(object?.alert_start_date_local)
.toLocaleString('en-US', {...options, timeZone: 'UTC'})
}
</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
key={num}
w="20px"
h="100%"
color="text"
bg={
num ===
Number(object?.alert_level_rank)
? "widgetBadgeBg"
: "transparent"
}
borderRadius={
num ===
Number(object?.alert_level_rank)
? "xl"
: "none"
}
textAlign="center"
fontSize="sm"
fontWeight="bold"
>
{num}
</Center>
))}
</Flex>
</Flex>
</Timeline.Description>
</Timeline.Content>
</Timeline.Item>
))}
</Timeline.Root>
</Collapsible.Content>
</Collapsible.Root> </Collapsible.Root>
); );
else if (thisWidget?.size === "small") else if (thisWidget?.size === "small")
@@ -1,63 +1,130 @@
import {Flex, Slider, Switch, Text, Field, Fieldset, Input, CheckboxCard, CheckboxGroup} from "@chakra-ui/react"; import {
import {useDashboardStore} from "@/store/useDashboardStore.jsx"; Flex,
import { useState } from "react"; Slider,
Switch,
Text,
Field,
Fieldset,
Input,
CheckboxCard,
CheckboxGroup,
Combobox,
Portal,
useFilter,
useListCollection,
Wrap,
Badge
} from "@chakra-ui/react";
import { useDashboardStore } from "@/store/useDashboardStore.jsx";
import { useState, useMemo } from "react";
export default function IvansAlertsSettings({ widgetId, currentSettings }) { export default function AlertChangeSettings({ widgetId, currentSettings }) {
const updateSettings = useDashboardStore(state => state.updateWidgetSettings); const updateSettings = useDashboardStore(
(state) => state.updateWidgetSettings,
);
const [startError, setStartError] = useState(null); const [startError, setStartError] = useState(null);
const [endError, setEndError] = useState(null); const [endError, setEndError] = useState(null);
console.log("currentSettings:", currentSettings); console.log("currentSettings:", currentSettings);
const [selectedVolcanoes, setSelectedVolcanoes] = useState([]);
const { startsWith } = useFilter({ sensitivity: "base" });
const { collection, filter, reset } = useListCollection({
initialItems: volcanoes,
filter: startsWith,
limit: 10,
});
const handleValueChange = (details) => {
setSelectedVolcanoes(details.value)
}
return ( return (
<Flex direction="column" gap={4} pb={2}> <Flex direction="column" gap={4} pb={2} overflowY="auto">
<Fieldset.Root> <Fieldset.Root>
<Fieldset.Content> <Fieldset.Content>
{/* SELECTING DATE RANGE */} {/* SELECTING DATE RANGE */}
<Flex direction="column" w="100%" h="100%" justify="center" gap="2"> <Flex direction="column" w="100%" h="100%" justify="center" gap="2">
<Flex direction="column"> <Flex direction="column">
<Text w="100%" fontWeight="bold">Date Range Selection</Text> <Text w="100%" fontWeight="bold">
Date Range Selection
</Text>
<hr /> {/*line seperator*/} <hr /> {/*line seperator*/}
</Flex> </Flex>
{/* start date */} {/* start date */}
<Field.Root invalid={!!startError}> <Field.Root invalid={!!startError}>
<Flex direction="row" w="100%" h="100%" align="center" justify="center"> <Flex
<Text w="50%" align="center" justify="center" color="gray.700">Start Date</Text> direction="row"
w="100%"
h="100%"
align="center"
justify="center"
>
<Text w="50%" align="center" justify="center" color="gray.700">
Start Date
</Text>
<Flex direction="column" w="100%"> <Flex direction="column" w="100%">
<Input type="date" w="100%" borderRadius="lg" value={currentSettings?.startDate || ""} <Input
type="date"
w="100%"
borderRadius="lg"
value={currentSettings?.startDate || ""}
onChange={(e) => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
updateSettings(widgetId, {...currentSettings, startDate: value}); updateSettings(widgetId, {
...currentSettings,
startDate: value,
});
const dateSelected = new Date(value); const dateSelected = new Date(value);
const today = new Date(); const today = new Date();
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
if (value && (dateSelected > today)) setStartError("Can't Choose a Future Date"); if (value && dateSelected > today)
setStartError("Can't Choose a Future Date");
else setStartError(""); else setStartError("");
}} }}
/> />
{startError && <Field.ErrorText>{startError}</Field.ErrorText>} {startError && (
<Field.ErrorText>{startError}</Field.ErrorText>
)}
</Flex> </Flex>
</Flex> </Flex>
</Field.Root> </Field.Root>
{/* end date */} {/* end date */}
<Field.Root invalid={!!endError}> <Field.Root invalid={!!endError}>
<Flex direction="row" w="100%" h="100%" align="center" justify="center"> <Flex
<Text w="50%" align="center" justify="center" color="gray.700">End Date</Text> 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%"> <Flex direction="column" w="100%">
<Input type="date" w="100%" borderRadius="lg" value={currentSettings?.endDate || ""} <Input
type="date"
w="100%"
borderRadius="lg"
value={currentSettings?.endDate || ""}
onChange={(e) => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
updateSettings(widgetId, {...currentSettings, endDate: value}); updateSettings(widgetId, {
...currentSettings,
endDate: value,
});
const dateSelected = new Date(value); const dateSelected = new Date(value);
const today = new Date(); const today = new Date();
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
if (value && (dateSelected > today)) setEndError("Can't Choose a Future Date"); if (value && dateSelected > today)
setEndError("Can't Choose a Future Date");
else setEndError(""); else setEndError("");
}} }}
/> />
@@ -67,61 +134,156 @@ export default function IvansAlertsSettings({ widgetId, currentSettings }) {
</Field.Root> </Field.Root>
</Flex> </Flex>
{/* FILTER ON CHANGE TYPE */} {/* FILTERS */}
<CheckboxGroup w="100%"> <CheckboxGroup w="100%">
<Flex direction="column" w="100%" gap={2}> <Flex direction="column" w="100%" gap={2}>
<Flex direction="column" w="100%"> <Flex direction="column" w="100%">
<Text w="fit" fontWeight="bold">Filters</Text> <Text w="fit" fontWeight="bold">
Filters
</Text>
<hr /> {/*line seperator*/} <hr /> {/*line seperator*/}
</Flex> </Flex>
<CheckboxCard.Root size="sm" variant="surface" borderRadius="lg"
{/* level increases */}
<CheckboxCard.Root
size="sm"
variant="surface"
borderRadius="lg"
defaultChecked={currentSettings?.filters?.levelIncreases} defaultChecked={currentSettings?.filters?.levelIncreases}
onCheckedChange={e => { onCheckedChange={(e) => {
updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, levelIncreases: e.checked}}) updateSettings(widgetId, {
...currentSettings,
filters: {
...currentSettings.filters,
levelIncreases: e.checked,
},
});
}} }}
> >
<CheckboxCard.HiddenInput /> {/* when removed can't check/uncheck box */} <CheckboxCard.HiddenInput /> {/* when removed can't check/uncheck box */}
<CheckboxCard.Control> <CheckboxCard.Control>
<CheckboxCard.Content> <CheckboxCard.Content>
<CheckboxCard.Label fontWeight={400}>Level Increases</CheckboxCard.Label> <CheckboxCard.Label fontWeight={400}>
Level Increases
</CheckboxCard.Label>
</CheckboxCard.Content> </CheckboxCard.Content>
<CheckboxCard.Indicator /> <CheckboxCard.Indicator />
</CheckboxCard.Control> </CheckboxCard.Control>
</CheckboxCard.Root> </CheckboxCard.Root>
<CheckboxCard.Root size="sm" variant="surface" borderRadius="lg" {/* level decreases */}
<CheckboxCard.Root
size="sm"
variant="surface"
borderRadius="lg"
defaultChecked={currentSettings?.filters?.levelDecreases} defaultChecked={currentSettings?.filters?.levelDecreases}
onCheckedChange={e => { onCheckedChange={(e) => {
updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, levelDecreases: e.checked}}) updateSettings(widgetId, {
...currentSettings,
filters: {
...currentSettings.filters,
levelDecreases: e.checked,
},
});
}} }}
> >
<CheckboxCard.HiddenInput /> <CheckboxCard.HiddenInput />
<CheckboxCard.Control> <CheckboxCard.Control>
<CheckboxCard.Content> <CheckboxCard.Content>
<CheckboxCard.Label fontWeight={400}>Level Decreases</CheckboxCard.Label> <CheckboxCard.Label fontWeight={400}>
Level Decreases
</CheckboxCard.Label>
</CheckboxCard.Content> </CheckboxCard.Content>
<CheckboxCard.Indicator /> <CheckboxCard.Indicator />
</CheckboxCard.Control> </CheckboxCard.Control>
</CheckboxCard.Root> </CheckboxCard.Root>
<CheckboxCard.Root size="sm" variant="surface" borderRadius="lg" {/* elevated levels */}
<CheckboxCard.Root
size="sm"
variant="surface"
borderRadius="lg"
defaultChecked={currentSettings?.filters?.elevatedLevels} defaultChecked={currentSettings?.filters?.elevatedLevels}
onCheckedChange={e => { onCheckedChange={(e) => {
updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, elevatedLevels: e.checked}}) updateSettings(widgetId, {
...currentSettings,
filters: {
...currentSettings.filters,
elevatedLevels: e.checked,
},
});
}} }}
> >
<CheckboxCard.HiddenInput /> <CheckboxCard.HiddenInput />
<CheckboxCard.Control> <CheckboxCard.Control>
<CheckboxCard.Content> <CheckboxCard.Content>
<CheckboxCard.Label fontWeight={400}>Only Elevated Levels</CheckboxCard.Label> <CheckboxCard.Label fontWeight={400}>
Only Elevated Levels
</CheckboxCard.Label>
</CheckboxCard.Content> </CheckboxCard.Content>
<CheckboxCard.Indicator /> <CheckboxCard.Indicator />
</CheckboxCard.Control> </CheckboxCard.Control>
</CheckboxCard.Root> </CheckboxCard.Root>
</Flex> </Flex>
</CheckboxGroup> </CheckboxGroup>
{/* select volcano to filter on */}
<Combobox.Root
mt={1}
variant="subtle"
multiple
openOnClick
collection={collection}
onValueChange={handleValueChange}
onInputValueChange={(e) => filter(e.inputValue)}
>
<Wrap gap="2">
{selectedVolcanoes.map((volcano) => (
<Badge key={volcano}>{volcano}</Badge>
))}
</Wrap>
<Combobox.Label fontSize="xs" >Select Volcano (s)</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Type to search" borderRadius="lg"/>
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger onClick={reset} />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
{volcanoes.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator />
</Combobox.Item>
))}
<Combobox.Empty>No items found</Combobox.Empty>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
</Fieldset.Content> </Fieldset.Content>
</Fieldset.Root> </Fieldset.Root>
</Flex> </Flex>
) );
} }
const volcanoes = [
{ label: "Mt. St. Helens", value: "helens" },
{ label: "Mt. Adams", value: "adams" },
{ label: "Rainier", value: "rainier" },
{ label: "The Three Sisters", value: "three sisters" },
{ label: "Fuego", value: "fuego" },
{ label: "Laki Laki", value: "lakilaki" },
{ label: "Lewotobi", value: "lewotobi" },
{ label: "Karangatang", value: "karangatang" },
{ label: "Cotopaxi", value: "cotopaxi" },
{ label: "Merapi", value: "merapi" },
{ label: "Marapi", value: "marapi" },
{ label: "Kick 'Em Jenny", value: "kick em jenny"}
]
+2 -1
View File
@@ -102,7 +102,8 @@ export const WIDGET_REGISTRY = {
filters: { filters: {
levelIncreases: true, levelIncreases: true,
levelDecreases: true, levelDecreases: true,
elevatedLevels: false, // will filter to show only volcanoes in their 2nd highest to highest al elevatedLevels: false, // will filter to show only volcanoes in their top 2 highest alert levels
volcanoes: 'all', // filter on what volcanoes to display, defaults to 'all' instead of listing every volc
}, },
}, },
allowedSizes: { allowedSizes: {