diff --git a/client/src/components/Widgets/AlertChanges.jsx b/client/src/components/Widgets/AlertChanges.jsx
index 42eea0d..0f50da8 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,153 +155,285 @@ export default function AlertChanges({ settings, widgetID}) {
Green: "alertGreen",
};
- if (isLoading) return (
-
-
-
- );
+ const options = {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ hour: "numeric",
+ minute: "2-digit",
+ second: "2-digit",
+ timeZoneName: "short"
+ };
- if (error) return (
-
- {error}
-
- );
+ if (isLoading)
+ return (
+
+
+
+ );
- if (data.length === 0) return (
-
- No flight level data available for the selected dates.
-
- );
+ if (error)
+ return (
+
+ {error}
+
+ );
+
+ 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}
+
+
+ {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" ? : }
-
-
-
-
-
- {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}
-
- ))
+
+
+ {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) => (
+
-
-
-
-
- ))}
-
-
- >
- )}
-
-
- )
- else if (thisWidget?.size === "small") return (
- volcano[1]?.map((domain, index) => (
+ borderRadius={
+ num === Number(object?.alert_level_rank)
+ ? "xl"
+ : "none"
+ }
+ textAlign="center"
+ fontSize="sm"
+ fontWeight="bold"
+ >
+ {num}
+
+ ))}
+
+
+
+
+
+ ))}
+
+
+
+ );
+ 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/CommCoorReport.jsx b/client/src/components/Widgets/CommCoorReport.jsx
index f156d2f..4cb2d88 100644
--- a/client/src/components/Widgets/CommCoorReport.jsx
+++ b/client/src/components/Widgets/CommCoorReport.jsx
@@ -114,23 +114,26 @@ export default function CommCoorReport({ settings, widgetID }) {
}
}, [settings, widgetID, updateSettings, apiData]);
- if (isLoading) return (
-
-
-
- );
+ if (isLoading)
+ return (
+
+
+
+ );
- if (error) return (
-
- {error}
-
- );
+ if (error)
+ return (
+
+ {error}
+
+ );
- if (!apiData) return (
-
- No data
-
- );
+ if (!apiData)
+ return (
+
+ No data
+
+ );
return (
diff --git a/client/src/components/Widgets/IvansAlerts.jsx b/client/src/components/Widgets/IvansAlerts.jsx
index f65db1f..d857f2d 100644
--- a/client/src/components/Widgets/IvansAlerts.jsx
+++ b/client/src/components/Widgets/IvansAlerts.jsx
@@ -1,4 +1,4 @@
-import {Button, Flex, Spinner, Text, Box, Collapsible} from "@chakra-ui/react";
+import {Button, Flex, Spinner, Text, Box, Collapsible, Highlight} from "@chakra-ui/react";
import {useEffect, useMemo, useCallback } from 'react';
import {useDashboardStore} from "@/store/useDashboardStore.jsx";
import VdapApi from '../../hooks/useVdapApi.js';
@@ -100,7 +100,7 @@ export default function IvansAlerts({ settings, widgetID }) {
bg="widgetItemBg"
>
- {item.volcano_name}
+ {item.volcano_name}
{item.send_time}
@@ -111,33 +111,32 @@ export default function IvansAlerts({ settings, widgetID }) {
{/* This box needs to be at h="100%" for the pdf toolbar to appear */}
-
+
+ Preparer: {item?.preparer}
{item?.summary}
- Preparer: {item?.preparer}
{item?.files?.map((file, index) => {
return (
-
-
-
- {file?.name}
-
-
- Open
-
-
+
+
+ File:
+
+
+ {file?.name}
+
+
diff --git a/client/src/components/Widgets/RemoteSensing.jsx b/client/src/components/Widgets/RemoteSensing.jsx
index 38ed592..a533b06 100644
--- a/client/src/components/Widgets/RemoteSensing.jsx
+++ b/client/src/components/Widgets/RemoteSensing.jsx
@@ -96,7 +96,7 @@ export default function RemoteSensing({ settings, widgetID }) {
{/* This box needs to be at h="100%" for the pdf toolbar to appear */}
-
+
{item?.summary}
Source: {item?.sender}
diff --git a/client/src/components/Widgets/settings/AlertChangeSettings.jsx b/client/src/components/Widgets/settings/AlertChangeSettings.jsx
index b4b041f..106aa21 100644
--- a/client/src/components/Widgets/settings/AlertChangeSettings.jsx
+++ b/client/src/components/Widgets/settings/AlertChangeSettings.jsx
@@ -1,38 +1,283 @@
-import {Flex, Slider, Switch} from "@chakra-ui/react";
-import {useDashboardStore} from "@/store/useDashboardStore.jsx";
+import {
+ Flex,
+ Slider,
+ Switch,
+ Text,
+ Field,
+ Fieldset,
+ Input,
+ CheckboxCard,
+ CheckboxGroup,
+ Combobox,
+ Portal,
+ useFilter,
+ useListCollection,
+ Wrap,
+ Badge, Separator, HStack, useSlotRecipe
+} from "@chakra-ui/react";
+import { useDashboardStore } from "@/store/useDashboardStore.jsx";
+import { useState, useMemo } from "react";
-export default function IvansAlertsSettings({ widgetId, currentSettings }) {
- const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
+export default function AlertChangeSettings({ widgetId, currentSettings }) {
+ // STYLING
+ const popoverRecipe = useSlotRecipe({ key: "glassPopover" });
+ const popoverStyles = popoverRecipe();
- console.log("currentSettings:", currentSettings);
+ // STATE MANAGEMENT
+ const updateSettings = useDashboardStore(
+ (state) => state.updateWidgetSettings,
+ );
+ const [startError, setStartError] = useState(null);
+ const [endError, setEndError] = useState(null);
+ const [selectedVolcanoes, setSelectedVolcanoes] = useState([]);
+ const { startsWith } = useFilter({ sensitivity: "base" });
+
+ const { collection, filter, reset } = useListCollection({
+ initialItems: volcanoes,
+ filter: startsWith,
+ limit: 10,
+ });
+
+ // FUNCTIONS
+ const handleValueChange = (details) => {
+ setSelectedVolcanoes(details.value)
+ }
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}}
+
+
+
+
+
+ {/* FILTERS */}
+
+
+
+ Filters
+ {/*line seperator*/}
+
+
+ {/* level increases */}
+ {
+ updateSettings(widgetId, {
+ ...currentSettings,
+ filters: {
+ ...currentSettings.filters,
+ levelIncreases: e.checked,
+ },
+ });
+ }}
+ >
+ {/* when removed can't check/uncheck box */}
+
+
+
+ Level Increases
+
+
+
+
+
+
+ {/* level decreases */}
+ {
+ updateSettings(widgetId, {
+ ...currentSettings,
+ filters: {
+ ...currentSettings.filters,
+ levelDecreases: e.checked,
+ },
+ });
+ }}
+ >
+
+
+
+
+ Level Decreases
+
+
+
+
+
+
+ {/* elevated levels */}
+ {
+ updateSettings(widgetId, {
+ ...currentSettings,
+ filters: {
+ ...currentSettings.filters,
+ elevatedLevels: e.checked,
+ },
+ });
+ }}
+ >
+
+
+
+
+ Only Elevated Levels
+
+
+
+
+
+
+
+
+ {/* select volcano to filter on */}
+ filter(e.inputValue)}
+ >
+
+
+ {selectedVolcanoes.map((volcano) => (
+ {volcano}
+ ))}
+
+
+ Select Volcano (s)
+
+
+
+
+
+
+
+
+
+
+
+ {volcanoes.map((item) => (
+
+ {item.label}
+
+
+ ))}
+ No items found
+
+
+
+
+
+
- )
-}
\ No newline at end of file
+ );
+}
+
+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"}
+]
\ No newline at end of file
diff --git a/client/src/components/Widgets/settings/CommCoorReportSettings.jsx b/client/src/components/Widgets/settings/CommCoorReportSettings.jsx
index 3edf232..ff82e98 100644
--- a/client/src/components/Widgets/settings/CommCoorReportSettings.jsx
+++ b/client/src/components/Widgets/settings/CommCoorReportSettings.jsx
@@ -1,12 +1,18 @@
-import {Button, Field, Flex, Input, Switch, Text} from "@chakra-ui/react";
+import {Button, Field, Flex, Input, Switch, Text, Fieldset, Separator, HStack, useSlotRecipe} from "@chakra-ui/react";
import {useDashboardStore} from "@/store/useDashboardStore.jsx";
import {useState} from "react";
export default function CommCoorReportSettings({ widgetId, currentSettings }) {
+ // STYLING
+ const popoverRecipe = useSlotRecipe({ key: "glassPopover" });
+ const popoverStyles = popoverRecipe();
+
+ // STATE MANAGEMENT
const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
const [startError, setStartError] = useState(null);
const [endError, setEndError] = useState(null);
+ // FUNCTIONS
const handleCopyToClipboard = () => {
updateSettings(widgetId, {copyToClipboard: true});
}
@@ -17,67 +23,84 @@ export default function CommCoorReportSettings({ widgetId, currentSettings }) {
return (
- {/* COPY TO CLIPBOARD */}
-
+
+
+ {/* SELECTING DATE RANGE */}
+
+ Date Range Selection
+ {/*line seperator*/}
+
+
+ {/* start date */}
+
+
+
+ Start Date
+
+
+ {
+ const value = e.target.value;
+ updateSettings(widgetId, {...currentSettings, startDate: value});
- {/* DOWNLOAD REPORT */}
-
+ const dateSelected = new Date(value);
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
- {/* SELECTING DATE RANGE */}
-
-
-
-
- Start Date
-
-
- {
- const value = e.target.value;
- updateSettings(widgetId, {...currentSettings, startDate: value});
+ 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);
+ 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}}
-
+ if (value && (dateSelected > today)) setEndError("Can't Choose a Future Date");
+ else setEndError("");
+ }}
+ />
+ {endError && {endError}}
+
+
+
-
-
-
-
- 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}}
-
+
+ Tools
+ {/*line seperator*/}
+
+
+ {/* COPY TO CLIPBOARD */}
+
+ {/* DOWNLOAD REPORT */}
+
-
-
+
+
)
}
\ No newline at end of file
diff --git a/client/src/components/Widgets/settings/IvansAlertsSettings.jsx b/client/src/components/Widgets/settings/IvansAlertsSettings.jsx
index a4915f7..2b3939d 100644
--- a/client/src/components/Widgets/settings/IvansAlertsSettings.jsx
+++ b/client/src/components/Widgets/settings/IvansAlertsSettings.jsx
@@ -1,12 +1,30 @@
-import {Button, Field, Flex, Input, Switch, Text} from "@chakra-ui/react";
+import {
+ Button,
+ Field,
+ Flex,
+ Input,
+ Switch,
+ Text,
+ Fieldset,
+ CheckboxCard,
+ Separator,
+ HStack,
+ useSlotRecipe
+} from "@chakra-ui/react";
import {useDashboardStore} from "../../../store/useDashboardStore.jsx";
import {useState} from "react";
export default function IvansAlertsSettings({ widgetId, currentSettings }) {
+ // STYLING
+ const popoverRecipe = useSlotRecipe({ key: "glassPopover" });
+ const popoverStyles = popoverRecipe();
+
+ // STATE MANAGEMENT
const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
const [startError, setStartError] = useState(null);
const [endError, setEndError] = useState(null);
+ // FUNCTIONS
const handleCopyToClipboard = () => {
updateSettings(widgetId, {copyToClipboard: true});
}
@@ -20,89 +38,174 @@ export default function IvansAlertsSettings({ widgetId, currentSettings }) {
}
return (
-
- {/* COPY TO CLIPBOARD */}
-
-
- {/* SORTS */}
-
-
-
-
-
- Sort Oldest to Newest
-
-
-
-
-
-
- Sort by Volcano
-
-
- {/* SELECTING DATE RANGE */}
-
-
-
-
- Start Date
-
-
-
+
+
+ {/* SELECTING DATE RANGE */}
+
+ Date Range Selection
+ {/*line seperator*/}
+
+
+ {/* start date */}
+
+ {
- const value = e.target.value;
- updateSettings(widgetId, {...currentSettings, startDate: value});
+ h="100%"
+ align="center"
+ justify="center"
+ >
+
+ 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);
+ 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
-
-
- today)
+ setStartError("Can't Choose a Future Date");
+ else setStartError("");
+ }}
+ />
+ {startError && (
+ {startError}
+ )}
+
+
+
+
+ {/* end date */}
+
+ {
- const value = e.target.value;
- updateSettings(widgetId, {...currentSettings, endDate: value});
+ h="100%"
+ align="center"
+ justify="center"
+ >
+
+ 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);
+ 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}}
-
+ if (value && dateSelected > today)
+ setEndError("Can't Choose a Future Date");
+ else setEndError("");
+ }}
+ />
+ {endError && {endError}}
+
+
+
-
-
+
+ {/* SORTS */}
+
+ Sorts
+ {/*line seperator*/}
+
+
+ {/* time sort */}
+
+
+
+
+
+ Oldest to Newest
+
+
+ {/* alphabetic sort */}
+
+
+
+
+
+ Alphabetically by Volcano
+
+
+
+ Filters
+ {/*line seperator*/}
+
+
+ {/* elevated levels */}
+ {
+ updateSettings(widgetId, {
+ ...currentSettings,
+ filters: {
+ ...currentSettings.filters,
+ elevatedLevels: e.checked,
+ },
+ });
+ }}
+ >
+
+
+
+
+ Only Elevated Levels
+
+
+
+
+
+
+ {/* COPY TO CLIPBOARD */}
+
+ Tools
+ {/*line seperator*/}
+
+
+
+
)
-}
\ No newline at end of file
+}
diff --git a/client/src/components/Widgets/settings/RemoteSensingSettings.jsx b/client/src/components/Widgets/settings/RemoteSensingSettings.jsx
index 15ebb7e..d08bf73 100644
--- a/client/src/components/Widgets/settings/RemoteSensingSettings.jsx
+++ b/client/src/components/Widgets/settings/RemoteSensingSettings.jsx
@@ -1,12 +1,18 @@
-import {Button, Field, Flex, Input, Switch, Text} from "@chakra-ui/react";
+import {Button, Field, Flex, HStack, Input, Separator, Switch, Text, useSlotRecipe, Fieldset} from "@chakra-ui/react";
import {useDashboardStore} from "@/store/useDashboardStore.jsx";
import {useState} from "react";
export default function RemoteSensingSettings({ widgetId, currentSettings }) {
+ // STYLING
+ const popoverRecipe = useSlotRecipe({ key: "glassPopover" });
+ const popoverStyles = popoverRecipe();
+
+ // STATE MANAGEMENT
const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
const [startError, setStartError] = useState(null);
const [endError, setEndError] = useState(null);
+ // FUNCTIONS
const handleCopyToClipboard = () => {
updateSettings(widgetId, {copyToClipboard: true});
}
@@ -21,88 +27,111 @@ export default function RemoteSensingSettings({ widgetId, currentSettings }) {
return (
- {/* COPYING TO CLIPBOARD */}
-
+
+
+ {/* SELECTING DATE RANGE */}
+
+ Date Range Selection
+ {/*line seperator*/}
+
+
+ {/* start date */}
+
+
+
+ Start Date
+
+
+
-
-
-
-
- Sort Oldest to Newest
-
-
-
-
-
-
- Sort by Volcano
-
+ value={currentSettings?.startDate || ""}
+ onChange={(e) => {
+ const value = e.target.value;
+ updateSettings(widgetId, {...currentSettings, startDate: value});
- {/* SELECTING DATE RANGE */}
-
-
-
-
- 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);
- 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}}
+
+
+
- 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}}
+
+
+
-
-
-
-
- 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);
+ {/* SORTING */}
+
+ Sorting
+ {/*line seperator*/}
+
+
+
+
+
+
+ Oldest to Newest
+
- if (value && (dateSelected > today)) setEndError("Can't Choose a Future Date");
- else setEndError("");
- }}
- />
- {endError && {endError}}
-
-
-
-
+
+
+
+
+
+ By Volcano
+
+
+ {/* COPYING TO CLIPBOARD */}
+
+ Tools
+ {/*line seperator*/}
+
+
+
+
)
}
\ No newline at end of file
diff --git a/client/src/constants/widgetRegistry.jsx b/client/src/constants/widgetRegistry.jsx
index c21a2f6..4ed37ec 100644
--- a/client/src/constants/widgetRegistry.jsx
+++ b/client/src/constants/widgetRegistry.jsx
@@ -63,7 +63,6 @@ export const WIDGET_REGISTRY = {
},
allowedSizes: {
small: { w: 3, h: 6 },
- medium: { w: 3, h: 9 },
large: { w: 3, h: 12 },
}
},
@@ -85,7 +84,6 @@ export const WIDGET_REGISTRY = {
},
allowedSizes: {
small: { w: 3, h: 6 },
- medium: { w: 3, h: 9 },
large: { w: 3, h: 12 },
}
},
@@ -99,6 +97,12 @@ export const WIDGET_REGISTRY = {
defaultSettings: {
startDate: getStartDate(),
endDate: getEndDate(),
+ filters: {
+ levelIncreases: true,
+ levelDecreases: true,
+ 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: {
small: { w: 3, h: 6 },
@@ -180,7 +184,7 @@ export const WIDGET_REGISTRY = {
endDate: getEndDate(),
},
allowedSizes: {
- medium: { w: 3, h: 12 },
+ large: { w: 3, h: 12 },
}
},
diff --git a/client/src/pages/auth/Login.jsx b/client/src/pages/auth/Login.jsx
index b76b172..97e50c6 100644
--- a/client/src/pages/auth/Login.jsx
+++ b/client/src/pages/auth/Login.jsx
@@ -65,7 +65,7 @@ const Login = () => {
return (
-
+
@@ -88,6 +88,7 @@ const Login = () => {
onChange={(e) => setUsername(e.target.value)}
placeholder="jdoe"
{...fieldStyles}
+ color="gray.800"
/>
@@ -102,8 +103,9 @@ const Login = () => {
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
- placeholder="••••••••"
+ // placeholder="••••••••" // not a necessary placeholder
{...fieldStyles}
+ color="gray.800"
/>
diff --git a/client/src/theme.js b/client/src/theme.js
index 517333b..a559ddf 100644
--- a/client/src/theme.js
+++ b/client/src/theme.js
@@ -2,7 +2,7 @@ import {defineSlotRecipe, createSystem, defaultConfig, defineConfig, defineRecip
// REUSABLE GLASS SURFACE
const glassSurface = {
- bg: {base: "rgba(255, 255, 255, 0.4)", _dark: "rgba(255, 255, 255, 0.04)"},
+ bg: {base: "rgb(253 253 253 / 0.4)", _dark: "rgb(37 38 46 / 0.8)"},
backdropFilter: "blur(12px)",
WebkitBackdropFilter: "blur(12px)",
border: "1px solid",
@@ -68,8 +68,8 @@ const sidebarRecipe = defineSlotRecipe({
root: {
// 1. Semi-transparent backgrounds
bg: {
- base: "linear-gradient(to bottom right, #97282880 20%, #EB7F3160 60%, #FCAD3840 100%)",
- _dark: "linear-gradient(to bottom right, #9900003C 20%, #FF66003C 60%, #FFC0003C 100%)"
+ base: "linear-gradient(to bottom right, #B63B1F88 20%, #FF990060 70%, #FFBF0040 90%)",
+ _dark: "linear-gradient(to bottom right, #9900004C 20%, #FF66003C 60%, #FFAE003C 100%)"
},
// 2. The glass blur effect
@@ -346,17 +346,103 @@ const widgetRecipe = defineSlotRecipe({
// 6. TRUE POPOVER: Compact floating card for 3-dots menus
const glassPopoverRecipe = defineSlotRecipe({
- slots: ["content", "arrow", "positioner"],
+ slots: ["content", "arrow", "positioner", "date", "checkboxcard", "checkbox", "switch", "button"],
base: {
content: {
...glassSurface,
- borderRadius: "xl",
+ borderRadius: "1rem",
p: 3,
boxShadow: "lg",
- bg: {base: "rgba(255, 255, 255, 0.85)", _dark: "rgba(20, 22, 25, 0.85)"},
+ bg: {base: "rgb(246 246 246 / 0.95)", _dark: "#353742"},
backdropFilter: "blur(15px)",
WebkitBackdropFilter: "blur(15px)",
},
+ date: {
+ color: "text",
+ bg: "widgetBadgeBg",
+ borderRadius: "lg",
+ h: "2rem",
+ _focus: {
+ borderStyle: "none",
+ }
+ },
+ checkboxcard: {
+ // applies to both dark & lt modes
+ w: "100%",
+ borderRadius: "lg",
+ color: "text",
+
+ "& div[data-part='control']": {
+ alignItems: "center",
+ padding: "6px",
+ },
+
+ bg: "widgetItemBg",
+ _checked: {
+ boxShadow: "none",
+ bg: "rgb(182 59 31 / 0.2)",
+ borderColor: "#B63B1F"
+ },
+
+ _dark: {
+ bg: "widgetBadgeBg",
+ _checked: {
+ boxShadow: "none",
+ color: "text",
+ bg: "#503D40",
+ borderColor: "#B55038"
+ }
+ }
+ },
+ checkbox: {
+ bg: "widgetInnerBadgeBg",
+ _checked: {
+ bg: "#B63B1F",
+ borderColor: "#B63B1F"
+ },
+
+ _dark: {
+ bg: "widgetBadgeBg",
+ _checked: {
+ color: "text",
+ bg: "#B55038",
+ borderColor: "#B55038"
+ }
+ }
+ },
+ switch: {
+ color: "text",
+ bg: "widgetBadgeBg",
+
+ _checked: {
+ bg: "#B63B1F",
+
+ _dark: {
+ bg: "#b55038"
+ }
+ },
+ },
+ button: {
+ w: "100%",
+ fontSize: "xs",
+ h: "2rem",
+ borderRadius: "lg",
+
+ color: "white",
+ bg: "#B63B1F",
+ _hover: {
+ color: "text",
+ bg: "#B63B1F32"
+ },
+
+ _dark: {
+ bg: "#b55038",
+ color: "text",
+ _hover: {
+ bg: "#503D40"
+ }
+ }
+ }
},
});
@@ -381,7 +467,7 @@ const glassDialogRecipe = defineSlotRecipe({
maxW: "60%",
minH: "80%",
overflow: "hidden",
- bg: {base: "rgba(255, 255, 255, 0.75)", _dark: "rgba(20, 22, 25, 0.8)"},
+ bg: {base: "rgba(255, 255, 255, 0.75)", _dark: "rgb(22 23 28 / 0.9)"},
backdropFilter: "blur(15px)",
WebkitBackdropFilter: "blur(15px)",
},
@@ -471,7 +557,7 @@ const glassTabRecipe = defineRecipe({
// --- THE GLASS EFFECT (Inactive) ---
// Uses a highly transparent white/black depending on mode
- bg: {base: "rgba(255, 255, 255, 0.4)", _dark: "rgba(255, 255, 255, 0.03)"},
+ bg: {base: "rgba(255, 255, 255, 0.4)", _dark: "#1e2027"},
backdropFilter: "blur(12px)",
WebkitBackdropFilter: "blur(12px)",
border: "1px solid",
@@ -485,7 +571,7 @@ const glassTabRecipe = defineRecipe({
pr: "16px",
// --- THE GLASS EFFECT (Hover) ---
// Becomes slightly more opaque when you mouse over
- bg: {base: "rgba(255, 255, 255, 0.8)", _dark: "rgba(255, 255, 255, 0.1)"},
+ bg: {base: "rgba(255, 255, 255, 0.8)", _dark: "#191a22"},
borderColor: {base: "rgba(0, 0, 0, 0.1)", _dark: "rgba(255, 255, 255, 0.15)"},
color: "fg",
textDecoration: "none",
@@ -530,22 +616,20 @@ export const rawWidgetColors = {
========================================================================== */
const config = defineConfig({
globalCss: {
+ // the shadow box behind widgets when editing layout
".react-grid-placeholder": {
background: "#c0d2e3ff !important",
borderColor: "#a9b6c2ff !important",
boxShadow: "0 0 10px #c0d2e3ff !important",
opacity: "1 !important",
- borderRadius: "5px",
+ borderRadius: "3xl",
_dark: {
- background: "#393c3fff !important",
+ background: "#3B3D49FF !important",
borderColor: "#000000ff !important",
boxShadow: "0 0 10px #16181bff !important",
opacity: "1 !important",
},
},
- ".react-resizable-handle-se": {
- zIndex: "1",
- },
"*": {
scrollbarWidth: "thin",
scrollbarColor: "#a0aec0 transparent",
@@ -566,6 +650,7 @@ const config = defineConfig({
"::-webkit-scrollbar-track": {
background: "transparent",
},
+ // this is what changes the maps colors to 'dark mode'
".leaflet-control-zoom-in, .leaflet-control-zoom-out, .leaflet-control-attribution, .leaflet-container": {
_dark: {
filter: "invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%)",
@@ -574,32 +659,34 @@ const config = defineConfig({
".leaflet-container": {
fontFamily: "system-ui, sans-serif !important",
},
- ".react-grid-item": {
- transition: "none !important"
- },
+ // removes the animation of moving widgets, seems very harsh and jumpy w/o animation
+ // ".react-grid-item": {
+ // transition: "none !important"
+ // },
},
theme: {
semanticTokens: {
colors: {
- text: { value: { base: "#000000", _dark: "#FFFFFF" } },
+ text: { value: { base: "#131315", _dark: "rgb(234 234 234 / 0.94)" } },
textInverted: { value: { base: "#000000", _dark: "#FFFFFF" } }, // Fixed: Properly inverted
- MainBackground: { value: { base: "#F9F9F9", _dark: "#141414" } },
- titleColor: { value: { base: "gray.800", _dark: "#FFFFFF" } },
- iconColor: { value: { base: "gray.600", _dark: "gray.300" } }, // Fixed: Visible in dark mode
+ MainBackground: { value: { base: "#f1f1f1", _dark: "#16171c" } },
+ titleColor: { value: { base: "colors.gray.800", _dark: "#e1e1e1" } },
+ iconColor: { value: { base: "#191919", _dark: "rgb(255 255 255 / 0.82)" } }, // Fixed: Visible in dark mode
glassSeperator: { value: { base: "#0000001A", _dark: "#FFFFFF3F" } },
- gooeyButtonBackground: {value: {base: "rgba(255, 255, 255, 0.1)", _dark: "rgba(255, 255, 255, 0.1)"}},
+ gooeyButtonBackground: {value: {base: "rgba(255, 255, 255, 0.1)", _dark: "#333540"}},
// WIDGET SPECIFIC VALUES
- widgetItemBg: { value: { base: "rgba(0, 0, 0, 0.04)", _dark: "rgba(255, 255, 255, 0.05)" } },
- widgetItemExpandedBg: { value: { base: "rgba(0, 0, 0, 0.08)", _dark: "rgba(0, 0, 0, 0.3)" } },
- widgetBadgeBg: { value: { base: "rgba(255, 255, 255, 0.6)", _dark: "rgba(0, 0, 0, 0.5)" } },
- widgetTextMuted: { value: { base: "gray.600", _dark: "gray.400" } },
+ widgetItemBg: { value: { base: "rgb(115 132 166 / 0.17)", _dark: "rgb(51 53 64)" } },
+ widgetItemExpandedBg: { value: { base: "rgb(184 197 220 / 0.17)", _dark: "rgb(59 61 73)" } },
+ widgetBadgeBg: { value: { base: "rgb(154 163 179 / 0.45)", _dark: "rgb(75 77 90)" } },
+ widgetInnerBadgeBg: { value: { base: "rgb(224 226 232)", _dark: "rgb(100 103 119)" } },
+ widgetTextMuted: { value: { base: "#262627", _dark: "rgb(236 236 236 / 0.9)" } },
// ALERT COLORS
- alertRed: { value: { base: "red.600", _dark: "red.400" } },
- alertOrange: { value: { base: "orange.600", _dark: "orange.400" } },
- alertYellow: { value: { base: "yellow.600", _dark: "yellow.400" } },
- alertGreen: { value: { base: "green.600", _dark: "green.400" } },
+ alertRed: { value: { base: "colors.red.600", _dark: "colors.red.400" } },
+ alertOrange: { value: { base: "colors.orange.600", _dark: "colors.orange.400" } },
+ alertYellow: { value: { base: "colors.yellow.600", _dark: "colors.yellow.400" } },
+ alertGreen: { value: { base: "colors.green.600", _dark: "colors.green.400" } },
},
},
tokens: {