From 265aa9bb7618a2c4b2bac5f0623b7ad306e7ff7a Mon Sep 17 00:00:00 2001 From: "Bradley (Brad) Andrick" Date: Tue, 27 Feb 2024 13:26:31 -0500 Subject: [PATCH] Ba/feature/replace date range picker (#335) * remove old top content component * update readme * update example config * refactor button placement and names * update tests * adjust legend positioning * update mocks after config change * stack resultsCount buttons * stack count message and edit responsiveness * update changelog * blackout filters * update readme with added config values * update example config * fixing bug to search all gridcodes selected * persist dateTime value on details view tab changed * open details on search * fix selected view mode color to match material UI guide * adjust cloud slider style and persist state on tab change * persist collection selected on details tab change * remove popup modal from right content * add auto-zoom to search, adjust style, and raise disabled overlay * edits to application redux state * refactor mapHelper to fix single gridcode bug, popup refactor, ItemZoom function * refactor popupResult to move into LeftContent Details Tab * add style theme to auto zoom switch * clean up search imports * cleaning up css * change keyPress combination for searching * add and update tests * update wording of readme * remove duplicate styles * reorder leftContent functions * remove redundant comment * remove inline styles * update changelog * update changelog wording * resolve bug in preview height * remove unnecessary css change * adding new react-datePicker * clean up merge main into branch * add dayJS & bump versions * remove old datetime format function, add convertToUTC with offset * move range preview to collection Dropdown component * bonus: refactor theme of switch * refactor date range picker * remove old wojtekmaj-react-datetimerange-picker * add comment about use of !important * resolve issue with rerender date formatting * add state change for rerender date format bug fix * add no results indication * handle all tab change rerenders * clean collection range display code * remove extra comment * add label to show UTC format * clean up datePickers * remove extra const usage * update changelog * update changelog again * relocate No Results Message * remove old test ID * change range text to be extent --- CHANGELOG.md | 6 + package-lock.json | 323 ++++-------------- package.json | 4 +- .../CollectionDropdown/CollectionDropdown.css | 9 + .../CollectionDropdown/CollectionDropdown.jsx | 17 + .../DateTimeRangeSelector.css | 45 ++- .../DateTimeRangeSelector.jsx | 222 +++++++----- .../Content/LeftContent/LeftContent.jsx | 6 +- .../Content/RightContent/RightContent.jsx | 8 + src/components/Search/Search.css | 3 +- src/components/Search/Search.jsx | 5 +- src/redux/slices/mainSlice.js | 7 +- src/services/get-search-service.js | 4 +- src/utils/datetime.js | 35 +- src/utils/mapHelper.js | 4 +- 15 files changed, 313 insertions(+), 385 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 427f76ba..8cc69bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Run search shortcut added for `SPACE` bar key press. - Added config option for `SHOW_ITEM_AUTO_ZOOM` to render switch that lets user toggle behavior of map center/zooming automatically on selected scene. - Added config option for `STAC_LINK_ENABLED` to render link out to item in STAC API when set to `true`. +- Added `results not found message` when search results are empty. ### Changed @@ -30,6 +31,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Results popup now renders in left panel. - Tabs added to left panel to switch between results and info panels. - Refactor of loading indicator to specify when imagery overlay is loading instead vs. search loading. +- Style adjusted for switch to be in line with other UI elements. +- Replace date picker library `DateTimeRangePicker` to use `react-datepicker`. +- Collection range is now always visible in search panel. +- Date Range Picker does not get changed on collection change. Only set on initial load and then manually by user. ### Fixed @@ -39,6 +44,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Improved responsiveness for mid-size screens. - Bug fix for when multiple grid-code grids are selected, bug was only showing one grid-code in results. - Refactor keyboard shortcut for running search. Changed to use `ctrl+space`. +- Date range format now renders and searches correctly using UTC instead of using local timezone. ### Removed diff --git a/package-lock.json b/package-lock.json index 4a1970d4..e87b7377 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,14 +15,16 @@ "@mui/material": "^5.15.0", "@reduxjs/toolkit": "^2.0.1", "@vitejs/plugin-react": "^4.2.1", - "@wojtekmaj/react-datetimerange-picker": "^5.4.4", "colormap": "^2.3.2", + "dayjs": "^1.11.10", + "dayjs-plugin-utc": "^0.1.2", "dompurify": "^3.0.6", "h3-js": "^4.1.0", "leaflet": "^1.9.3", "leaflet-draw": "^1.0.4", "leaflet-geosearch": "^3.9.0", "react": "^18.2.0", + "react-datepicker": "^6.1.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-leaflet": "^4.2.1", @@ -1158,6 +1160,20 @@ "@floating-ui/utils": "^0.2.0" } }, + "node_modules/@floating-ui/react": { + "version": "0.26.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.9.tgz", + "integrity": "sha512-p86wynZJVEkEq2BBjY/8p2g3biQ6TlgT4o/3KgFKyTWoJLU1GZ8wpctwRqtkEl2tseYA+kw7dBAIDFcednfI5w==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.8", + "@floating-ui/utils": "^0.2.1", + "tabbable": "^6.0.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/react-dom": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", @@ -2743,19 +2759,6 @@ "@types/leaflet": "*" } }, - "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" - }, - "node_modules/@types/lodash.memoize": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz", - "integrity": "sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==", - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -2810,7 +2813,7 @@ "version": "18.2.19", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz", "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==", - "devOptional": true, + "dev": true, "dependencies": { "@types/react": "*" } @@ -3380,41 +3383,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@wojtekmaj/date-utils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz", - "integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==", - "funding": { - "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" - } - }, - "node_modules/@wojtekmaj/react-datetimerange-picker": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@wojtekmaj/react-datetimerange-picker/-/react-datetimerange-picker-5.5.0.tgz", - "integrity": "sha512-bgdKBsXUrMSBXmlb4hod78HRQ4sA6oCHzaWoWhXwS/lQizuxPI8K8csNsBBwMYMw7DPRq8Jv6aEawHAfjwvHpw==", - "dependencies": { - "clsx": "^2.0.0", - "make-event-props": "^1.6.0", - "prop-types": "^15.6.0", - "react-calendar": "^4.6.0", - "react-clock": "^4.5.0", - "react-datetime-picker": "^5.5.1", - "react-fit": "^1.7.0" - }, - "funding": { - "url": "https://github.com/wojtekmaj/react-datetimerange-picker?sponsor=1" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -4369,11 +4337,24 @@ "node": ">=14" } }, + "node_modules/date-fns": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", + "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", - "dev": true + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "node_modules/dayjs-plugin-utc": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dayjs-plugin-utc/-/dayjs-plugin-utc-0.1.2.tgz", + "integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg==" }, "node_modules/debug": { "version": "4.3.4", @@ -4521,14 +4502,6 @@ "node": ">=6" } }, - "node_modules/detect-element-overflow": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/detect-element-overflow/-/detect-element-overflow-1.4.2.tgz", - "integrity": "sha512-4m6cVOtvm/GJLjo7WFkPfwXoEIIbM7GQwIh4WEa4g7IsNi1YzwUsGL5ApNLrrHL29bHeNeQ+/iZhw+YHqgE2Fw==", - "funding": { - "url": "https://github.com/wojtekmaj/detect-element-overflow?sponsor=1" - } - }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -6345,18 +6318,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/get-user-locale": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.1.tgz", - "integrity": "sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==", - "dependencies": { - "@types/lodash.memoize": "^4.1.7", - "lodash.memoize": "^4.1.1" - }, - "funding": { - "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1" - } - }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", @@ -7600,7 +7561,8 @@ "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -7795,14 +7757,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-event-props": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.6.2.tgz", - "integrity": "sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==", - "funding": { - "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" - } - }, "node_modules/make-plural": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.3.0.tgz", @@ -9971,111 +9925,20 @@ "node": ">=0.10.0" } }, - "node_modules/react-calendar": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-4.8.0.tgz", - "integrity": "sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA==", - "dependencies": { - "@wojtekmaj/date-utils": "^1.1.3", - "clsx": "^2.0.0", - "get-user-locale": "^2.2.1", - "prop-types": "^15.6.0", - "warning": "^4.0.0" - }, - "funding": { - "url": "https://github.com/wojtekmaj/react-calendar?sponsor=1" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-clock": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/react-clock/-/react-clock-4.6.0.tgz", - "integrity": "sha512-Yz+vwrwrfVRSBw3BdmX/Mc7mVdQYJQ5Pi00qDzGLyLNWQuEmp5PC2oYjQAsDalLjekeDwBIGD7OLcKnkAp1kcw==", - "dependencies": { - "@wojtekmaj/date-utils": "^1.5.0", - "clsx": "^2.0.0", - "get-user-locale": "^2.2.1", - "prop-types": "^15.6.0" - }, - "funding": { - "url": "https://github.com/wojtekmaj/react-clock?sponsor=1" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-date-picker": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/react-date-picker/-/react-date-picker-10.6.0.tgz", - "integrity": "sha512-db5lcmU/52X8ur8SU1QU3PYBiaDG5SbzZDlqWk3YruPx5Ti9w6UpqCRsd1TXycVla9Ut2I3Qb4BUe27jxSwHeg==", - "dependencies": { - "@wojtekmaj/date-utils": "^1.1.3", - "clsx": "^2.0.0", - "get-user-locale": "^2.2.1", - "make-event-props": "^1.6.0", - "prop-types": "^15.6.0", - "react-calendar": "^4.6.0", - "react-fit": "^1.7.0", - "update-input-width": "^1.4.0" - }, - "funding": { - "url": "https://github.com/wojtekmaj/react-date-picker?sponsor=1" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-datetime-picker": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/react-datetime-picker/-/react-datetime-picker-5.6.0.tgz", - "integrity": "sha512-zbYSuYuiRj4/6lR9xGjAgw7V4gpTGtzOwZIfw1TONj6K6OKuaDreczS+6ijJjwLYlMcx8V+Sw1IMP+K059wnnA==", + "node_modules/react-datepicker": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.1.0.tgz", + "integrity": "sha512-8uz+hAOpvHqZGvD4Ky1hJ0/tLI4S9B0Gu9LV7LtLxRKXODs/xrxEay0aMVp7AW9iizTeImZh/6aA00fFaRZpJw==", "dependencies": { - "@wojtekmaj/date-utils": "^1.1.3", - "clsx": "^2.0.0", - "get-user-locale": "^2.2.1", - "make-event-props": "^1.6.0", - "prop-types": "^15.6.0", - "react-calendar": "^4.6.0", - "react-clock": "^4.5.0", - "react-date-picker": "^10.5.0", - "react-fit": "^1.7.0", - "react-time-picker": "^6.5.0" - }, - "funding": { - "url": "https://github.com/wojtekmaj/react-datetime-picker?sponsor=1" + "@floating-ui/react": "^0.26.2", + "classnames": "^2.2.6", + "date-fns": "^3.3.1", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.13.0" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" } }, "node_modules/react-dom": { @@ -10106,33 +9969,6 @@ "react": ">= 16.8 || 18.0.0" } }, - "node_modules/react-fit": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/react-fit/-/react-fit-1.7.1.tgz", - "integrity": "sha512-y/TYovCCBzfIwRJsbLj0rH4Es40wPQhU5GPPq9GlbdF09b0OdzTdMSkBza0QixSlgFzTm6dkM7oTFzaVvaBx+w==", - "dependencies": { - "detect-element-overflow": "^1.4.0", - "prop-types": "^15.6.0", - "tiny-warning": "^1.0.0" - }, - "funding": { - "url": "https://github.com/wojtekmaj/react-fit?sponsor=1" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -10151,6 +9987,19 @@ "react-dom": "^18.0.0" } }, + "node_modules/react-onclickoutside": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, "node_modules/react-redux": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", @@ -10185,34 +10034,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-time-picker": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/react-time-picker/-/react-time-picker-6.6.0.tgz", - "integrity": "sha512-1PCetwrYcFNXALU9Oml32NAcFgPCPZLB5U8AQEgBoavJw61YmA0B0OSto6cOz9syGmPdcLZhDqRtN+EkZji+3w==", - "dependencies": { - "@wojtekmaj/date-utils": "^1.1.3", - "clsx": "^2.0.0", - "get-user-locale": "^2.2.1", - "make-event-props": "^1.6.0", - "prop-types": "^15.6.0", - "react-clock": "^4.5.0", - "react-fit": "^1.7.0", - "update-input-width": "^1.4.0" - }, - "funding": { - "url": "https://github.com/wojtekmaj/react-time-picker?sponsor=1" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-tooltip": { "version": "5.26.2", "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.26.2.tgz", @@ -12296,6 +12117,11 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", @@ -12376,11 +12202,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "node_modules/tinybench": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", @@ -13492,14 +13313,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-input-width": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/update-input-width/-/update-input-width-1.4.2.tgz", - "integrity": "sha512-/p0XLhrQQQ4bMWD7bL9duYObwYCO1qGr8R19xcMmoMSmXuQ7/1//veUnCObQ7/iW6E2pGS6rFkS4TfH4ur7e/g==", - "funding": { - "url": "https://github.com/wojtekmaj/update-input-width?sponsor=1" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -15031,14 +14844,6 @@ "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", "dev": true }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 7613619e..a9734130 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,16 @@ "@mui/material": "^5.15.0", "@reduxjs/toolkit": "^2.0.1", "@vitejs/plugin-react": "^4.2.1", - "@wojtekmaj/react-datetimerange-picker": "^5.4.4", "colormap": "^2.3.2", + "dayjs": "^1.11.10", + "dayjs-plugin-utc": "^0.1.2", "dompurify": "^3.0.6", "h3-js": "^4.1.0", "leaflet": "^1.9.3", "leaflet-draw": "^1.0.4", "leaflet-geosearch": "^3.9.0", "react": "^18.2.0", + "react-datepicker": "^6.1.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-leaflet": "^4.2.1", diff --git a/src/components/CollectionDropdown/CollectionDropdown.css b/src/components/CollectionDropdown/CollectionDropdown.css index f612d0d8..004ea155 100644 --- a/src/components/CollectionDropdown/CollectionDropdown.css +++ b/src/components/CollectionDropdown/CollectionDropdown.css @@ -27,3 +27,12 @@ padding-top: 1px; min-width: 100%; } + +.collectionRangeText { + display: flex; + flex-direction: row; + width: 100%; + font-size: 12px; + font-weight: 100; + margin-top: 10px; +} diff --git a/src/components/CollectionDropdown/CollectionDropdown.jsx b/src/components/CollectionDropdown/CollectionDropdown.jsx index 72486bc9..6f721098 100644 --- a/src/components/CollectionDropdown/CollectionDropdown.jsx +++ b/src/components/CollectionDropdown/CollectionDropdown.jsx @@ -21,6 +21,9 @@ const Dropdown = () => { const _selectedCollection = useSelector( (state) => state.mainSlice.selectedCollection ) + const _selectedCollectionData = useSelector( + (state) => state.mainSlice.selectedCollectionData + ) const dispatch = useDispatch() const [collectionId, setCollectionId] = useState(_selectedCollection) const _collectionsData = useSelector( @@ -70,6 +73,10 @@ const Dropdown = () => { clearAllLayers() } + function formatDate(dateString) { + return dateString ? dateString.split('T')[0] : null + } + return ( @@ -93,6 +100,16 @@ const Dropdown = () => { + {_selectedCollectionData?.extent?.temporal && ( +
+ Extent:  + {formatDate(_selectedCollectionData.extent.temporal.interval[0][0]) || + 'No Lower Limit'}{' '} + to{' '} + {formatDate(_selectedCollectionData.extent.temporal.interval[0][1]) || + 'Present'} +
+ )}
) } diff --git a/src/components/DateTimeRangeSelector/DateTimeRangeSelector.css b/src/components/DateTimeRangeSelector/DateTimeRangeSelector.css index 5d2d30ca..d97c06e8 100644 --- a/src/components/DateTimeRangeSelector/DateTimeRangeSelector.css +++ b/src/components/DateTimeRangeSelector/DateTimeRangeSelector.css @@ -1,13 +1,19 @@ -@import '@wojtekmaj/react-datetimerange-picker/dist/DateTimeRangePicker.css'; -@import './react-calendar-overrides.css'; -@import 'react-clock/dist/Clock.css'; - label { position: relative; top: 2px; } -/* Date Range Picker */ +.datePickerContainer { + display: flex; + flex-direction: row; + width: 100%; + margin-top: 15px; + margin-bottom: 15px; +} + +.reactDatePicker { + width: 80px; +} .datePicker { font-size: 14px; @@ -18,6 +24,10 @@ label { font-size: 16px; } +.datePicker select { + margin-top: 5px; +} + .datePicker label span svg { height: 16px; width: 16px; @@ -48,3 +58,28 @@ span.error-true { opacity: 1; z-index: 999; } + +.dateRangeSpanText { + display: flex; + justify-content: center; + align-items: center; + margin-left: 5px; + margin-right: 5px; +} + +/* !important used here to override a MUI icon style to work in non-MUI datepicker */ +.datePicker-icon { + color: #373d4d; + height: 1rem !important; + width: 1rem !important; + cursor: pointer; +} + +.react-datepicker__today-button:hover { + background-color: #d3d3d3; +} + +.datePickerLabelUTCText { + font-weight: 100; + font-size: 12px; +} diff --git a/src/components/DateTimeRangeSelector/DateTimeRangeSelector.jsx b/src/components/DateTimeRangeSelector/DateTimeRangeSelector.jsx index 58ce1a00..c0edc89e 100644 --- a/src/components/DateTimeRangeSelector/DateTimeRangeSelector.jsx +++ b/src/components/DateTimeRangeSelector/DateTimeRangeSelector.jsx @@ -2,12 +2,16 @@ import React, { useEffect, useState } from 'react' import './DateTimeRangeSelector.css' import { useSelector, useDispatch } from 'react-redux' import 'react-tooltip/dist/react-tooltip.css' -import { Tooltip } from 'react-tooltip' -import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined' -import DateTimeRangePicker from '@wojtekmaj/react-datetimerange-picker' import { setSearchDateRangeValue } from '../../redux/slices/mainSlice' +import CalendarTodayIcon from '@mui/icons-material/CalendarToday' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +import { convertToUTC } from '../../utils/datetime' const DateTimeRangeSelector = () => { + dayjs.extend(utc) const dispatch = useDispatch() const _selectedCollectionData = useSelector( (state) => state.mainSlice.selectedCollectionData @@ -15,119 +19,149 @@ const DateTimeRangeSelector = () => { const _hasCollectionChanged = useSelector( (state) => state.mainSlice.hasCollectionChanged ) - const _SearchDateRangeValue = useSelector( (state) => state.mainSlice.searchDateRangeValue ) - - const [startDate, setstartDate] = useState() - const [datePickerValue, setDatePickerValue] = useState(_SearchDateRangeValue) - - const [temporalRangeFound, settemporalRangeFound] = useState(false) + const _hasLeftPanelTabChanged = useSelector( + (state) => state.mainSlice.hasLeftPanelTabChanged + ) + const [startDate, setstartDate] = useState(_SearchDateRangeValue[0]) + const [endDate, setendDate] = useState(_SearchDateRangeValue[1]) useEffect(() => { - let collectionEndDateOrCurrent - - if (_selectedCollectionData) { - const startDateFromCollection = new Date( - _selectedCollectionData.extent.temporal.interval[0] - ) - settemporalRangeFound(true) - setstartDate(startDateFromCollection) - collectionEndDateOrCurrent = - _selectedCollectionData.extent.temporal.interval[0][1] !== null - ? new Date(_selectedCollectionData.extent.temporal.interval[0][1]) - : new Date() + if (!_selectedCollectionData) { + return } + + const currentDateUTCString = dayjs(new Date()).utc().startOf('day').format() + const collectionEndDateOrCurrentLocal = + _selectedCollectionData.extent.temporal.interval[0][1] !== null + ? convertToUTC(_selectedCollectionData.extent.temporal.interval[0][1]) + : convertToUTC(currentDateUTCString) + // if temporal range not in last two week on init, set to match collection - if (_selectedCollectionData && !_hasCollectionChanged) { + if (!_hasCollectionChanged) { if ( - (datePickerValue[0] < - new Date(_selectedCollectionData.extent.temporal.interval[0][0]) && - datePickerValue[1] < - new Date(_selectedCollectionData.extent.temporal.interval[0][0])) || - (datePickerValue[0] > collectionEndDateOrCurrent && - datePickerValue[1] > collectionEndDateOrCurrent) + (startDate < + convertToUTC( + _selectedCollectionData.extent.temporal.interval[0][0] + ) && + endDate < + convertToUTC( + _selectedCollectionData.extent.temporal.interval[0][0] + )) || + (startDate > collectionEndDateOrCurrentLocal && + endDate > collectionEndDateOrCurrentLocal) ) { - setDatePickerValue([ - new Date(_selectedCollectionData.extent.temporal.interval[0][0]), - collectionEndDateOrCurrent - ]) - } - } - if (_selectedCollectionData && _hasCollectionChanged) { - if (datePickerValue) { - if ( - (datePickerValue[0] < - new Date(_selectedCollectionData.extent.temporal.interval[0][0]) && - datePickerValue[1] < - new Date( - _selectedCollectionData.extent.temporal.interval[0][0] - )) || - (datePickerValue[0] > collectionEndDateOrCurrent && - datePickerValue[1] > collectionEndDateOrCurrent) - ) { - setDatePickerValue([ - new Date(_selectedCollectionData.extent.temporal.interval[0][0]), - collectionEndDateOrCurrent - ]) - } - } else { - setDatePickerValue([ - new Date(_selectedCollectionData.extent.temporal.interval[0][0]), - collectionEndDateOrCurrent - ]) + setstartDate( + convertToUTC(_selectedCollectionData.extent.temporal.interval[0][0]) + ) + setendDate(collectionEndDateOrCurrentLocal) } + } else { + setstartDate(convertToUTC(_SearchDateRangeValue[0])) + setendDate(convertToUTC(_SearchDateRangeValue[1])) } }, [_selectedCollectionData]) useEffect(() => { - dispatch(setSearchDateRangeValue(datePickerValue)) - }, [datePickerValue]) + let correctStartSearchDate + let correctEndSearchDate + + const StartDateAsDateObject = + startDate instanceof Date ? startDate : new Date(startDate) + const offsetInMilliseconds = + StartDateAsDateObject.getTimezoneOffset() * 60 * 1000 + + if (startDate instanceof Date) { + correctStartSearchDate = new Date( + startDate.getTime() - offsetInMilliseconds + ).toISOString() + } else { + correctStartSearchDate = new Date(startDate).toISOString() + } + + if (endDate instanceof Date) { + correctEndSearchDate = new Date( + endDate.getTime() - offsetInMilliseconds + ).toISOString() + } else { + correctEndSearchDate = new Date(endDate).toISOString() + } + + dispatch( + setSearchDateRangeValue([correctStartSearchDate, correctEndSearchDate]) + ) + }, [startDate, endDate]) + + useEffect(() => { + if (_hasLeftPanelTabChanged) { + setstartDate(convertToUTC(_SearchDateRangeValue[0])) + setendDate(convertToUTC(_SearchDateRangeValue[1])) + } + }, []) return (
- +
+ + } + toggleCalendarOnIconClick + closeOnScroll={true} + peekNextMonth + showMonthDropdown + showYearDropdown + dropdownMode="select" + dateFormat="yyyy-MM-dd" + popperPlacement="top-end" + onChange={(date) => setstartDate(date)} + > + to + + } + toggleCalendarOnIconClick + closeOnScroll={true} + showMonthDropdown + showYearDropdown + dropdownMode="select" + dateFormat="yyyy-MM-dd" + popperPlacement="top-end" + onChange={(date) => setendDate(date)} + > +
) } diff --git a/src/components/Layout/Content/LeftContent/LeftContent.jsx b/src/components/Layout/Content/LeftContent/LeftContent.jsx index 16032ce1..d6add0e5 100644 --- a/src/components/Layout/Content/LeftContent/LeftContent.jsx +++ b/src/components/Layout/Content/LeftContent/LeftContent.jsx @@ -4,7 +4,10 @@ import Search from '../../../Search/Search' import PopupResults from '../../../PopupResults/PopupResults' import { useSelector, useDispatch } from 'react-redux' import { debounceNewSearch } from '../../../../utils/searchHelper' -import { settabSelected } from '../../../../redux/slices/mainSlice' +import { + settabSelected, + sethasLeftPanelTabChanged +} from '../../../../redux/slices/mainSlice' const LeftContent = () => { const dispatch = useDispatch() @@ -34,6 +37,7 @@ const LeftContent = () => { } function setDetailsTab() { dispatch(settabSelected('details')) + dispatch(sethasLeftPanelTabChanged(true)) } return ( diff --git a/src/components/Layout/Content/RightContent/RightContent.jsx b/src/components/Layout/Content/RightContent/RightContent.jsx index bfbe0e03..dc0a8690 100644 --- a/src/components/Layout/Content/RightContent/RightContent.jsx +++ b/src/components/Layout/Content/RightContent/RightContent.jsx @@ -292,6 +292,14 @@ const RightContent = () => { ) : null} + {_searchResults?.searchType !== 'AggregatedResults' && + !_isDrawingEnabled && + _searchResults && + _searchResults.features?.length === 0 ? ( +
+
No Results Found
+
+ ) : null} {_searchLoading ? (
{ }, colorPrimary: { '&.Mui-checked': { - color: '#373d4d' + color: '#fff' } }, track: { - opacity: 0.9, backgroundColor: '#dedede', '.Mui-checked.Mui-checked + &': { - backgroundColor: '#a9b0c1' + backgroundColor: '#6cc24a' } } } diff --git a/src/redux/slices/mainSlice.js b/src/redux/slices/mainSlice.js index e333a523..e7259640 100644 --- a/src/redux/slices/mainSlice.js +++ b/src/redux/slices/mainSlice.js @@ -43,7 +43,8 @@ const initialState = { selectedCollection: 'Select Collection', tabSelected: 'filters', selectedPopupResultIndex: 0, - autoCenterOnItemChanged: false + autoCenterOnItemChanged: false, + hasLeftPanelTabChanged: false } // next, for every key in the initialState @@ -171,6 +172,9 @@ export const mainSlice = createSlice({ }, setautoCenterOnItemChanged: (state, action) => { state.autoCenterOnItemChanged = action.payload + }, + sethasLeftPanelTabChanged: (state, action) => { + state.hasLeftPanelTabChanged = action.payload } } }) @@ -217,5 +221,6 @@ export const { setreferenceLayers } = mainSlice.actions export const { settabSelected } = mainSlice.actions export const { setselectedPopupResultIndex } = mainSlice.actions export const { setautoCenterOnItemChanged } = mainSlice.actions +export const { sethasLeftPanelTabChanged } = mainSlice.actions export default mainSlice.reducer diff --git a/src/services/get-search-service.js b/src/services/get-search-service.js index f1159fa0..d85ec32d 100644 --- a/src/services/get-search-service.js +++ b/src/services/get-search-service.js @@ -4,7 +4,8 @@ import { setSearchLoading, setSearchResults, setmappedScenes, - settabSelected + settabSelected, + sethasLeftPanelTabChanged } from '../redux/slices/mainSlice' import { addDataToLayer, footprintLayerStyle } from '../utils/mapHelper' @@ -36,6 +37,7 @@ export async function SearchService(searchParams, typeOfSearch) { store.dispatch(setSearchLoading(false)) store.dispatch(setClickResults(json.features)) store.dispatch(settabSelected('details')) + store.dispatch(sethasLeftPanelTabChanged(true)) } }) .catch((error) => { diff --git a/src/utils/datetime.js b/src/utils/datetime.js index ef709d99..974f4891 100644 --- a/src/utils/datetime.js +++ b/src/utils/datetime.js @@ -1,24 +1,25 @@ -// convert DateTime Range Picker value RFC3339 used by STAC API datetime parameter -// STAC API uses inclusive start and end datetime ranges -const convertDateTime = (dt, start) => - `${dt.getUTCFullYear()}-${(dt.getUTCMonth() + 1) - .toString() - .padStart(2, '0')}-` + - `${dt.getUTCDate().toString().padStart(2, '0')}T${ - start ? '00:00:00' : '23:59:59.999' - }Z` - -const convertStartDateTime = (dt) => convertDateTime(dt, true) -const convertEndDateTime = (dt) => convertDateTime(dt, false) - // date used for STAC API request for mosaic view export const convertDate = (dateTimeRef) => { - const fromDate = convertStartDateTime(dateTimeRef[0]) - const toDate = convertEndDateTime(dateTimeRef[1]) - - return `${fromDate}/${toDate}` + return `${dateTimeRef[0]}/${dateTimeRef[1]}` } // date used for STAC API request for scene view export const convertDateForURL = (dateTimeRef) => encodeURIComponent(convertDate(dateTimeRef)) + +export function convertToUTC(dateString) { + const [datePart, timePart] = dateString.split('T') + const [year, month, day] = datePart.split('-').map(Number) + const [hours, minutes, seconds] = timePart.slice(0, -1).split(':').map(Number) + const utcDate = new Date( + Date.UTC(year, month - 1, day, hours, minutes, seconds) + ) + + // Get the timezone offset in minutes + const offsetInMinutes = utcDate.getTimezoneOffset() + + // Add the offset to the UTC date to get the correct date for UI presentation in datepicker + const correctDate = new Date(utcDate.getTime() + offsetInMinutes * 60 * 1000) + + return correctDate +} diff --git a/src/utils/mapHelper.js b/src/utils/mapHelper.js index 4330a70d..7dded61c 100644 --- a/src/utils/mapHelper.js +++ b/src/utils/mapHelper.js @@ -10,7 +10,8 @@ import { setimageOverlayLoading, setSearchLoading, settabSelected, - setCurrentPopupResult + setCurrentPopupResult, + sethasLeftPanelTabChanged } from '../redux/slices/mainSlice' import { searchGridCodeScenes } from './searchHelper' import debounce from './debounce' @@ -127,6 +128,7 @@ export function mapClickHandler(e) { // push to store store.dispatch(setClickResults(intersectingFeatures)) store.dispatch(settabSelected('details')) + store.dispatch(sethasLeftPanelTabChanged(true)) } } else if (_searchType === 'grid-code') { for (const i in intersectingFeatures) {