diff --git a/src/components/dialog/observation-chart.js b/src/components/dialog/observation-chart.js index f9d96231..bf894b56 100644 --- a/src/components/dialog/observation-chart.js +++ b/src/components/dialog/observation-chart.js @@ -15,6 +15,17 @@ export default function ObservationChart(url) { return (); } +/** + * this suppresses the re-chart errors on the x/y-axis rendering. + * + * @type {{(message?: any, ...optionalParams: any[]): void, (...data: any[]): void}} + */ +const error = console.error; +console.error = (...args) => { + if (/defaultProps/.test(args[0])) return; + error(...args); +}; + /** * Retrieves and returns the chart data in json format * diff --git a/src/components/model-selection/DropDownOptions.js b/src/components/model-selection/DropDownOptions.js new file mode 100644 index 00000000..5ea4537a --- /dev/null +++ b/src/components/model-selection/DropDownOptions.js @@ -0,0 +1,45 @@ +import React, {Fragment} from "react"; +import PropTypes from 'prop-types'; +import {Option} from '@mui/joy'; + +/** + * returns a list of drop down options for that data/type. + * + * @param data + * @param type + * @constructor + */ +export default function DropDownOptions(data) { + // set component prop types + DropDownOptions.propTypes = { data: PropTypes.any }; + + // do not render if there is no data + if (data.data != null) { + // if there is a warning getting the result + if (data.data['Warning'] !== undefined) { + return ( +
+ Warning: {data.data['Warning']} +
+ ); + } + // if there is an error getting the result + else if(data.data['Error'] !== undefined) { + return ( +
+ Error: {data.data['Error']} +
+ ); + } + // return all the options + else { + return ( + + {data.data[data.type].filter(item => item !== "").map(item => ( + + ))} + + ); + } + } +} \ No newline at end of file diff --git a/src/components/model-selection/catalogItems.js b/src/components/model-selection/catalogItems.js new file mode 100644 index 00000000..ac181a6d --- /dev/null +++ b/src/components/model-selection/catalogItems.js @@ -0,0 +1,78 @@ +import React, {Fragment, useState} from "react"; +import PropTypes from 'prop-types'; +import {AccordionGroup, Accordion, AccordionSummary, AccordionDetails, Stack} from '@mui/joy'; + +/** + * returns a list of drop down options for that data/type. + * + * @param data + * @param type + * @constructor + */ +export default function CatalogItems(data) { + // set component prop types + CatalogItems.propTypes = { data: PropTypes.any }; + + // create some state for what catalog accordian is expanded/not expanded + const [accordianIndex, setAccordianIndex] = useState(-1); + + // do not render if there is no data + if (data.data != null) { + // if there is a warning getting the result + if (data.data['Warning'] !== undefined) { + return ( +
+ Warning: {data.data['Warning']} +
+ ); + } + // if there is an error getting the result + else if(data.data['Error'] !== undefined) { + return ( +
+ Error: {data.data['Error']} +
+ ); + } + // return all the data cards + else { + return ( + + + { + data + .data['catalog'] + .filter(catalogs => catalogs !== "") + .map((catalog, itemIndex) => + ( + + { + setAccordianIndex(expanded ? itemIndex : null); + }}> + + + Model date: {catalog['id']} + + + + {catalog['members'].map((member, memberIndex) => ( + + {member['id'] } + + ))} + + + + ) + ) + } + + + ); + } + } +} \ No newline at end of file diff --git a/src/components/model-selection/modelSelectionForm.js b/src/components/model-selection/modelSelectionForm.js deleted file mode 100644 index 4da50bd2..00000000 --- a/src/components/model-selection/modelSelectionForm.js +++ /dev/null @@ -1,179 +0,0 @@ -import React, {Fragment, useState} from 'react'; -import {Button, Divider, Option, Select, Stack, Tab, Tabs, TabList, TabPanel} from '@mui/joy'; -import {useLayers} from "@context"; -import {DatePicker} from '@mui/x-date-pickers'; -import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; -import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; - -import dayjs from 'dayjs'; - -// import {LayerCard} from "@components/trays/layers/layer-card"; - -/** - * This component renders the layer selection form - * - * @returns {JSX.Element} - * @constructor - */ -export const ModelSelectionForm = () => { - // get references to the filtered layer state - const { - //filteredModelLayers - } = useLayers(); - - /** - * method to initiate a model search with the filter selections on the tropical form - * - * @param event - */ - const formTropicalHandler = (event) => { - event.preventDefault(); - const formData = new FormData(event.target); - const formJson = Object.fromEntries(formData.entries()); - alert(JSON.stringify(formJson)); - }; - - /** - * method to initiate a model search with the filter selections on the synoptic form - * - * @param event - */ - const formSynopticHandler = (event) => { - alert(synopticDate); - event.preventDefault(); - const formData = new FormData(event.target); - const formJson = Object.fromEntries(formData.entries()); - alert(JSON.stringify(formJson)); - }; - - // declare state variables for all tropical tab controls - const [tropicalStorm, setTropicalStorm] = useState(''); - const [tropicalAdvisory, setTropicalAdvisory] = useState(''); - const [tropicalGrid, setTropicalGrid] = useState(''); - const [tropicalInstance, setTropicalInstance] = useState(''); - - // declare all state variables for the synoptic tab - const [synopticDate, setSynopticDate] = useState(new Date()); - const [synopticCycle, setSynopticCycle] = useState(''); - const [synopticGrid, setSynopticGrid] = useState(''); - const [synopticInstance, setSynopticInstance] = useState(''); - - /** - * build the query string to ge the data - */ - function buildQueryString(formData, event) { - // ['grid_type', 'instance_name', 'met_class', 'storm_name', 'cycle', 'advisory_number', 'run_date'] - - const queryString = tropicalStorm + tropicalAdvisory + tropicalGrid + tropicalInstance + synopticCycle + synopticGrid + synopticInstance; - - alert(queryString); - - event.preventDefault(); - const formJson = Object.fromEntries(formData.entries()); - alert(JSON.stringify(formJson)); - } - - // render the form - return ( - - - - Tropical - Synoptic - - - -
- - - - - - - - - - - - - - {/* list of search results goes here - may be able to leverage trays/layers/layer-card /> - */} - - -
- -
- - - { setSynopticDate(newValue); }} - /> - - - - - - - - - - - - - - - - { - /* - list of search results goes here - may be able to leverage trays/layers/layer-card /> - */ - } - - -
-
-
- ); -}; - -/** - * this method populates the controls on the form. - * - */ -// const dataLoader = () => { -// -// }; diff --git a/src/components/model-selection/modelSelectionTray.js b/src/components/model-selection/modelSelectionTray.js new file mode 100644 index 00000000..25560a69 --- /dev/null +++ b/src/components/model-selection/modelSelectionTray.js @@ -0,0 +1,40 @@ +import React, {Fragment} from 'react'; +import {Tab, Tabs, TabList, TabPanel} from '@mui/joy'; +import {SynopticTabForm} from "@model-selection/synopticTab"; +import {TropicalTabForm} from "@model-selection/tropicalTab"; + +/** + * This component renders the layer selection form + * + * @returns {JSX.Element} + * @constructor + */ +export const ModelSelectionTray = () => { + // render the form + return ( + + + + Tropical + Synoptic + + + + + + + + + + + + ); +}; + +/** + * this method populates the controls on the form. + * + */ +// const dataLoader = () => { +// +// }; diff --git a/src/components/model-selection/synopticTab.js b/src/components/model-selection/synopticTab.js new file mode 100644 index 00000000..f7004064 --- /dev/null +++ b/src/components/model-selection/synopticTab.js @@ -0,0 +1,175 @@ +import React, { Fragment, useState, useEffect } from 'react'; +import { Button, Divider, Select, Stack } from '@mui/joy'; +import { DatePicker } from '@mui/x-date-pickers'; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import DropDownOptions from "@model-selection/DropDownOptions"; +import CatalogItems from "@model-selection/catalogItems"; + +export const SynopticTabForm = () => { + // declare all state variables for the synoptic tab dropdown data + const [synopticDate, setSynopticDate] = useState(''); + const [synopticCycle, setSynopticCycle] = useState(''); + const [synopticGrid, setSynopticGrid] = useState(''); + const [synopticInstance, setSynopticInstance] = useState(''); + + // init the data urls + const rootUrl = `${process.env.REACT_APP_UI_DATA_URL}`; + const basePulldownUrl = 'get_pulldown_data?met_class=synoptic&use_v3_sp=true'; + const baseDataUrl = 'get_ui_data_secure?met_class=synoptic&use_v3_sp=true'; + const [finalDataUrl, setFinalDataUrl] = useState(rootUrl + basePulldownUrl); + + // storage for received data to render pulldowns + const [dropDownData, setDropDownData] = useState(null); + const [catalogData, setCatalogData] = useState(null); + + /** + * method to initiate a model search with the filter selections on the synoptic form + * + * @param event + */ + const formSynopticSubmit = (event) => { + // dont do the usual form submit operations + event.preventDefault(); + + // gather all the form data + const formData = new FormData(event.target); + const formJson = Object.fromEntries(formData.entries()); + + // build the query string from the submitted form data + const queryString = + ((formJson['synoptic-date'] !== "") ? '&run_date=' + formJson['synoptic-date'] : '') + + ((formJson['synoptic-cycle'] !== "") ? '&cycle=' + formJson['synoptic-cycle'] : '') + + ((formJson['synoptic-grid'] !== "") ? '&grid_type=' + formJson['synoptic-grid'] : '') + + ((formJson['synoptic-instance'] !== "") ? '&instance=' + formJson['synoptic-instance'] : ''); + + // set the url to go after ui data + setFinalDataUrl(rootUrl + baseDataUrl + queryString); + }; + + /** + * Retrieves and returns the model data in json format + * + * @param url + * @returns { json } + */ + useQuery( { + // specify the data key and url to use + queryKey: ['apsviz-synoptic-model-data', finalDataUrl], + + // create the function to call for data + queryFn: async () => { + // create the authorization header + const requestOptions = { + method: 'GET', + headers: {Authorization: `Bearer ${process.env.REACT_APP_UI_DATA_TOKEN}`} + }; + + // make the call to get the data + const {data} = await axios.get(finalDataUrl, requestOptions); + + // check the request type + if (finalDataUrl.indexOf('get_pulldown_data') !== -1) { + // save the dropdown data + setDropDownData(data); + } + else { + // save the catalog data + setCatalogData(data); + } + + // return something + return true; + } + }); + + /** + * this will build the data url and will cause a DB hit + */ + useEffect(() => { + // build the new data url + buildDataUrl(); + }); + + /** + * method to build the query sting to get data + * + */ + function buildDataUrl() { + // init the query string + let query_string = ''; + + // set the query string + if (synopticDate !== '' && synopticDate != null) { query_string += '&run_date=' + synopticDate.toISOString().split("T")[0]; } + + // set the query string + if (synopticCycle !== '' && synopticCycle != null) { query_string += '&cycle=' + synopticCycle; } + + // set the query string + if (synopticGrid !== '' && synopticGrid != null) { query_string += '&grid_type=' + synopticGrid; } + + // set the query string + if (synopticInstance !== '' && synopticInstance != null) { query_string += '&instance_name=' + synopticInstance; } + + // set the pulldown data url. this will trigger a data gathering + setFinalDataUrl(rootUrl + basePulldownUrl + query_string); + } + + /** + * filter the date picker to only allow certain dates to be selected + * + * @param date + * @returns {boolean} + */ + const disableDate = (date) => { + // return false if the date is not found in the list of available dates + return !dropDownData.run_dates.includes(date.toISOString().split("T")[0]); + }; + + /** + * return the rendered component + */ + return ( + +
+ + + { setSynopticDate(newValue); }}/> + + + + + + + + + + + + + + + + { + + } + + +
+ ); +}; \ No newline at end of file diff --git a/src/components/model-selection/tropicalTab.js b/src/components/model-selection/tropicalTab.js new file mode 100644 index 00000000..d9fedc50 --- /dev/null +++ b/src/components/model-selection/tropicalTab.js @@ -0,0 +1,167 @@ +import React, {Fragment, useState, useEffect} from 'react'; +import {Button, Divider, Select, Stack} from '@mui/joy'; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import DropDownOptions from "@model-selection/DropDownOptions"; +import CatalogItems from "@model-selection/catalogItems"; + +export const TropicalTabForm = () => { + // declare state variables for all tropical tab controls + const [tropicalStorm, setTropicalStorm] = useState(''); + const [tropicalAdvisory, setTropicalAdvisory] = useState(''); + const [tropicalGrid, setTropicalGrid] = useState(''); + const [tropicalInstance, setTropicalInstance] = useState(''); + + + // init the data urls + const rootUrl = `${process.env.REACT_APP_UI_DATA_URL}`; + const basePulldownUrl = 'get_pulldown_data?met_class=tropical&use_v3_sp=true'; + const baseDataUrl = 'get_ui_data_secure?met_class=tropical&use_v3_sp=true'; + const [finalDataUrl, setFinalDataUrl] = useState(rootUrl + basePulldownUrl); + + // storage for received data to render pulldowns + const [dropDownData, setDropDownData] = useState(null); + const [catalogData, setCatalogData] = useState(null); + + /** + * method to initiate a model search with the filter selections on the tropical form + * + * @param event + */ + const formTropicalHandler = (event) => { + // dont do the usual submit operations + event.preventDefault(); + + // gather all the form data + const formData = new FormData(event.target); + const formJson = Object.fromEntries(formData.entries()); + + // build the query string from the submitted form data + const queryString = + ((formJson['tropical-storm-name'] !== "") ? '&storm_name=' + formJson['tropical-storm-name'] : '') + + ((formJson['tropical-advisory'] !== "") ? '&advisory_number=' + formJson['tropical-advisory'] : '') + + ((formJson['tropical-grid'] !== "") ? '&grid_type=' + formJson['tropical-grid'] : '') + + ((formJson['tropical-instance'] !== "") ? '&instance=' + formJson['tropical-instance'] : ''); + + // set the url to go after ui data + setFinalDataUrl(rootUrl + baseDataUrl + queryString); + }; + + /** + * Retrieves and returns the dropdown data in json format + * + * @param url + * @returns { json } + */ + // return the data to the caller + useQuery( { + // specify the data key and url to use + queryKey: ['apsviz-tropical-dropdown-data', finalDataUrl], + + // create the function to call for data + queryFn: async () => { + // create the authorization header + const requestOptions = { + method: 'GET', + headers: {Authorization: `Bearer ${process.env.REACT_APP_UI_DATA_TOKEN}`} + }; + // make the call to get the data + const {data} = await axios.get(finalDataUrl, requestOptions); + + // check the request type + if (finalDataUrl.indexOf('get_pulldown_data') !== -1) { + // save the dropdown data + setDropDownData(data); + } + else { + // save the catalog data + setCatalogData(data); + } + + // return something + return true; + } + }); + + /** + * this will build the data url and will cause a DB hit + */ + useEffect(() => { + // build the data url + buildDropDownDataUrl(); + }); + + /** + * resets the form + */ + function resetForm() { + setTropicalStorm(''); + setTropicalAdvisory(''); + setTropicalGrid(''); + setTropicalInstance(''); + + buildDropDownDataUrl(); + } + + /** + * method to build the query sting to get data + * + */ + function buildDropDownDataUrl() { + // init the query string + let query_string = ''; + + // set the storm query string + if (tropicalStorm !== '' && tropicalStorm !== null) { query_string += '&storm_name=' + tropicalStorm; } + + // set the advisory query string + if (tropicalAdvisory !== '' && tropicalAdvisory !== null) { query_string += '&advisory_number=' + tropicalAdvisory; } + + // set the grin query string + if (tropicalGrid !== '' && tropicalGrid !== null) {query_string += '&grid_type=' + tropicalGrid; } + + // set the instance query string + if (tropicalInstance !== '' && tropicalInstance !== null) {query_string += '&instance_name=' + tropicalInstance; } + + // set the pulldown data url. this will trigger a data gathering + setFinalDataUrl(rootUrl + basePulldownUrl + query_string); + } + + /** + * return the rendered component + */ + return ( + +
+ + + + + + + + + + + + + + { + + } + + +
); +}; diff --git a/src/components/trays/model-selection/model-selection.js b/src/components/trays/model-selection/model-selection.js index 44f05193..badf05fd 100644 --- a/src/components/trays/model-selection/model-selection.js +++ b/src/components/trays/model-selection/model-selection.js @@ -1,5 +1,5 @@ import React, { Fragment } from 'react'; -import { ModelSelectionForm } from "@model-selection/modelSelectionForm.js"; +import { ModelSelectionTray } from "@model-selection/modelSelectionTray.js"; /** * component that handles the selection of layers for the map. @@ -11,7 +11,7 @@ export const ModelSelection = () => { // render the layer selection component return ( - + ); };