From a7de5bf222c4f3e30f24ea22936568ab2d9e09a4 Mon Sep 17 00:00:00 2001 From: lublagg Date: Wed, 24 Jan 2024 17:05:03 -0500 Subject: [PATCH 01/11] Filter returned NOAA items. --- src/components/App.tsx | 8 +++--- src/hooks/use-codap-api.tsx | 53 ++++++++++++++++++++++++++++++++----- src/types.ts | 4 +++ src/utils/noaaApiHelper.ts | 2 +- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index bd981b8..6630242 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -26,7 +26,7 @@ const kInitialDimensions = { export const App = () => { const { state, setState } = useStateContext(); - const { createNOAAItems } = useCODAPApi(); + const { filterItems, createNOAAItems } = useCODAPApi(); const [statusMessage, setStatusMessage] = useState(""); const [isFetching, setIsFetching] = useState(false); const { showModal } = state; @@ -64,11 +64,13 @@ export const App = () => { units }; const dataRecords = formatData(formatDataProps); + const items = Array.isArray(dataRecords) ? dataRecords : [dataRecords]; + const filteredItems = filterItems(items); setStatusMessage("Sending weather records to CODAP"); - await createNOAAItems(dataRecords, getSelectedDataTypes()).then( + await createNOAAItems(filteredItems, getSelectedDataTypes()).then( function (result: any) { setIsFetching(false); - setStatusMessage(`Retrieved ${dataRecords.length} cases`); + setStatusMessage(`Retrieved ${filteredItems.length} cases`); return result; }, function (msg: string) { diff --git a/src/hooks/use-codap-api.tsx b/src/hooks/use-codap-api.tsx index a1348f2..3cc3073 100644 --- a/src/hooks/use-codap-api.tsx +++ b/src/hooks/use-codap-api.tsx @@ -1,4 +1,4 @@ -import { Attribute, Collection, DataContext, IDataType } from "../types"; +import { Attribute, Collection, DataContext, IDataType, IItem } from "../types"; import { IResult, codapInterface, createItems, getDataContext } from "@concord-consortium/codap-plugin-api"; import { DSCollection1, DSCollection2, DSName } from "../constants"; import { useStateContext } from "./use-state"; @@ -138,17 +138,55 @@ export const useCODAPApi = () => { return Promise.all(promises); }; - const arrayify = (value: any) => { - return Array.isArray(value) ? value : [value]; + const filterItems = (items: IItem[]) => { + const { selectedFrequency, frequencies } = state; + const { attrs, filters } = frequencies[selectedFrequency]; + const filteredItems = items.filter((item: IItem) => { + const allFiltersMatch: boolean[] = []; + filters.forEach((filter) => { + const { attribute, operator } = filter; + const attrKey = attrs.find((attr) => attr.name === attribute)?.abbr; + if (attrKey) { + const itemValue = Number(item[attrKey]); + if (operator === "equals") { + allFiltersMatch.push(itemValue === filter.value); + } else if (operator === "doesNotEqual") { + allFiltersMatch.push(itemValue !== filter.value); + } else if (operator === "greaterThan") { + allFiltersMatch.push(itemValue > filter.value); + } else if (operator === "lessThan") { + allFiltersMatch.push(itemValue < filter.value); + } else if (operator === "greaterThanOrEqualTo") { + allFiltersMatch.push(itemValue >= filter.value); + } else if (operator === "lessThanOrEqualTo") { + allFiltersMatch.push(itemValue <= filter.value); + } else if (operator === "between") { + const { lowerValue, upperValue } = filter; + allFiltersMatch.push(itemValue > lowerValue && itemValue < upperValue); + } else if (operator === "top" || operator === "bottom") { + const sortedItems = items.sort((a, b) => { + return Number(b[attrKey]) - Number(a[attrKey]); + }); + const end = operator === "top" ? filter.value : sortedItems.length; + const itemsToCheck = sortedItems.slice(end - filter.value, end); + allFiltersMatch.push(itemsToCheck.includes(item)); + } else if (operator === "aboveMean" || operator === "belowMean") { + const mean = items.reduce((acc, i) => acc + Number(i[attrKey]), 0) / items.length; + const expression = operator === "aboveMean" ? itemValue > mean : itemValue < mean; + allFiltersMatch.push(expression); + } + } + }); + return allFiltersMatch.every((match) => match === true); + }); + return filteredItems; }; - const createNOAAItems = async (dataRecords: any, dataTypes: IDataType[]) => { + const createNOAAItems = async (items: IItem[], dataTypes: IDataType[]) => { await updateWeatherDataset(dataTypes); - const items = arrayify(dataRecords); // eslint-disable-next-line no-console - console.log("noaa-cdo ... createNOAAItems with " + dataRecords.length + " case(s)"); + console.log("noaa-cdo ... createNOAAItems with " + items.length + " case(s)"); await createItems(DSName, items); - await codapInterface.sendRequest({ "action": "create", "resource": "component", @@ -161,6 +199,7 @@ export const useCODAPApi = () => { }; return { + filterItems, createNOAAItems }; }; diff --git a/src/types.ts b/src/types.ts index cfc3e01..91507f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -243,3 +243,7 @@ export interface UnitMap { export interface IRecord { [key: string]: number | string | Date | IWeatherStation | IFrequency; } + +export interface IItem { + [key: string]: string; +} diff --git a/src/utils/noaaApiHelper.ts b/src/utils/noaaApiHelper.ts index 2e5cb96..b0af7c4 100644 --- a/src/utils/noaaApiHelper.ts +++ b/src/utils/noaaApiHelper.ts @@ -51,7 +51,7 @@ export const decodeData = (iField: string, iValue: any, database: string) => { }; export const convertNOAARecordToValue = (iRecord: IRecord, weatherStation: IWeatherStation, database: string) => { - let out: IRecord = {}; // to-do: add interface / type + let out: IRecord = {}; Object.keys(iRecord).forEach(function (key: any) { let value = iRecord[key]; let dataTypeName; From 7b0991242241f221e70c8fc090869dad045b1155 Mon Sep 17 00:00:00 2001 From: eireland Date: Wed, 24 Jan 2024 15:26:36 -0800 Subject: [PATCH 02/11] Opens CODAP map zoomed in to the selected weather station --- src/components/location-picker.tsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index da3b11a..18b95e3 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -1,6 +1,8 @@ import React, { useEffect, useRef, useState } from "react"; import classnames from "classnames"; +import { createMap, selectStations } from "../utils/codapHelpers"; import { autoComplete, geoLocSearch } from "../utils/geonameSearch"; +import { kStationsCollectionName } from "../constants"; import { useStateContext } from "../hooks/use-state"; import { IPlace } from "../types"; import { findNearestActiveStation } from "../utils/getWeatherStations"; @@ -13,6 +15,7 @@ import "./location-picker.scss"; export const LocationPicker = () => { const {state, setState} = useStateContext(); + const {location, units, weatherStation} = state; const [showMapButton, setShowMapButton] = useState(false); const [isEditing, setIsEditing] = useState(false); const [locationPossibilities, setLocationPossibilities] = useState([]); @@ -22,17 +25,12 @@ export const LocationPicker = () => { const locationDivRef = useRef(null); const locationInputEl = useRef(null); const locationSelectionListEl = useRef(null); - const selectedLocation = state.location; - const unit = state.units; - const unitDistanceText = unit === "standard" ? "mi" : "km"; - const stationDistance = state.weatherStationDistance && unit === "standard" + const selectedLocation = location; + const unitDistanceText = units === "standard" ? "mi" : "km"; + const stationDistance = state.weatherStationDistance && units === "standard" ? Math.round((state.weatherStationDistance * 0.6 * 10) / 10) :state.weatherStationDistance && Math.round(state.weatherStationDistance * 10) / 10; - const handleOpenMap = () => { - //send request to CODAP to open map with available weather stations - }; - useEffect(() => { if (locationInputEl.current?.value === "") { setShowSelectionList(false); @@ -178,6 +176,15 @@ export const LocationPicker = () => { setIsEditing(true); }; + const handleOpenMap = () => { + if (weatherStation) { + createMap(kStationsCollectionName, {width: 500, height: 350}, [weatherStation.latitude, weatherStation.longitude], 7); + selectStations([weatherStation.name]); + } else if (location) { + createMap(kStationsCollectionName, {width: 500, height: 350}, [location.latitude, location.longitude], 7); + } + }; + return (
From 61ca737d0a2d6d59983a79a7107235cbb0dc209b Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 25 Jan 2024 07:57:02 -0800 Subject: [PATCH 03/11] Changes cursor to pointer when element is clicakble. --- src/components/App.scss | 4 ++++ src/components/App.tsx | 2 +- src/components/attribute-filter.scss | 2 ++ src/components/attribute-selector.scss | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/App.scss b/src/components/App.scss index 04a5e4e..f947f4a 100755 --- a/src/components/App.scss +++ b/src/components/App.scss @@ -24,6 +24,10 @@ background-color: #fff; } + .info-icon { + cursor: pointer; + } + .header-divider { width: 313px; border: solid 1px #979797; diff --git a/src/components/App.tsx b/src/components/App.tsx index bd981b8..998d011 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -145,7 +145,7 @@ export const App = () => {
Retrieve weather data from observing stations. - +
diff --git a/src/components/attribute-filter.scss b/src/components/attribute-filter.scss index 9720fc8..e0f6ccb 100644 --- a/src/components/attribute-filter.scss +++ b/src/components/attribute-filter.scss @@ -26,6 +26,7 @@ $filter-green: #2dbe5e; &.units-header { min-width: 36px; background-color: rgba(126, 126, 126, 0.3); + cursor: pointer; } &.filter-header { width: 41px; @@ -60,6 +61,7 @@ $filter-green: #2dbe5e; color: #177991; font-size: 10px; box-sizing: border-box; + cursor: pointer; &.filtering { background-color: $filter-background-green; diff --git a/src/components/attribute-selector.scss b/src/components/attribute-selector.scss index 55ab762..d12147a 100644 --- a/src/components/attribute-selector.scss +++ b/src/components/attribute-selector.scss @@ -78,6 +78,7 @@ font-size: 12px; font-weight: 500; color: #a2a2a2; + cursor: pointer; &:hover { border: solid 1px rgba(0, 144, 164, 0.25); From 73c6ad1e9b2b4fe3c775d1c31d8af26e4bd19266 Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 25 Jan 2024 08:01:38 -0800 Subject: [PATCH 04/11] Truncates long location possibility names --- src/components/location-picker.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/location-picker.scss b/src/components/location-picker.scss index 2674a4b..d5de205 100644 --- a/src/components/location-picker.scss +++ b/src/components/location-picker.scss @@ -123,6 +123,9 @@ margin-top: 2px; margin-left: -37px; width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; &.geoname-candidate { background-color: #ddeff1; From 0397ab1ddf0165bfa4e84697235ac81d291301d4 Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 25 Jan 2024 08:06:48 -0800 Subject: [PATCH 05/11] Chnages cursor to pointer for the filter modal operator --- src/components/attribute-filter.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/attribute-filter.scss b/src/components/attribute-filter.scss index e0f6ccb..078162a 100644 --- a/src/components/attribute-filter.scss +++ b/src/components/attribute-filter.scss @@ -132,6 +132,7 @@ table tr:nth-child(odd) { text-align: right; color: #177991; margin: 0 3px; + cursor: pointer; } svg { margin-right: 3px; From 1e84938586d2ae5bd5a6e8a1102ef7c3f760ac4a Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 25 Jan 2024 08:21:46 -0800 Subject: [PATCH 06/11] Adds default attributes to hourly and monthly frequencies Fixes All attribute toggle inconsistencies. --- src/components/attribute-selector.tsx | 10 +++++++++- src/types.ts | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/attribute-selector.tsx b/src/components/attribute-selector.tsx index 4995f1e..1723933 100644 --- a/src/components/attribute-selector.tsx +++ b/src/components/attribute-selector.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import classnames from "classnames"; import { useStateContext } from "../hooks/use-state"; import { dailyMonthlyAttrMap, hourlyAttrMap } from "../types"; @@ -15,6 +15,14 @@ export const AttributesSelector = () => { const attributeNamesList = selectedFrequency === "hourly" ? hourlyAttributeNames : dailyMonthlyAttributeNames; const selectedAttrsAndFiltersForFrequency = frequencies[selectedFrequency]; + useEffect(() => { + if (frequencies[selectedFrequency].attrs.length === attributeList.length) { + setAllSelected(true); + } else { + setAllSelected(false); + } + }, [attributeList.length, frequencies, selectedFrequency]); + const handleUnitsClicked = () => { setState(draft => { draft.units = draft.units === "standard" ? "metric" : "standard"; diff --git a/src/types.ts b/src/types.ts index cfc3e01..95a88c4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -148,9 +148,9 @@ export const hourlyAttrMap: AttrType[] = [ export const DefaultState: IState = { selectedFrequency: "daily", - frequencies: {hourly: {attrs: [], filters: []}, + frequencies: {hourly: {attrs: hourlyAttrMap, filters: []}, daily: {attrs: dailyMonthlyAttrMap, filters: []}, - monthly: {attrs: [], filters: []}}, + monthly: {attrs: dailyMonthlyAttrMap, filters: []}}, units: "standard", didUserSelectDate: false, }; From c947172a3d78f919cf8a165b2eb4c8455b025058 Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 25 Jan 2024 08:36:48 -0800 Subject: [PATCH 07/11] Fixes wind speed text --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 95a88c4..5fe7739 100644 --- a/src/types.ts +++ b/src/types.ts @@ -133,7 +133,7 @@ export const dailyMonthlyAttrMap: AttrType[] = [ {name: "Average temperature", abbr: "tAvg", unit: unitMap.temperature}, {name: "Precipitation", abbr: "precip", unit: unitMap.precipitation}, {name: "Snowfall", abbr: "snow", unit: unitMap.precipitation}, - {name: "Average windspeed", abbr: "avgWind", unit: unitMap.speed} + {name: "Average wind speed", abbr: "avgWind", unit: unitMap.speed} ]; export const hourlyAttrMap: AttrType[] = [ From 42aca6788ffac81cc48ce4923a1286db124fbe9b Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 25 Jan 2024 09:12:16 -0800 Subject: [PATCH 08/11] Fixes some styling in filter table to match spec --- src/components/attribute-filter.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/attribute-filter.scss b/src/components/attribute-filter.scss index 9720fc8..429a2b0 100644 --- a/src/components/attribute-filter.scss +++ b/src/components/attribute-filter.scss @@ -3,7 +3,7 @@ $filter-background-green: rgba(90, 249, 90, 0.25); $filter-green: #2dbe5e; .attribute-filter-container { - + padding-bottom: 16px; .table-header { color: rgba(0, 0, 0, 0.48); font-size: 10px; @@ -86,7 +86,7 @@ $filter-green: #2dbe5e; } } -table tr:nth-child(odd) { +table tr:nth-child(even) { background-color: rgba(216, 216, 216, 0.3); } From 2280d6580ad04f0240616503bd85058ef1a55dc6 Mon Sep 17 00:00:00 2001 From: lublagg Date: Thu, 25 Jan 2024 18:37:01 -0500 Subject: [PATCH 09/11] Set timezone when station is selected. --- src/components/App.tsx | 21 ++++++---- src/components/location-picker.tsx | 65 ++++++++++++++++++++++-------- src/constants.ts | 10 +++++ src/types.ts | 8 +++- src/utils/noaaApiHelper.ts | 22 +++++----- 5 files changed, 89 insertions(+), 37 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 6630242..bf32558 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -52,11 +52,15 @@ export const App = () => { }; const fetchSuccessHandler = async (data: any) => { - const {stationTimezoneOffset, weatherStation, selectedFrequency, startDate, endDate, units} = state; - if (data && weatherStation) { + const {startDate, endDate, units, selectedFrequency, + weatherStation, timezone} = state; + const allDefined = (startDate && endDate && units && selectedFrequency && + weatherStation && timezone); + + if (data && allDefined) { const formatDataProps = { data, - stationTimezoneOffset, + timezone, weatherStation, frequency: selectedFrequency, startDate, @@ -102,9 +106,12 @@ export const App = () => { }; const handleGetData = async () => { - const { location, startDate, endDate, selectedFrequency, weatherStation, stationTimezoneOffset } = state; - const attributes = state.frequencies[selectedFrequency].attrs.map(attr => attr.name); - if (location && attributes && startDate && endDate && weatherStation && selectedFrequency) { + const { location, startDate, endDate, weatherStation, frequencies, + selectedFrequency, timezone } = state; + const attributes = frequencies[selectedFrequency].attrs.map(attr => attr.name); + const allDefined = (startDate && endDate && location && weatherStation && timezone); + + if (allDefined) { const isEndDateAfterStartDate = endDate.getTime() >= startDate.getTime(); if (isEndDateAfterStartDate) { setStatusMessage("Fetching weather records from NOAA"); @@ -114,7 +121,7 @@ export const App = () => { frequency: selectedFrequency, weatherStation, attributes, - stationTimezoneOffset + gmtOffset: timezone.gmtOffset }); try { const tRequest = new Request(tURL); diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index da3b11a..8120b69 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -8,11 +8,13 @@ import OpenMapIcon from "../assets/images/icon-map.svg"; // import EditIcon from "../assets/images/icon-edit.svg"; import LocationIcon from "../assets/images/icon-location.svg"; import CurrentLocationIcon from "../assets/images/icon-current-location.svg"; +import { geonamesUser, kOffsetMap, timezoneServiceURL } from "../constants"; import "./location-picker.scss"; export const LocationPicker = () => { - const {state, setState} = useStateContext(); + const { state, setState } = useStateContext(); + const { location, units, weatherStation, weatherStationDistance } = state; const [showMapButton, setShowMapButton] = useState(false); const [isEditing, setIsEditing] = useState(false); const [locationPossibilities, setLocationPossibilities] = useState([]); @@ -22,12 +24,10 @@ export const LocationPicker = () => { const locationDivRef = useRef(null); const locationInputEl = useRef(null); const locationSelectionListEl = useRef(null); - const selectedLocation = state.location; - const unit = state.units; - const unitDistanceText = unit === "standard" ? "mi" : "km"; - const stationDistance = state.weatherStationDistance && unit === "standard" - ? Math.round((state.weatherStationDistance * 0.6 * 10) / 10) - :state.weatherStationDistance && Math.round(state.weatherStationDistance * 10) / 10; + const unitDistanceText = units === "standard" ? "mi" : "km"; + const stationDistance = weatherStationDistance && units === "standard" + ? Math.round((weatherStationDistance * 0.6 * 10) / 10) + : weatherStationDistance && Math.round(weatherStationDistance * 10) / 10; const handleOpenMap = () => { //send request to CODAP to open map with available weather stations @@ -36,7 +36,6 @@ export const LocationPicker = () => { useEffect(() => { if (locationInputEl.current?.value === "") { setShowSelectionList(false); - // setSelectedLocation(undefined); } }, [locationInputEl.current?.value]); @@ -47,8 +46,8 @@ export const LocationPicker = () => { }, [isEditing]); useEffect(() => { - if (selectedLocation) { - findNearestActiveStation(selectedLocation.latitude, selectedLocation.longitude, 80926000, "present") + if (location) { + findNearestActiveStation(location.latitude, location.longitude, 80926000, "present") .then(({station, distance}) => { if (station) { setState((draft) => { @@ -59,7 +58,39 @@ export const LocationPicker = () => { }); } // eslint-disable-next-line react-hooks/exhaustive-deps - },[selectedLocation]); + }, [location]); + + useEffect(() => { + if (weatherStation) { + const fetchTimezone = async (lat: number, long: number) => { + let url = `${timezoneServiceURL}?lat=${lat}&lng=${long}&username=${geonamesUser}`; + let res = await fetch(url); + if (res) { + if (res.ok) { + const timezoneData = await res.json(); + const { gmtOffset } = timezoneData as { gmtOffset: keyof typeof kOffsetMap }; + setState((draft) => { + draft.timezone = { + gmtOffset, + name: kOffsetMap[gmtOffset] + }; + }); + } else { + console.warn(res.statusText); + } + } else { + console.warn(`Failed to fetch timezone data for station ${weatherStation.name}`); + } + }; + + fetchTimezone(weatherStation.latitude, weatherStation.longitude); + } else { + setState((draft) => { + draft.timezone = undefined; + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [weatherStation]); const getLocationList = () => { if (locationInputEl.current) { @@ -182,13 +213,13 @@ export const LocationPicker = () => {
Location - { selectedLocation && !isEditing && + { location && !isEditing &&
- { state.weatherStation && + { weatherStation && <> - {state.weatherStationDistance && + {weatherStationDistance && ({stationDistance} {unitDistanceText}) } - {state.weatherStation?.name} + {weatherStation?.name} {/* hide this for now until implemented*/} } @@ -200,10 +231,10 @@ export const LocationPicker = () => {
- { selectedLocation && !isEditing + { location && !isEditing ?
Stations near - {state.location?.name} + {location?.name}
: diff --git a/src/constants.ts b/src/constants.ts index 72cee98..b833808 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -145,3 +145,13 @@ export const kWeatherStationCollectionAttrs = [ }, } ]; + +export const kOffsetMap = { + "-4": "AST", + "-5": "EST", + "-6": "CST", + "-7": "MST", + "-8": "PST", + "-9": "AKST", + "-10": "HST" +}; diff --git a/src/types.ts b/src/types.ts index 91507f4..8f3b598 100644 --- a/src/types.ts +++ b/src/types.ts @@ -100,6 +100,11 @@ interface IWeatherStationID { id: string; } +export interface ITimeZone { + gmtOffset: string; + name: string; +} + export interface IState { location?: IPlace; weatherStation?: IWeatherStation; @@ -112,8 +117,7 @@ export interface IState { endDate?: Date; units: IUnits; showModal?: "info" | "data-return-warning"; - stationTimezoneOffset?: number; - stationTimezoneName?: string; + timezone?: ITimeZone; didUserSelectDate: boolean; } diff --git a/src/utils/noaaApiHelper.ts b/src/utils/noaaApiHelper.ts index b0af7c4..1d5aa59 100644 --- a/src/utils/noaaApiHelper.ts +++ b/src/utils/noaaApiHelper.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import { IFrequency, IRecord, IUnits, IWeatherStation } from "../types"; +import { IFrequency, IRecord, ITimeZone, IUnits, IWeatherStation } from "../types"; import { frequencyToReportTypeMap, nceiBaseURL } from "../constants"; import { dataTypeStore } from "./noaaDataTypes"; @@ -19,23 +19,22 @@ export const convertUnits = (fromUnitSystem: IUnits, toUnitSystem: IUnits, data: interface IFormatData { data: IRecord[]; - stationTimezoneOffset?: number; - stationTimezoneName?: string; units: IUnits; frequency: IFrequency; weatherStation: IWeatherStation; + timezone: ITimeZone; } export const formatData = (props: IFormatData) => { - const {data, stationTimezoneOffset, stationTimezoneName, units, frequency, weatherStation} = props; + const {data, timezone, units, frequency, weatherStation} = props; const database = frequencyToReportTypeMap[frequency]; let dataRecords: any[] = []; data.forEach((r: any) => { const aValue = convertNOAARecordToValue(r, weatherStation, database); aValue.latitude = weatherStation.latitude; aValue.longitude = weatherStation.longitude; - aValue["UTC offset"] = stationTimezoneOffset || ""; - aValue.timezone = stationTimezoneName || ""; + aValue["UTC offset"] = timezone.gmtOffset; + aValue.timezone = timezone.name; aValue.elevation = weatherStation.elevation; aValue["report type"] = frequency; dataRecords.push(aValue); @@ -84,19 +83,20 @@ interface IComposeURL { frequency: IFrequency; attributes: string[]; weatherStation: IWeatherStation; - stationTimezoneOffset?: number; + gmtOffset: string; } export const composeURL = (props: IComposeURL) => { - const { startDate, endDate, frequency, attributes, weatherStation, stationTimezoneOffset } = props; + const { startDate, endDate, frequency, attributes, weatherStation, gmtOffset } = props; const database = frequencyToReportTypeMap[frequency]; const format = "YYYY-MM-DDThh:mm:ss"; let sDate = dayjs(startDate); let eDate = dayjs(endDate); + // adjust for local station time - if (database === "global-hourly" && stationTimezoneOffset) { - sDate = dayjs(startDate).subtract(stationTimezoneOffset, "hour"); - eDate = dayjs(endDate).subtract(stationTimezoneOffset, "hour").add(1, "day"); + if (database === "global-hourly") { + sDate = dayjs(startDate).subtract(Number(gmtOffset), "hour"); + eDate = dayjs(endDate).subtract(Number(gmtOffset), "hour").add(1, "day"); } const startDateString = dayjs(sDate).format(format); const endDateString = dayjs(eDate).format(format); From 9e3004b188a0dd8ed94c19c32f7b354bada97b25 Mon Sep 17 00:00:00 2001 From: lublagg Date: Thu, 25 Jan 2024 18:43:48 -0500 Subject: [PATCH 10/11] Change dependency to location instead of weatherStation. --- src/components/location-picker.tsx | 58 ++++++++++++++---------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index 8120b69..0fba750 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -54,44 +54,38 @@ export const LocationPicker = () => { draft.weatherStation = station; draft.weatherStationDistance = distance; }); + + const fetchTimezone = async (lat: number, long: number) => { + let url = `${timezoneServiceURL}?lat=${lat}&lng=${long}&username=${geonamesUser}`; + let res = await fetch(url); + if (res) { + if (res.ok) { + const timezoneData = await res.json(); + const { gmtOffset } = timezoneData as { gmtOffset: keyof typeof kOffsetMap }; + setState((draft) => { + draft.timezone = { + gmtOffset, + name: kOffsetMap[gmtOffset] + }; + }); + } else { + console.warn(res.statusText); + } + } else { + console.warn(`Failed to fetch timezone data for ${location}`); + } + }; + fetchTimezone(location.latitude, location.longitude); } }); + } else { + setState((draft) => { + draft.timezone = undefined; + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [location]); - useEffect(() => { - if (weatherStation) { - const fetchTimezone = async (lat: number, long: number) => { - let url = `${timezoneServiceURL}?lat=${lat}&lng=${long}&username=${geonamesUser}`; - let res = await fetch(url); - if (res) { - if (res.ok) { - const timezoneData = await res.json(); - const { gmtOffset } = timezoneData as { gmtOffset: keyof typeof kOffsetMap }; - setState((draft) => { - draft.timezone = { - gmtOffset, - name: kOffsetMap[gmtOffset] - }; - }); - } else { - console.warn(res.statusText); - } - } else { - console.warn(`Failed to fetch timezone data for station ${weatherStation.name}`); - } - }; - - fetchTimezone(weatherStation.latitude, weatherStation.longitude); - } else { - setState((draft) => { - draft.timezone = undefined; - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [weatherStation]); - const getLocationList = () => { if (locationInputEl.current) { autoComplete(locationInputEl.current) From 9e97bcaf83e294d99be552a4db91d7138ec501e1 Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 25 Jan 2024 17:02:42 -0800 Subject: [PATCH 11/11] Fix build errors --- src/components/location-picker.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index 3088368..1ed1a0f 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import classnames from "classnames"; import { createMap, selectStations } from "../utils/codapHelpers"; import { autoComplete, geoLocSearch } from "../utils/geonameSearch"; -import { kStationsCollectionName } from "../constants"; +import { kStationsCollectionName, geonamesUser, kOffsetMap, timezoneServiceURL } from "../constants"; import { useStateContext } from "../hooks/use-state"; import { IPlace } from "../types"; import { findNearestActiveStation } from "../utils/getWeatherStations"; @@ -10,7 +10,6 @@ import OpenMapIcon from "../assets/images/icon-map.svg"; // import EditIcon from "../assets/images/icon-edit.svg"; import LocationIcon from "../assets/images/icon-location.svg"; import CurrentLocationIcon from "../assets/images/icon-current-location.svg"; -import { geonamesUser, kOffsetMap, timezoneServiceURL } from "../constants"; import "./location-picker.scss"; @@ -26,7 +25,6 @@ export const LocationPicker = () => { const locationDivRef = useRef(null); const locationInputEl = useRef(null); const locationSelectionListEl = useRef(null); - const selectedLocation = location; const unitDistanceText = units === "standard" ? "mi" : "km"; const stationDistance = weatherStationDistance && units === "standard" ? Math.round((weatherStationDistance * 0.6 * 10) / 10)