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 { 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], ), ); // 1) 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); // 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 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); return accumulator; }, {}); // converts the 'grouped' object back into an array of [key, value] pairs 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( // 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), ); // 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) => { const domain = volcano[1].reduce((acc, curr) => { 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", Orange: "alertOrange", Yellow: "alertYellow", Green: "alertGreen", }; const options = { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", second: "2-digit", timeZoneName: "short" }; if (isLoading) return ( ); if (error) return ( {error} ); if (filteredData.length === 0) return ( No alert level data available for the selected dates. ); return ( {filteredData.map((volcano, index) => { if (thisWidget?.size === "large") return ( {(collapsible) => ( {volcano[0]} {new Date(volcano[1][0]?.alert_start_date_local) .toLocaleString('en-US', options) } {volcano[1][0]?.change_type === "increase" ? ( ) : ( )} {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" ? ( ) : ( )} {new Date(object?.alert_start_date_local) .toLocaleString('en-US', {...options, timeZone: 'UTC'}) } {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) => ( {domain[0] === "aviation" && } {domain[0] === "terrestrial" && } {domain[0] === "maritime" && } {volcano[0] || ""} {domain[1]?.[0]?.alert_start_date_local || ""} {domain[1]?.[0]?.change_type === "increase" ? ( ) : ( )} {domain[1]?.[0]?.alert_level_name} )); })}
); }