diff --git a/client/src/components/Widgets/AlertChanges.jsx b/client/src/components/Widgets/AlertChanges.jsx
index d448b46..497e008 100644
--- a/client/src/components/Widgets/AlertChanges.jsx
+++ b/client/src/components/Widgets/AlertChanges.jsx
@@ -1,28 +1,51 @@
-import {useDashboardStore} from "@/store/useDashboardStore.jsx";
+import { useDashboardStore } from "@/store/useDashboardStore.jsx";
import VdapApi from "../../hooks/useVdapApi.js";
import useBuildUrl from "../../hooks/useBuildUrl.js";
-import { Footprints, Plane, Sailboat, TrendingUp, TrendingDown } from 'lucide-react';
-import {Box, Collapsible, Flex, Spinner, Text, Timeline, Center} from "@chakra-ui/react";
+import {
+ 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";
+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}) {
- // Find 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]
- ));
+ // 1) BUILD URL, default is 7 days past
+ const url = useBuildUrl(
+ "/api/v2/alert_changes",
+ settings?.startDate,
+ settings?.endDate,
+ );
- // build url, default is 7 days past
- const url = useBuildUrl('/api/v2/alert_changes', settings?.startDate, settings?.endDate);
+ // 2) CALL API hook to retrieve data
+ const { data: apiData, isLoading, error } = VdapApi(url);
- // call api hook to retrieve data
- const {data: apiData, isLoading, error} = VdapApi(url);
-
- const data = useMemo(() => {
+ // 3) SORT the data by grouping into volcanoes and sorting form newest to oldest
+ // 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 sortedData = useMemo(() => {
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)
- // 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) => {
if (!accumulator[item.volcano]) accumulator[item.volcano] = [];
accumulator[item.volcano].push(item);
@@ -33,33 +56,97 @@ export default function AlertChanges({ settings, widgetID}) {
const group_arr = Object.entries(grouped);
// sort data newest to oldest within each volcano
- const sorted = group_arr.map((item) =>
- [
- item[0], // volcano name
- [...(item[1])].sort((a, b) =>
- new Date(b.alert_start_date_local) - new Date(a.alert_start_date_local))
- ]
+ const sorted = group_arr.map((item) => [
+ item[0], // volcano name
+ [...item[1]].sort( // item[1] is the array of al changes for a volcano
+ (a, b) =>
+ 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
- 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));
-
+ // additionally, seperates out data into domains within a volcano for sm widgets,
+ // to list all most recent al changes w/in a volcano w/in a domain
if (thisWidget?.size === "small") {
- return (sorted_entries.map((volcano) => {
+ return sorted_entries.map((volcano) => {
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);
return acc;
}, {});
const domain_arr = Object.entries(domain);
+ console.log("sm data: ", [volcano[0], domain_arr]);
return [volcano[0], domain_arr];
- }));
+ });
}
+ console.log("lg data: ", sorted_entries);
return sorted_entries;
}, [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
const alertColors = {
Red: "alertRed",
@@ -68,149 +155,283 @@ export default function AlertChanges({ settings, widgetID}) {
Green: "alertGreen",
};
- if (isLoading) return (
-
-
-
- );
+ if (isLoading)
+ return (
+
+
+
+ );
- if (error) return (
-
- {error}
-
- );
+ if (error)
+ return (
+
+ {error}
+
+ );
- if (data.length === 0) return (
-
- No flight level data available for the selected dates.
-
- );
+ if (filteredData.length === 0)
+ return (
+
+
+ No alert level data available for the selected dates.
+
+
+ );
return (
- // bg="transparent" to let the glass panel show through
-
- {data.map((volcano, index) => {
- if (thisWidget?.size === "large") return (
-
-
- {(collapsible) => (
- <>
-
-
+ {filteredData.map((volcano, index) => {
+ if (thisWidget?.size === "large")
+ return (
+
+
+ {(collapsible) => (
+ <>
+
+
+
-
- {volcano[0]}
- {volcano[1][0]?.alert_start_date_local}
+ minW={0}
+ textAlign="left"
+ >
+
+ {volcano[0]}
+
+
+ {volcano[1][0]?.alert_start_date_local}
+
+
+
+
+ {volcano[1][0]?.change_type === "increase" ? (
+
+ ) : (
+
+ )}
+
+
+ {volcano[1][0]?.alert_level_name}
+
+
-
-
- {volcano[1][0]?.alert_level_name}
-
-
-
-
+
-
-
- {volcano[1]?.map((object, index) => (
-
-
-
- {object?.alert_scheme_domain === "terrestrial" && }
- {object?.alert_scheme_domain === "aviation" && }
- {object?.alert_scheme_domain === "maritime" && }
-
-
- {object?.change_type === "increase" ? : }
-
-
-
-
-
- {object?.alert_start_date_local}
-
- {object?.alert_level_name}
-
-
- Alert Scale:
- {(Array.from({ length: Number(object?.alert_scheme_highest) - Number(object?.alert_scheme_lowest) + 1}, (v, i) => Number(object?.alert_scheme_lowest) + i))
- .map((num) => (
+
+
+ {volcano[1]?.map((object, index) => (
+
+
+
+ {object?.alert_scheme_domain ===
+ "terrestrial" && }
+ {object?.alert_scheme_domain === "aviation" && (
+
+ )}
+ {object?.alert_scheme_domain === "maritime" && (
+
+ )}
+
+
+ {object?.change_type === "increase" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ {object?.alert_start_date_local}
+
+
+ {object?.alert_level_name}
+
+
+
+ Alert Scale:{" "}
+
+ {Array.from(
+ {
+ length:
+ Number(object?.alert_scheme_highest) -
+ Number(object?.alert_scheme_lowest) +
+ 1,
+ },
+ (v, i) =>
+ Number(object?.alert_scheme_lowest) + i,
+ ).map((num) => (
{num}
- ))
- }
+ ))}
+
-
-
-
-
- ))}
-
-
- >
- )}
-
-
- )
- else if (thisWidget?.size === "small") return (
- volcano[1]?.map((domain, index) => (
+
+
+
+ ))}
+
+
+ >
+ )}
+
+
+ );
+ else if (thisWidget?.size === "small")
+ return volcano[1]?.map((domain, index) => (
-
+
- {domain[0] === "aviation" && }
- {domain[0] === "terrestrial" && }
- {domain[0] === "maritime" && }
+ {domain[0] === "aviation" && }
+ {domain[0] === "terrestrial" && }
+ {domain[0] === "maritime" && }
- {volcano[0] || ""}
+
+ {volcano[0] || ""}
+
- {domain[1]?.[0]?.alert_start_date_local || ""}
+
+ {domain[1]?.[0]?.alert_start_date_local || ""}
+
-
+
- {domain[1]?.[0]?.change_type === "increase" ? : }
+ {domain[1]?.[0]?.change_type === "increase" ? (
+
+ ) : (
+
+ )}
{domain[1]?.[0]?.alert_level_name}
- ))
- );
+ ));
})}
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/client/src/components/Widgets/settings/AlertChangeSettings.jsx b/client/src/components/Widgets/settings/AlertChangeSettings.jsx
index b4b041f..9b4d4cc 100644
--- a/client/src/components/Widgets/settings/AlertChangeSettings.jsx
+++ b/client/src/components/Widgets/settings/AlertChangeSettings.jsx
@@ -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 { useState } from "react";
export default function IvansAlertsSettings({ widgetId, currentSettings }) {
const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
+ const [startError, setStartError] = useState(null);
+ const [endError, setEndError] = useState(null);
console.log("currentSettings:", currentSettings);
return (
-
- {/* SLIDER DATE RANGE SELECTION */}
- {
- console.log("change end", e);
- const day = new Date(Date.now() - (e.value[0])*86400000).toLocaleDateString('en-CA');
- updateSettings(widgetId, {...currentSettings, startDate: day});
- }}
- >
- {currentSettings?.startDate} to {currentSettings?.endDate}
-
-
-
-
-
-
- today
- 1 week ago
- 2 weeks ago
-
-
-
-
+
+
+
+ {/* SELECTING DATE RANGE */}
+
+
+ Date Range Selection
+
{/*line seperator*/}
+
+
+ {/* start date */}
+
+
+ Start Date
+
+ {
+ const value = e.target.value;
+ updateSettings(widgetId, {...currentSettings, startDate: value});
+
+ const dateSelected = new Date(value);
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+
+ if (value && (dateSelected > today)) setStartError("Can't Choose a Future Date");
+ else setStartError("");
+ }}
+ />
+ {startError && {startError}}
+
+
+
+
+ {/* end date */}
+
+
+ End Date
+
+ {
+ 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 && {endError}}
+
+
+
+
+
+ {/* FILTER ON CHANGE TYPE */}
+
+
+
+ Filters
+
{/*line seperator*/}
+
+ {
+ updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, levelIncreases: e.checked}})
+ }}
+ >
+ {/* when removed can't check/uncheck box */}
+
+
+ Level Increases
+
+
+
+
+
+ {
+ updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, levelDecreases: e.checked}})
+ }}
+ >
+
+
+
+ Level Decreases
+
+
+
+
+
+ {
+ updateSettings(widgetId, {...currentSettings, filters: {...currentSettings.filters, elevatedLevels: e.checked}})
+ }}
+ >
+
+
+
+ Only Elevated Levels
+
+
+
+
+
+
+
+
+
)
}
\ No newline at end of file
diff --git a/client/src/constants/widgetRegistry.jsx b/client/src/constants/widgetRegistry.jsx
index 93919fa..79a9c19 100644
--- a/client/src/constants/widgetRegistry.jsx
+++ b/client/src/constants/widgetRegistry.jsx
@@ -99,6 +99,11 @@ export const WIDGET_REGISTRY = {
defaultSettings: {
startDate: getStartDate(),
endDate: getEndDate(),
+ filters: {
+ levelIncreases: true,
+ levelDecreases: true,
+ elevatedLevels: false, // will filter to show only volcanoes in their 2nd highest to highest al
+ },
},
allowedSizes: {
small: { w: 3, h: 6 },
diff --git a/client/src/theme.js b/client/src/theme.js
index 2c50ce3..f1ff888 100644
--- a/client/src/theme.js
+++ b/client/src/theme.js
@@ -353,7 +353,7 @@ const glassPopoverRecipe = defineSlotRecipe({
borderRadius: "xl",
p: 3,
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)",
WebkitBackdropFilter: "blur(15px)",
},