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} + + - - + + Your browser cannot display this PDF directly. - - Download PDF + + Download PDF Instead 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: {