diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs diff --git a/favicon-DRC.ico b/favicon-DRC.ico new file mode 100644 index 000000000..466e55f28 Binary files /dev/null and b/favicon-DRC.ico differ diff --git a/favicon.ico b/favicon.ico index 8461691f1..e74167870 100644 Binary files a/favicon.ico and b/favicon.ico differ diff --git a/index.html b/index.html index a290d99ad..e383433d6 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,6 @@ - @@ -18,8 +17,6 @@ - - Half-Earth Project Map diff --git a/package.json b/package.json index c7149f0ae..27d8d0407 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,21 @@ "type": "module", "dependencies": { "@arcgis/core": "4.30.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", "@esri/arcgis-rest-feature-layer": "^3.4.3", "@esri/arcgis-rest-request": "^3.4.3", "@loadable/component": "^5.10.1", + "@mui/icons-material": "^6.0.2", + "@mui/material": "^6.0.2", "@tanstack/react-query": "^4.1.0", "@tippyjs/react": "^4.2.5", "@transifex/native": "^4.1.0", "@transifex/react": "^4.1.0", + "@vitejs/plugin-basic-ssl": "^1.1.0", "@vitejs/plugin-react": "^4.3.1", "assert": "^2.0.0", + "chart.js": "^4.4.3", "classnames": "^2.2.6", "contentful": "^7.6.0", "cross-var": "^1.1.0", @@ -30,6 +36,7 @@ "qs": "^6.7.0", "rc-slider": "^9.7.2", "react": "17.0.2", + "react-chartjs-2": "^5.2.0", "react-copy-to-clipboard": "^5.0.1", "react-dom": "^17.0.2", "react-dropzone": "^11.4.0", diff --git a/public/favicon-DRC.ico b/public/favicon-DRC.ico new file mode 100644 index 000000000..ea084e3d7 Binary files /dev/null and b/public/favicon-DRC.ico differ diff --git a/public/favicon-epa.ico b/public/favicon-epa.ico new file mode 100644 index 000000000..1f337e469 Binary files /dev/null and b/public/favicon-epa.ico differ diff --git a/public/favicon-gin.ico b/public/favicon-gin.ico new file mode 100644 index 000000000..e74167870 Binary files /dev/null and b/public/favicon-gin.ico differ diff --git a/public/manifest.json b/public/manifest.json index 224109c11..5b1ab6ed2 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Half Earth Map", - "name": "Half-Earth Project Map", + "short_name": "EPA National Biodiversity", + "name": "EPA National Biodiversity", "icons": [ { "src": "favicon.ico", diff --git a/src/app.jsx b/src/app.jsx index 98c93c711..451a0508b 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -31,6 +31,7 @@ const FeaturedGlobe = loadable(() => import('pages/featured-globe')); const NationalReportCard = loadable(() => import('pages/nrc')); const NationalReportCardLanding = loadable(() => import('pages/nrc-landing')); const AreaOfInterest = loadable(() => import('pages/aoi')); +const DashboardComponent = loadable(() => import('pages/dashboard')); const mapStateToProps = ({ location }) => ({ route: location.routesMap[location.type], @@ -62,6 +63,10 @@ function AppLayout(props) { ); case 'aoi': return ; + case 'dashboard': + return ; + case 'dashboard-species': + return ; default: return isMobile ? : ; } diff --git a/src/assets/icons/analytics.svg b/src/assets/icons/analytics.svg new file mode 100644 index 000000000..b9e077f5c --- /dev/null +++ b/src/assets/icons/analytics.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/arrow-down-solid.svg b/src/assets/icons/arrow-down-solid.svg new file mode 100644 index 000000000..2c96bb5e7 --- /dev/null +++ b/src/assets/icons/arrow-down-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/arrow-up-solid.svg b/src/assets/icons/arrow-up-solid.svg new file mode 100644 index 000000000..7422915c3 --- /dev/null +++ b/src/assets/icons/arrow-up-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/bird_icon.svg b/src/assets/icons/bird_icon.svg new file mode 100644 index 000000000..1c07a478c --- /dev/null +++ b/src/assets/icons/bird_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dashboard/amphibian_icon.svg b/src/assets/icons/dashboard/amphibian_icon.svg new file mode 100644 index 000000000..a10e71db2 --- /dev/null +++ b/src/assets/icons/dashboard/amphibian_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dashboard/ant_icon.svg b/src/assets/icons/dashboard/ant_icon.svg new file mode 100644 index 000000000..36328d40f --- /dev/null +++ b/src/assets/icons/dashboard/ant_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dashboard/bird_icon.svg b/src/assets/icons/dashboard/bird_icon.svg new file mode 100644 index 000000000..1c07a478c --- /dev/null +++ b/src/assets/icons/dashboard/bird_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dashboard/butterfly_icon.svg b/src/assets/icons/dashboard/butterfly_icon.svg new file mode 100644 index 000000000..407c83dce --- /dev/null +++ b/src/assets/icons/dashboard/butterfly_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dashboard/mammal_icon.svg b/src/assets/icons/dashboard/mammal_icon.svg new file mode 100644 index 000000000..2aae7479a --- /dev/null +++ b/src/assets/icons/dashboard/mammal_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dashboard/odonate_icon.svg b/src/assets/icons/dashboard/odonate_icon.svg new file mode 100644 index 000000000..b1eeadfae --- /dev/null +++ b/src/assets/icons/dashboard/odonate_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dashboard/reptile_icon.svg b/src/assets/icons/dashboard/reptile_icon.svg new file mode 100644 index 000000000..1e973c8fe --- /dev/null +++ b/src/assets/icons/dashboard/reptile_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dashboard/tree_icon.svg b/src/assets/icons/dashboard/tree_icon.svg new file mode 100644 index 000000000..ee09e8d3a --- /dev/null +++ b/src/assets/icons/dashboard/tree_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/duiker_icon_black.svg b/src/assets/icons/duiker_icon_black.svg new file mode 100644 index 000000000..6c861129c --- /dev/null +++ b/src/assets/icons/duiker_icon_black.svg @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/elephant.svg b/src/assets/icons/elephant.svg new file mode 100644 index 000000000..6fb18bd88 --- /dev/null +++ b/src/assets/icons/elephant.svg @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/src/assets/icons/gauge_icon.svg b/src/assets/icons/gauge_icon.svg new file mode 100644 index 000000000..2a1aea659 --- /dev/null +++ b/src/assets/icons/gauge_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/house-solid.svg b/src/assets/icons/house-solid.svg new file mode 100644 index 000000000..374579be5 --- /dev/null +++ b/src/assets/icons/house-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/minus-solid.svg b/src/assets/icons/minus-solid.svg new file mode 100644 index 000000000..fe94d5749 --- /dev/null +++ b/src/assets/icons/minus-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/moon-regular.svg b/src/assets/icons/moon-regular.svg new file mode 100644 index 000000000..112fb45d1 --- /dev/null +++ b/src/assets/icons/moon-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/okapi.svg b/src/assets/icons/okapi.svg new file mode 100644 index 000000000..6def296bb --- /dev/null +++ b/src/assets/icons/okapi.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/icons/stacks.svg b/src/assets/icons/stacks.svg new file mode 100644 index 000000000..964df48ed --- /dev/null +++ b/src/assets/icons/stacks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/sun-regular.svg b/src/assets/icons/sun-regular.svg new file mode 100644 index 000000000..16a9efbfa --- /dev/null +++ b/src/assets/icons/sun-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/timeline.svg b/src/assets/icons/timeline.svg new file mode 100644 index 000000000..9dfe6fae4 --- /dev/null +++ b/src/assets/icons/timeline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/user_icon.svg b/src/assets/icons/user_icon.svg new file mode 100644 index 000000000..ed18bd6b2 --- /dev/null +++ b/src/assets/icons/user_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/Choeropsis-liberiensis.jpeg b/src/assets/images/Choeropsis-liberiensis.jpeg new file mode 100644 index 000000000..27c7c4cf8 Binary files /dev/null and b/src/assets/images/Choeropsis-liberiensis.jpeg differ diff --git a/src/assets/images/Grey-winged_Robin-Chat_imported_from_iNaturalist_photo_4170503_on_23_February_2024.jpg b/src/assets/images/Grey-winged_Robin-Chat_imported_from_iNaturalist_photo_4170503_on_23_February_2024.jpg new file mode 100644 index 000000000..f88cac4ed Binary files /dev/null and b/src/assets/images/Grey-winged_Robin-Chat_imported_from_iNaturalist_photo_4170503_on_23_February_2024.jpg differ diff --git a/src/assets/images/amphibians.svg b/src/assets/images/amphibians.svg new file mode 100644 index 000000000..f1d96422e --- /dev/null +++ b/src/assets/images/amphibians.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/apalis.jpg b/src/assets/images/apalis.jpg new file mode 100644 index 000000000..79fa244ec Binary files /dev/null and b/src/assets/images/apalis.jpg differ diff --git a/src/assets/images/birds.svg b/src/assets/images/birds.svg new file mode 100644 index 000000000..d1656aa4f --- /dev/null +++ b/src/assets/images/birds.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/dashboard/Chiromantis_rufescens.jpg b/src/assets/images/dashboard/Chiromantis_rufescens.jpg new file mode 100644 index 000000000..357e6dfd6 Binary files /dev/null and b/src/assets/images/dashboard/Chiromantis_rufescens.jpg differ diff --git a/src/assets/images/dashboard/Congolacerta_vauereselli_hendrick_hinkel.jpg b/src/assets/images/dashboard/Congolacerta_vauereselli_hendrick_hinkel.jpg new file mode 100644 index 000000000..d47032ed2 Binary files /dev/null and b/src/assets/images/dashboard/Congolacerta_vauereselli_hendrick_hinkel.jpg differ diff --git a/src/assets/images/dashboard/Hyperolius_castaneus.jpg b/src/assets/images/dashboard/Hyperolius_castaneus.jpg new file mode 100644 index 000000000..3fffeef64 Binary files /dev/null and b/src/assets/images/dashboard/Hyperolius_castaneus.jpg differ diff --git a/src/assets/images/dashboard/Hyperolius_tuberculatus_brian_gratwicke.jpg b/src/assets/images/dashboard/Hyperolius_tuberculatus_brian_gratwicke.jpg new file mode 100644 index 000000000..23c1e2dd4 Binary files /dev/null and b/src/assets/images/dashboard/Hyperolius_tuberculatus_brian_gratwicke.jpg differ diff --git a/src/assets/images/dashboard/Leptopelis_christyi_gauvain_saucy.jpeg b/src/assets/images/dashboard/Leptopelis_christyi_gauvain_saucy.jpeg new file mode 100644 index 000000000..a6d2fb2c2 Binary files /dev/null and b/src/assets/images/dashboard/Leptopelis_christyi_gauvain_saucy.jpeg differ diff --git a/src/assets/images/dashboard/Myotis_bocagii.jpg b/src/assets/images/dashboard/Myotis_bocagii.jpg new file mode 100644 index 000000000..0d77381a8 Binary files /dev/null and b/src/assets/images/dashboard/Myotis_bocagii.jpg differ diff --git a/src/assets/images/dashboard/amphibian_icon_black.png b/src/assets/images/dashboard/amphibian_icon_black.png new file mode 100644 index 000000000..6ef11cfba Binary files /dev/null and b/src/assets/images/dashboard/amphibian_icon_black.png differ diff --git a/src/assets/images/dashboard/amphibian_icon_white.png b/src/assets/images/dashboard/amphibian_icon_white.png new file mode 100644 index 000000000..c6e23d6e3 Binary files /dev/null and b/src/assets/images/dashboard/amphibian_icon_white.png differ diff --git a/src/assets/images/dashboard/ant_icon_black.png b/src/assets/images/dashboard/ant_icon_black.png new file mode 100644 index 000000000..3728d5151 Binary files /dev/null and b/src/assets/images/dashboard/ant_icon_black.png differ diff --git a/src/assets/images/dashboard/ant_icon_white.png b/src/assets/images/dashboard/ant_icon_white.png new file mode 100644 index 000000000..6144ab2c7 Binary files /dev/null and b/src/assets/images/dashboard/ant_icon_white.png differ diff --git a/src/assets/images/dashboard/bird_icon_black.png b/src/assets/images/dashboard/bird_icon_black.png new file mode 100644 index 000000000..5c7d1bbf7 Binary files /dev/null and b/src/assets/images/dashboard/bird_icon_black.png differ diff --git a/src/assets/images/dashboard/bird_icon_white.png b/src/assets/images/dashboard/bird_icon_white.png new file mode 100644 index 000000000..a4f1a023c Binary files /dev/null and b/src/assets/images/dashboard/bird_icon_white.png differ diff --git a/src/assets/images/dashboard/bonobo.jpg b/src/assets/images/dashboard/bonobo.jpg new file mode 100644 index 000000000..87d8a1122 Binary files /dev/null and b/src/assets/images/dashboard/bonobo.jpg differ diff --git a/src/assets/images/dashboard/butterfly_icon_black.png b/src/assets/images/dashboard/butterfly_icon_black.png new file mode 100644 index 000000000..3a3364f2f Binary files /dev/null and b/src/assets/images/dashboard/butterfly_icon_black.png differ diff --git a/src/assets/images/dashboard/butterfly_icon_white.png b/src/assets/images/dashboard/butterfly_icon_white.png new file mode 100644 index 000000000..72a7a0f14 Binary files /dev/null and b/src/assets/images/dashboard/butterfly_icon_white.png differ diff --git a/src/assets/images/dashboard/mammal_icon_black.png b/src/assets/images/dashboard/mammal_icon_black.png new file mode 100644 index 000000000..f0c215775 Binary files /dev/null and b/src/assets/images/dashboard/mammal_icon_black.png differ diff --git a/src/assets/images/dashboard/mammal_icon_white.png b/src/assets/images/dashboard/mammal_icon_white.png new file mode 100644 index 000000000..eec9652a5 Binary files /dev/null and b/src/assets/images/dashboard/mammal_icon_white.png differ diff --git a/src/assets/images/dashboard/odonate_icon_black.png b/src/assets/images/dashboard/odonate_icon_black.png new file mode 100644 index 000000000..9e0ccb2e4 Binary files /dev/null and b/src/assets/images/dashboard/odonate_icon_black.png differ diff --git a/src/assets/images/dashboard/odonate_icon_white.png b/src/assets/images/dashboard/odonate_icon_white.png new file mode 100644 index 000000000..93741ef69 Binary files /dev/null and b/src/assets/images/dashboard/odonate_icon_white.png differ diff --git a/src/assets/images/dashboard/okapi.jpg b/src/assets/images/dashboard/okapi.jpg new file mode 100644 index 000000000..06c00aeca Binary files /dev/null and b/src/assets/images/dashboard/okapi.jpg differ diff --git a/src/assets/images/dashboard/peacock.jpg b/src/assets/images/dashboard/peacock.jpg new file mode 100644 index 000000000..fd6f794c7 Binary files /dev/null and b/src/assets/images/dashboard/peacock.jpg differ diff --git a/src/assets/images/dashboard/reptile_icon_black.png b/src/assets/images/dashboard/reptile_icon_black.png new file mode 100644 index 000000000..ae47e0ed4 Binary files /dev/null and b/src/assets/images/dashboard/reptile_icon_black.png differ diff --git a/src/assets/images/dashboard/reptile_icon_white.png b/src/assets/images/dashboard/reptile_icon_white.png new file mode 100644 index 000000000..1746e4490 Binary files /dev/null and b/src/assets/images/dashboard/reptile_icon_white.png differ diff --git a/src/assets/images/dashboard/shi_legend.png b/src/assets/images/dashboard/shi_legend.png new file mode 100644 index 000000000..505454233 Binary files /dev/null and b/src/assets/images/dashboard/shi_legend.png differ diff --git a/src/assets/images/dashboard/spi_legend.png b/src/assets/images/dashboard/spi_legend.png new file mode 100644 index 000000000..c8c82785e Binary files /dev/null and b/src/assets/images/dashboard/spi_legend.png differ diff --git a/src/assets/images/dashboard/tree_icon_white.png b/src/assets/images/dashboard/tree_icon_white.png new file mode 100644 index 000000000..dc94df978 Binary files /dev/null and b/src/assets/images/dashboard/tree_icon_white.png differ diff --git a/src/assets/images/dashboard/viper.jpg b/src/assets/images/dashboard/viper.jpg new file mode 100644 index 000000000..0e9897e71 Binary files /dev/null and b/src/assets/images/dashboard/viper.jpg differ diff --git a/src/assets/images/drc_metrics_logos.jpg b/src/assets/images/drc_metrics_logos.jpg new file mode 100644 index 000000000..6557d81dc Binary files /dev/null and b/src/assets/images/drc_metrics_logos.jpg differ diff --git a/src/assets/images/drc_region_logos.jpg b/src/assets/images/drc_region_logos.jpg new file mode 100644 index 000000000..ec46529f9 Binary files /dev/null and b/src/assets/images/drc_region_logos.jpg differ diff --git a/src/assets/images/egg.jpeg b/src/assets/images/egg.jpeg new file mode 100644 index 000000000..785b141ff Binary files /dev/null and b/src/assets/images/egg.jpeg differ diff --git a/src/assets/images/frog.jpg b/src/assets/images/frog.jpg new file mode 100644 index 000000000..f8da5cb46 Binary files /dev/null and b/src/assets/images/frog.jpg differ diff --git a/src/assets/images/hyperolius.jpg b/src/assets/images/hyperolius.jpg new file mode 100644 index 000000000..6b0806564 Binary files /dev/null and b/src/assets/images/hyperolius.jpg differ diff --git a/src/assets/images/lophocebus.jpg b/src/assets/images/lophocebus.jpg new file mode 100644 index 000000000..af0592d30 Binary files /dev/null and b/src/assets/images/lophocebus.jpg differ diff --git a/src/assets/images/mammals.svg b/src/assets/images/mammals.svg new file mode 100644 index 000000000..a28016c83 --- /dev/null +++ b/src/assets/images/mammals.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/okapi_Derek_Keats_Flickr_CC-BY-2_0.jpg b/src/assets/images/okapi_Derek_Keats_Flickr_CC-BY-2_0.jpg new file mode 100644 index 000000000..a5faf3ce7 Binary files /dev/null and b/src/assets/images/okapi_Derek_Keats_Flickr_CC-BY-2_0.jpg differ diff --git a/src/assets/images/pilio.jpg b/src/assets/images/pilio.jpg new file mode 100644 index 000000000..31b022903 Binary files /dev/null and b/src/assets/images/pilio.jpg differ diff --git a/src/assets/images/reptiles.svg b/src/assets/images/reptiles.svg new file mode 100644 index 000000000..974b0bc49 --- /dev/null +++ b/src/assets/images/reptiles.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/streak-throated.jpg b/src/assets/images/streak-throated.jpg new file mode 100644 index 000000000..20652752e Binary files /dev/null and b/src/assets/images/streak-throated.jpg differ diff --git a/src/assets/images/trioceros.jpg b/src/assets/images/trioceros.jpg new file mode 100644 index 000000000..f437ad7a9 Binary files /dev/null and b/src/assets/images/trioceros.jpg differ diff --git a/src/assets/images/zebra-duiker.jpg b/src/assets/images/zebra-duiker.jpg new file mode 100644 index 000000000..fa4cd4da1 Binary files /dev/null and b/src/assets/images/zebra-duiker.jpg differ diff --git a/src/assets/logos/Guyana_PAC.png b/src/assets/logos/Guyana_PAC.png new file mode 100644 index 000000000..d4d7bfaa5 Binary files /dev/null and b/src/assets/logos/Guyana_PAC.png differ diff --git a/src/assets/logos/SL_flag.png b/src/assets/logos/SL_flag.png new file mode 100644 index 000000000..983a7e1a0 Binary files /dev/null and b/src/assets/logos/SL_flag.png differ diff --git a/src/assets/logos/epa_logo_transparent.png b/src/assets/logos/epa_logo_transparent.png new file mode 100644 index 000000000..33bdee320 Binary files /dev/null and b/src/assets/logos/epa_logo_transparent.png differ diff --git a/src/assets/logos/favicon-drc.ico b/src/assets/logos/favicon-drc.ico new file mode 100644 index 000000000..ea084e3d7 Binary files /dev/null and b/src/assets/logos/favicon-drc.ico differ diff --git a/src/assets/logos/favicon-epa.ico b/src/assets/logos/favicon-epa.ico new file mode 100644 index 000000000..1f337e469 Binary files /dev/null and b/src/assets/logos/favicon-epa.ico differ diff --git a/src/assets/logos/favicon-sle.ico b/src/assets/logos/favicon-sle.ico new file mode 100644 index 000000000..f95b719ba Binary files /dev/null and b/src/assets/logos/favicon-sle.ico differ diff --git a/src/assets/logos/guinea.jpeg b/src/assets/logos/guinea.jpeg new file mode 100644 index 000000000..b07f78848 Binary files /dev/null and b/src/assets/logos/guinea.jpeg differ diff --git a/src/assets/logos/guinea.png b/src/assets/logos/guinea.png new file mode 100644 index 000000000..0cc51e0c1 Binary files /dev/null and b/src/assets/logos/guinea.png differ diff --git a/src/assets/logos/institut-congolais.png b/src/assets/logos/institut-congolais.png new file mode 100644 index 000000000..325d35d8d Binary files /dev/null and b/src/assets/logos/institut-congolais.png differ diff --git a/src/components/charts/distribution-chart/distribution-chart-component.jsx b/src/components/charts/distribution-chart/distribution-chart-component.jsx new file mode 100644 index 000000000..6cb40a4ae --- /dev/null +++ b/src/components/charts/distribution-chart/distribution-chart-component.jsx @@ -0,0 +1,21 @@ +/* eslint-disable camelcase */ +import React from 'react'; +import { Bar } from 'react-chartjs-2'; + +import styles from './distribution-chart-styles.module.scss'; + +function DistributionsChartComponent(props) { + const { options, data } = props; + + return ( +
+ {data && ( +
+ +
+ )} +
+ ); +} + +export default DistributionsChartComponent; diff --git a/src/components/charts/distribution-chart/distribution-chart-styles.module.scss b/src/components/charts/distribution-chart/distribution-chart-styles.module.scss new file mode 100644 index 000000000..915d90677 --- /dev/null +++ b/src/components/charts/distribution-chart/distribution-chart-styles.module.scss @@ -0,0 +1,28 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + padding: 10px 5px 10px 26px; + flex-grow: 1; + align-items: center; + gap: 8px; + width: 100%; + + .title{ + color: $white; + font-family: $font-family-1; + font-size: $font-size-sm; + font-style: normal; + font-weight: 700; + line-height: normal; + text-transform: uppercase; + } + + .chart{ + width: 100%; + } +} diff --git a/src/components/charts/distribution-chart/index.js b/src/components/charts/distribution-chart/index.js new file mode 100644 index 000000000..2a8b8c479 --- /dev/null +++ b/src/components/charts/distribution-chart/index.js @@ -0,0 +1,3 @@ +import Component from './distribution-chart-component'; + +export default Component; diff --git a/src/components/charts/spi-arc-chart/index.js b/src/components/charts/spi-arc-chart/index.js new file mode 100644 index 000000000..8db701c31 --- /dev/null +++ b/src/components/charts/spi-arc-chart/index.js @@ -0,0 +1,3 @@ +import Component from './spi-arc-chart-component'; + +export default Component; diff --git a/src/components/charts/spi-arc-chart/spi-arc-chart-component.jsx b/src/components/charts/spi-arc-chart/spi-arc-chart-component.jsx new file mode 100644 index 000000000..fe07c320e --- /dev/null +++ b/src/components/charts/spi-arc-chart/spi-arc-chart-component.jsx @@ -0,0 +1,109 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { Doughnut } from 'react-chartjs-2'; + +import { useT } from '@transifex/react'; + +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + ArcElement, + Title, + Tooltip, + Legend, +} from 'chart.js'; +import cx from 'classnames'; + +import { LightModeContext } from '../../../context/light-mode'; + +import styles from './spi-arc-chart-styles.module.scss'; + +ChartJS.register( + CategoryScale, + LinearScale, + ArcElement, + Title, + Tooltip, + Legend +); + +function SpiArcChartComponent(props) { + const t = useT(); + const { scores, width, height, data, img, species, value, isPercent } = props; + const { lightMode } = useContext(LightModeContext); + const [score, setScore] = useState(0); + + const doughnutOptions = { + cutout: '80%', + radius: '100%', + rotation: -90, + responsive: false, + circumference: 180, + hoverOffset: 5, + animation: { + animateRotate: true, + animateScale: false, + }, + plugins: { + legend: { + display: false, + }, + }, + layout: { + padding: { + left: 5, + right: 5, + top: 5, + bottom: 5, + }, + }, + }; + + const arcChartHeight = height || 100; + const arcChartWidth = width || 120; + + const getPercentage = () => { + const { count, total } = scores[species]; + + if (!value) { + const percent = (count / total) * 100 || 0; + return [percent, 100 - percent]; + } + return [count, 100 - count]; + }; + + const getScore = () => { + if (!isPercent) return score; + return `${score}%`; + }; + + useEffect(() => { + if (!value) return; + setScore(value.toFixed(1)); + }, [value]); + + return ( +
+ {scores && ( + {getPercentage()[0].toFixed(1)} + )} + + {scores && ( +
+ {species} +
+ {scores[species].total} {t(species)} +
+
+ )} + {!scores &&
{getScore()}
} +
+ ); +} + +export default SpiArcChartComponent; diff --git a/src/components/charts/spi-arc-chart/spi-arc-chart-styles.module.scss b/src/components/charts/spi-arc-chart/spi-arc-chart-styles.module.scss new file mode 100644 index 000000000..c7b527503 --- /dev/null +++ b/src/components/charts/spi-arc-chart/spi-arc-chart-styles.module.scss @@ -0,0 +1,63 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.spi{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + --font-color: #{$white}; + + &.light{ + --font-color: #{$black}; + } + + .score{ + color: var(--font-color); + text-align: center; + font-family: $font-family-1; + font-size: $font-size-tick; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-bottom: -20px; + } + + .globalScore{ + color: var(--font-color); + text-align: center; + font-family: $font-family-1; + font-size: $font-size-tick; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-top: -35px; + } + + .taxoGroup{ + display: flex; + flex-direction: column; + margin-top: -60px; + align-items: center; + + .richness{ + color: var(--font-color); + text-align: center; + font-family: $font-family-1; + font-size: $font-size-xxs; + font-style: italic; + font-weight: 400; + line-height: normal; + } + + .score{ + font-size: $font-size-base; + font-weight: 600; + text-transform: uppercase; + margin-bottom: 0; + } + } +} diff --git a/src/components/dashboard-login/dashboard-login-component.jsx b/src/components/dashboard-login/dashboard-login-component.jsx new file mode 100644 index 000000000..f4b12c2fc --- /dev/null +++ b/src/components/dashboard-login/dashboard-login-component.jsx @@ -0,0 +1,80 @@ +import React, { useEffect } from 'react'; + +import { useT } from '@transifex/react'; + +import IdentityManager from '@arcgis/core/identity/IdentityManager'; +import OAuthInfo from '@arcgis/core/identity/OAuthInfo'; +import Portal from '@arcgis/core/portal/Portal'; + +import Button from 'components/button'; + +import styles from './dashboard-login-styles.module.scss'; + +const { ARCGIS_OAUTH_APP_ID } = import.meta.env; + +const info = new OAuthInfo({ + appId: ARCGIS_OAUTH_APP_ID, + popup: false, +}); + +function DashboardLoginComponent(props) { + const { setLoggedIn, setUser } = props; + const t = useT(); + + const handleLogin = () => { + IdentityManager.getCredential(info.portalUrl); + }; + + const handleLoginSuccess = () => { + const portal = new Portal(); + portal.authMode = 'immediate'; + portal.load().then(() => { + setLoggedIn(true); + setUser(portal.user); + }); + }; + + useEffect(() => { + IdentityManager.registerOAuthInfos([info]); + + IdentityManager.checkSignInStatus(info.portalUrl) + .then(handleLoginSuccess) + .catch((error) => { + throw Error(error); + }); + }, []); + + return ( +
+
+ {/* + { + setEmail(event.target.value); + }} + /> + + + { + setPassword(event.target.value); + }} + /> + */} +
+
+ ); +} + +export default DashboardLoginComponent; diff --git a/src/components/dashboard-login/dashboard-login-styles.module.scss b/src/components/dashboard-login/dashboard-login-styles.module.scss new file mode 100644 index 000000000..855982715 --- /dev/null +++ b/src/components/dashboard-login/dashboard-login-styles.module.scss @@ -0,0 +1,35 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + height: 100vh; + + --border-color: #{$brand-color-main}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + .loginForm{ + display: flex; + flex-direction: column; + width: 400px; + padding: 10px; + gap: 10px; + border-radius: 8px; + + .saveButton { + color: var(--save-button-text); + background-color: var(--border-color); + &:hover { + background-color: var(--border-color-hover); + color: var(--save-button-text); + } + } + } +} diff --git a/src/components/dashboard-login/index.js b/src/components/dashboard-login/index.js new file mode 100644 index 000000000..fbacfa624 --- /dev/null +++ b/src/components/dashboard-login/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import DashboardLoginComponent from './dashboard-login-component'; + +function DashboardLogin(props) { + return ; +} + +export default DashboardLogin; diff --git a/src/components/dashboard-nav/dashboard-nav-component.jsx b/src/components/dashboard-nav/dashboard-nav-component.jsx new file mode 100644 index 000000000..4b93c9302 --- /dev/null +++ b/src/components/dashboard-nav/dashboard-nav-component.jsx @@ -0,0 +1,123 @@ +import React, { useContext } from 'react'; + +import { useT } from '@transifex/react'; + +import SouthAmericaIcon from '@mui/icons-material/SouthAmerica'; +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; + +import { NAVIGATION } from 'constants/dashboard-constants.js'; + +import BirdsIcon from 'icons/bird_icon.svg?react'; +import SpeciesIcon from 'icons/gauge_icon.svg?react'; +import HomeIcon from 'icons/house-solid.svg?react'; +import StacksIcon from 'icons/stacks.svg?react'; +import TimeLineIcon from 'icons/timeline.svg?react'; + +import styles from './dashboard-nav-styles.module.scss'; + +function DashboardNavComponent(props) { + const t = useT(); + const { selectedIndex, setSelectedIndex, scientificName, countryISO } = props; + const { lightMode } = useContext(LightModeContext); + + const titles = { + HOME: 'home', + REGIONS: 'regions', + DATA_LAYER: 'data-layer', + BIO_DIVERSITY: 'bio-diversity', + REGION_ANALYSIS: 'region-analysis', + TRENDS: 'trends', + }; + + const updateHistory = (page, title) => { + window.history.pushState({ selectedIndex: page }, '', ``); + setSelectedIndex(page); + }; + + return ( +
+
+ + + + {selectedIndex >= NAVIGATION.SPECIES && + selectedIndex <= NAVIGATION.REGION_ANALYSIS && ( +
+ + +
+ )} + +
+
+ ); +} + +export default DashboardNavComponent; diff --git a/src/components/dashboard-nav/dashboard-nav-styles.module.scss b/src/components/dashboard-nav/dashboard-nav-styles.module.scss new file mode 100644 index 000000000..3be8c2563 --- /dev/null +++ b/src/components/dashboard-nav/dashboard-nav-styles.module.scss @@ -0,0 +1,75 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.sidenav{ + --nav-button-bg: #{$white}; + --svg-fill: #{$white}; + --svg-selected-fill: #{$black}; + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + &.light{ + --nav-button-bg: #{$black}; + --svg-fill: #{$black}; + --svg-selected-fill: #{$white}; + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + @include lightBackdropBlur(); + border: none; + } + + display: flex; + height: 100%; + + .icons{ + display: flex; + flex-direction: column; + height: 100%; + border-right: 1px solid var(--font-color); + + .subNav{ + display: flex; + flex-direction: column; + background-color: rgba(255, 255, 255, 0.5); + } + + button{ + width: 50px; + height: 50px; + + svg{ + width: 30px; + height: 30px; + fill: var(--svg-fill); + + rect{ + width: 50px; + } + } + + &.selected, + &:hover{ + background: var(--nav-button-bg); + + svg{ + fill: var(--svg-selected-fill); + } + } + + &.disabled{ + cursor: not-allowed; + background: transparent; + + svg{ + fill: var(--svg-fill); + } + } + } + } +} diff --git a/src/components/dashboard-nav/index.js b/src/components/dashboard-nav/index.js new file mode 100644 index 000000000..2d444d5e8 --- /dev/null +++ b/src/components/dashboard-nav/index.js @@ -0,0 +1,10 @@ +import React from 'react' +import DashboardNavComponent from './dashboard-nav-component'; + +function DashboardNav(props) { + return ( + + ) +} + +export default DashboardNav; diff --git a/src/components/filters/filter-component-styles.module.scss b/src/components/filters/filter-component-styles.module.scss new file mode 100644 index 000000000..174d15b27 --- /dev/null +++ b/src/components/filters/filter-component-styles.module.scss @@ -0,0 +1,55 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.filters { + width: 50%; + + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + color: var(--font-color); + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + .titleRow { + .title { + font-size: 18px; + font-weight: 700; + text-transform: uppercase; + } + display: flex; + justify-content: space-between; + align-items: center; + } + + .close{ + color: var(--font-color); + } + + .filterList{ + margin-bottom: 15px; + + .filterGroupTitle { + font-size: 16px; + padding: 0 0 8px; + } + + .filterbox { + padding-left: 10px; + display: flex; + flex-direction: column; + align-items: start; + gap: 10px; + } + } +} diff --git a/src/components/filters/filter-component.jsx b/src/components/filters/filter-component.jsx new file mode 100644 index 000000000..973ed5eda --- /dev/null +++ b/src/components/filters/filter-component.jsx @@ -0,0 +1,188 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { useT } from '@transifex/react'; + +import DoneIcon from '@mui/icons-material/Done'; +import { Chip } from '@mui/material'; +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; +import { Loading } from 'he-components'; + +import Button from 'components/button'; + +import hrTheme from 'styles/themes/hr-theme.module.scss'; + +import styles from './filter-component-styles.module.scss'; + +function FilterComponent(props) { + const t = useT(); + const { + setFilteredTaxaList, + selectedTaxa, + taxaList, + filters, + setFilters, + isLoading, + updateActiveFilter, + } = props; + + const [anyActive, setAnyActive] = useState(false); + const { lightMode } = useContext(LightModeContext); + + const clearCounts = () => { + setFilteredTaxaList([]); + const updatedFilters = [...filters]; + updatedFilters.forEach((f) => { + const innerFilter = f; + innerFilter.filters.forEach((ff) => { + const filter = ff; + filter.count = 0; + }); + }); + + setFilters(updatedFilters); + }; + + const refreshCounts = () => { + clearCounts(); + + let allTaxa = taxaList; + if (selectedTaxa) { + allTaxa = allTaxa.filter((taxa) => taxa.taxa === selectedTaxa); + } + + const filtered = []; + // allTaxa.species = []; + let isAnyActive = false; + + filters.forEach((f) => + f.filters.forEach((ff) => { + if (ff.active) isAnyActive = true; + }) + ); + // TODO: FIGURE OUT FILTERS + allTaxa.forEach((taxa) => { + // Object.values(allTaxa).forEach((taxa) => { + const candidateTaxa = { ...taxa }; + const candidateSpecies = []; + taxa.species.forEach((species) => { + const speciesFilters = []; + const speciesOrFilters = []; + filters.forEach((filterGroup) => { + filterGroup.filters.forEach((filter) => { + const result = filter.test(species); + filter.result = result; + if (isAnyActive && filter.active) { + if (filter.type === 'and') { + speciesFilters.push(result); + } else if (filter.type === 'or') { + speciesOrFilters.push(result); + } + } + }); + }); + const allAnd = + speciesFilters.every((x) => x) && speciesFilters.length > 0; + const anyOr = speciesOrFilters.includes(true); + let inCandidate = false; + if (!isAnyActive) { + inCandidate = true; + candidateSpecies.push(species); + // only ands + } else if (allAnd && speciesOrFilters.length === 0) { + inCandidate = true; + candidateSpecies.push(species); + // only or's + } else if (speciesFilters.length === 0 && anyOr) { + inCandidate = true; + candidateSpecies.push(species); + } else if (allAnd && anyOr) { + inCandidate = true; + candidateSpecies.push(species); + } + + const onAnd = allAnd || speciesFilters.length === 0; + + filters.forEach((filterGroup) => { + filterGroup.filters.forEach((filter) => { + if (onAnd && filter.result) { + filter.count += 1; + } + }); + }); + }); + + candidateTaxa.species = candidateSpecies; + candidateTaxa.count = candidateSpecies.length; + filtered.push(candidateTaxa); + }); + + setAnyActive(isAnyActive); + setFilteredTaxaList(filtered); + }; + + const activateFilter = (filter) => { + updateActiveFilter(filter); + refreshCounts(); + }; + + const clearFilters = () => { + filters.forEach((f) => + f.filters.forEach((ff) => { + ff.active = false; + }) + ); + refreshCounts(); + }; + + useEffect(() => { + if (!taxaList) return; + refreshCounts(); + }, [taxaList]); + + useEffect(() => { + refreshCounts(); + }, [selectedTaxa]); + + return ( +
+
+
{t('Filters')}
+ {anyActive && ( +
+
+ {isLoading && } + {!isLoading && + filters.map((filterGroup, index) => { + return ( +
+
+ {t(filterGroup.title)} +
+
+ {filterGroup.filters.map((filter, idx) => { + return ( + : ''} + color={filter.active ? 'success' : 'primary'} + label={`${t(filter.name)}: ${filter.count}`} + onClick={() => activateFilter(filter)} + /> + ); + })} +
+
+ ); + })} +
+ ); +} + +export default FilterComponent; diff --git a/src/components/filters/index.js b/src/components/filters/index.js new file mode 100644 index 000000000..9a5225690 --- /dev/null +++ b/src/components/filters/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import FilterComponent from './filter-component'; + +function FilterContainer(props) { + return ; +} + +export default FilterContainer; diff --git a/src/components/layer-toggle/index.js b/src/components/layer-toggle/index.js index c72faeee9..4f9c5f25b 100644 --- a/src/components/layer-toggle/index.js +++ b/src/components/layer-toggle/index.js @@ -35,7 +35,7 @@ function LayerToggle(props) { }; useEffect(() => { - const _isChecked = activeLayers.some( + const _isChecked = activeLayers?.some( (layer) => layer.title === option.value ); setIsChecked(_isChecked); diff --git a/src/components/map-view/component.jsx b/src/components/map-view/component.jsx new file mode 100644 index 000000000..b820c631b --- /dev/null +++ b/src/components/map-view/component.jsx @@ -0,0 +1,46 @@ +import React from 'react'; + +import Spinner from 'components/spinner'; + +import styles from 'styles/themes/scene-theme.module.scss'; + +export default function ViewComponent(props) { + const { + map, + view, + mapName, + mapId, + children, + loadState, + spinner = true, + } = props; + if (loadState === 'loading') { + return ( + <> +
+ + + ); + } + if (loadState === 'loaded') { + return ( +
+ {React.Children.map(children || null, (child, i) => { + return ( + child && ( + // eslint-disable-next-line react/no-array-index-key + + ) + ); + })} +
+ ); + } +} diff --git a/src/components/map-view/index.js b/src/components/map-view/index.js new file mode 100644 index 000000000..5d10bd630 --- /dev/null +++ b/src/components/map-view/index.js @@ -0,0 +1,130 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; + +// import FeatureLayer from '@arcgis/core/layers/FeatureLayer'; +// import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; +// import GroupLayer from '@arcgis/core/layers/GroupLayer'; +// import TileLayer from '@arcgis/core/layers/TileLayer'; +import { createDefaultDashboardLayers } from 'utils/dashboard-utils'; + +import Map from '@arcgis/core/Map'; +import MapView from '@arcgis/core/views/MapView'; + +// import { DASHBOARD_LAYER_SLUGS, DASHBOARD_URLS } from 'constants/dashboard'; +import { SATELLITE_BASEMAP_LAYER } from 'constants/layers-slugs'; + +import Component from './component'; +import mapStateToProps from './selectors'; + +function ViewContainer(props) { + const { + onMapLoad, + mapName, + mapId, + viewSettings, + map, + setMap, + view, + setView, + geometry, + } = props; + + const [loadState, setLoadState] = useState('loading'); + const [countryLayer, setCountryLayer] = useState(null); + const [graphicsLayer, setGraphicsLayer] = useState(null); + const [groupLayer, setGroupLayer] = useState(null); + + const highlightCountry = async (query, zoomGeometry, flatView) => { + // country symbol - when user clicks on a country + // we will query the country from the countries featurelayer + // add the country feature to the graphics layer. + const symbol = { + type: 'simple-fill', + color: 'rgba(255, 255, 255, 1)', + outline: null, + }; + + // query the countries layer for a country that intersects the clicked point + const { + features: [feature], + } = await countryLayer.queryFeatures(query); + // user clicked on a country and the feature is returned + if (feature) { + graphicsLayer.graphics.removeAll(); + feature.symbol = symbol; + // add the country to the graphics layer + graphicsLayer.graphics.add(feature); + // zoom to the highlighted country + flatView.goTo( + { + target: zoomGeometry, + center: [zoomGeometry.longitude - 15, zoomGeometry.latitude], + zoom: 5.5, + extent: feature.geometry.clone(), + }, + { duration: 1000 } + ); + + // set the group layer opacity to 1 + // also increase the layer brightness and add drop-shadow to make the clicked country stand out. + groupLayer.effect = 'brightness(1.5) drop-shadow(0, 0px, 12px)'; + groupLayer.opacity = 1; + } + }; + + useEffect(() => { + const layers = createDefaultDashboardLayers(); + setCountryLayer(layers.countries); + setGraphicsLayer(layers.graphics); + setGroupLayer(layers.group); + + const flatMap = new Map({ + basemap: SATELLITE_BASEMAP_LAYER, + ground: { + surfaceColor: '#070710', + }, + layers: [layers.countries, layers.group], + }); + + setMap(flatMap); + + if (onMapLoad) { + onMapLoad(flatMap); + } + }, []); + + useEffect(() => { + if (map) { + const flatView = new MapView({ + map, + container: `map-container-${mapName || mapId}`, + zoom: 6, + // popup: new Popup(), + ...viewSettings, + }); + + setView(flatView); + } + }, [map, viewSettings]); + + useEffect(() => { + if (map && view) { + setLoadState('loaded'); + } + }, [map, view]); + + useEffect(() => { + if (view && geometry) { + const query = { + geometry, + returnGeometry: true, + outFields: ['*'], + }; + highlightCountry(query, query.geometry, view); + } + }, [view, geometry]); + + return ; +} + +export default connect(mapStateToProps, null)(ViewContainer); diff --git a/src/components/map-view/selectors.js b/src/components/map-view/selectors.js new file mode 100644 index 000000000..d6f72cff1 --- /dev/null +++ b/src/components/map-view/selectors.js @@ -0,0 +1,11 @@ +import { createStructuredSelector } from 'reselect'; + +import { getIsGlobesMenuPages } from 'selectors/location-selectors'; + +const selectCenterOn = ({ location }) => + (location.query && location.query.centerOn) || null; + +export default createStructuredSelector({ + isGlobesMenuPages: getIsGlobesMenuPages, + centerOn: selectCenterOn, +}); diff --git a/src/components/partners/index.js b/src/components/partners/index.js new file mode 100644 index 000000000..1e39226d4 --- /dev/null +++ b/src/components/partners/index.js @@ -0,0 +1,8 @@ +import React, { useEffect } from 'react'; +import PartnersComponent from './partners-component'; + +function PartnersContainer() { + return ; +} + +export default PartnersContainer; diff --git a/src/components/partners/partner-styles.module.scss b/src/components/partners/partner-styles.module.scss new file mode 100644 index 000000000..545f1c178 --- /dev/null +++ b/src/components/partners/partner-styles.module.scss @@ -0,0 +1,11 @@ +.partners{ + display: flex; + gap: 25px; + align-self: flex-end; + margin: 0 10px 10px 0; + + img{ + height: 30px; + max-width: 100%; + } +} diff --git a/src/components/partners/partners-component.jsx b/src/components/partners/partners-component.jsx new file mode 100644 index 000000000..e02106851 --- /dev/null +++ b/src/components/partners/partners-component.jsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import styles from './partner-styles.module.scss'; + +import logoImgColor from 'logos/eowilson_logo_v3.svg'; +import heLogoImg from 'logos/he_logo_color_full.png?react'; +import iucnLogo from 'logos/iucn.png'; +import molLogo from 'logos/mol.png'; +import yaleLogo from 'logos/yale.png'; + +function PartnersComponent() { + return ( +
+ + + + + +
+ + ) +} + +export default PartnersComponent diff --git a/src/components/protected-areas-table/protected-areas-table-styles.module.scss b/src/components/protected-areas-table/protected-areas-table-styles.module.scss index 7f1443c8e..50cb6c855 100644 --- a/src/components/protected-areas-table/protected-areas-table-styles.module.scss +++ b/src/components/protected-areas-table/protected-areas-table-styles.module.scss @@ -12,7 +12,7 @@ border-bottom: solid 1px $firefly; th, td { - text-align: left; + text-align: center; } td { @@ -23,6 +23,7 @@ .headerColumnContainer { display: flex; align-items: center; + justify-content: center; } .arrowsContainer { diff --git a/src/components/species-group-title/index.js b/src/components/species-group-title/index.js new file mode 100644 index 000000000..58d478baf --- /dev/null +++ b/src/components/species-group-title/index.js @@ -0,0 +1,10 @@ +import React from 'react' +import SpeciesGroupTitleComponent from './species-group-title-component' + +function SpeciesGroupTitleContainer(props) { + return ( + + ) +} + +export default SpeciesGroupTitleContainer diff --git a/src/components/species-group-title/species-group-title-component.jsx b/src/components/species-group-title/species-group-title-component.jsx new file mode 100644 index 000000000..a6a815bb6 --- /dev/null +++ b/src/components/species-group-title/species-group-title-component.jsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import cx from 'classnames'; + +import styles from './species-group-title-component.module.scss'; + +function SpeciesGroupTitleComponent(props) { + const { species } = props; + const { scientific_name, common_name } = species; + return ( +
+
+ {species === '__blank' ? '' : common_name} +
+
{scientific_name}
+
+ ); +} + +export default SpeciesGroupTitleComponent; diff --git a/src/components/species-group-title/species-group-title-component.module.scss b/src/components/species-group-title/species-group-title-component.module.scss new file mode 100644 index 000000000..ed9d00519 --- /dev/null +++ b/src/components/species-group-title/species-group-title-component.module.scss @@ -0,0 +1,34 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.familyTitle { + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--font-color); + padding-bottom: 3px; + margin-left: 10px; + margin-right: 10px; + + .sci { + padding-left: 10px; + } +} +.listBox { + min-height: calc(100% - 170px); +} +.name { + .sci { + font-weight: 300; + font-size: $font-size-sm; + font-style: italic; + } + .common { + font-size: $font-size-sm; + font-weight: 500; + } +} diff --git a/src/components/species-group/index.js b/src/components/species-group/index.js new file mode 100644 index 000000000..a2fad39e0 --- /dev/null +++ b/src/components/species-group/index.js @@ -0,0 +1,10 @@ +import React from 'react' +import SpeciesGroupComponent from './species-group-component' + +function SpeciesGroupContainer(props) { + return ( + + ) +} + +export default SpeciesGroupContainer diff --git a/src/components/species-group/species-group-component.jsx b/src/components/species-group/species-group-component.jsx new file mode 100644 index 000000000..7133840ea --- /dev/null +++ b/src/components/species-group/species-group-component.jsx @@ -0,0 +1,56 @@ +import React, { useContext } from 'react'; + +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; + +import { + NAVIGATION, + SPECIES_SELECTED_COOKIE, + SPECIES_IMAGE_URL, +} from 'constants/dashboard-constants.js'; + +import TaxaImageComponent from '../taxa-image'; + +import styles from './species-group-component.module.scss'; + +function SpeciesGroupComponent(props) { + const { species, selectedTaxaObj, setSelectedIndex, setScientificName } = + props; + // eslint-disable-next-line camelcase + const { species_url, common_name, scientific_name } = species; + const { lightMode } = useContext(LightModeContext); + + const selectSpecies = (selectedSpecies) => { + setSelectedIndex(NAVIGATION.DATA_LAYER); + setScientificName(selectedSpecies.scientific_name); + localStorage.setItem( + SPECIES_SELECTED_COOKIE, + selectedSpecies.scientific_name + ); + }; + + return ( + + ); +} + +export default SpeciesGroupComponent; diff --git a/src/components/species-group/species-group-component.module.scss b/src/components/species-group/species-group-component.module.scss new file mode 100644 index 000000000..152ea49f6 --- /dev/null +++ b/src/components/species-group/species-group-component.module.scss @@ -0,0 +1,72 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + + +.speciesBox { + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + color: var(--font-color); + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + display: flex; + flex-direction: row; + padding: 0; + font-family: $font-family-1; + font-size: $font-size-base; + color: var(--font-color); + width: 100%; + + svg{ + fill: var(--font-color); + width: 30px; + height: 30px; + } + + .name { + padding: 5px 10px; + } + + cursor: pointer; + .imgBox { + width: 32px; + max-width: 32px; + min-width: 32px; + padding: 3px; + text-align: center; + overflow: hidden; + object-fit: cover; + } + img { + max-width: 32px; + min-width: 32px; + margin: 0 auto; + height: 32px; + border-radius: 2px; + } + .speciesText { + display: flex; + flex-direction: column; + align-items: flex-start; + text-align: left; + font-size: $font-size-sm; + color: var(--font-color); + } + .openDataset { + text-decoration: underline; + font-size: $font-size-sm; + } +} + + diff --git a/src/components/species-list/index.js b/src/components/species-list/index.js new file mode 100644 index 000000000..c984dc9a0 --- /dev/null +++ b/src/components/species-list/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import SpeciesListComponent from './species-list-component'; + +function SpeciesListContainer(props) { + return ; +} + +export default SpeciesListContainer; diff --git a/src/components/species-list/species-list-component-styles.module.scss b/src/components/species-list/species-list-component-styles.module.scss new file mode 100644 index 000000000..7115a32c5 --- /dev/null +++ b/src/components/species-list/species-list-component-styles.module.scss @@ -0,0 +1,191 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.filters { + width: 50%; + + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + color: var(--font-color); + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + .titleRow { + .title { + font-size: $font-size-tick; + font-weight: 700; + } + + display: flex; + justify-content: space-between; + align-items: center; + } + + .close{ + color: var(--font-color); + } + + .filterGroupTitle { + font-size: $font-size-base; + padding: 15px 0 8px; + } + + .filterbox { + padding-left: 10px; + display: flex; + flex-direction: column; + align-items: start; + gap: 10px; + } +} + +.titleRow { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + + .title { + font-size: $font-size-tick; + font-weight: 700; + text-transform: uppercase; + cursor: default; + } +} + +.taxaList { + max-width: 400px; + margin-top: 15px; + display: flex; + flex-direction: column; + gap: 5px; + + svg{ + fill: var(--font-color); + width: 30px; + height: 30px; + } +} + +.thumb { + height: 32px; + width: 32px; +} +.title { + cursor: pointer; + padding: 0 5px; +} + +.spinner { + max-height: 32px; +} +.datasetList { + max-width: 250px; + display: flex; + flex-direction: column; + color: var(--font-color); + a:hover, + a:focus { + background-color: unset; + color: var(--font-color); + } + img { + margin: 2px !important; + height: 25px !important; + max-width: 25px !important; + } +} +.title { + font-size: $font-size-base; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + color: var(--font-color); + font-family: $font-family-1; + + .header { + padding-left: 7px; + } +} + +.filterCount { + margin-left: auto; + text-align: right; + margin-right: 15px; + font-size: $font-size-xs; +} + +.speciesList { + padding-top: 5px; + height: calc(100vh - 280px); + overflow-y: hidden; + width: 250px; + + .filterResults { + margin-top: 8px; + overflow-y: auto; + max-height: calc(100vh - 315px); + } + + .search{ + border: 1px solid var(--border-color); + padding: 5px; + border-radius: 8px; + flex-grow: 1; + background: #{$white}; + width: 225px; + margin: 5px 0; + + input[type="text"]{ + color: var(--border-color); + width: 100%; + } + + ::placeholder{ + color: var(--border-color); + } + + svg{ + fill: var(--border-color); + color: var(--border-color); + + path{ + fill: var(--border-color); + color: var(--border-color); + } + } + + &.showResults{ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + } + + .speciesFilter { + padding: 10px 0; + line-height: 32px; + justify-content: center; + input { + width: 90%; + line-height: 32px; + margin-left: 2.5%; + } + } + + .close { + margin: 10px 15px 10px 10px; + } +} + diff --git a/src/components/species-list/species-list-component.jsx b/src/components/species-list/species-list-component.jsx new file mode 100644 index 000000000..d33640ea0 --- /dev/null +++ b/src/components/species-list/species-list-component.jsx @@ -0,0 +1,293 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { useT } from '@transifex/react'; + +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; +import { Loading } from 'he-components'; + +import Button from 'components/button'; +import SearchInput from 'components/search-input'; + +import hrTheme from 'styles/themes/hr-theme.module.scss'; + +import SpeciesGroupContainer from '../species-group'; +// import SpeciesGroupTitleContainer from '../species-group-title'; + +import TaxaImageComponent from '../taxa-image'; + +import styles from './species-list-component-styles.module.scss'; + +function SpeciesListComponent(props) { + const t = useT(); + const { selectedTaxa, setSelectedTaxa, filteredTaxaList, isLoading } = props; + + const { lightMode } = useContext(LightModeContext); + const SPHINGID_MOTHS = /Sphingid moths/gi; + + const [inFilter, setInFilter] = useState(0); + const [familyCounts, setFamilyCounts] = useState({}); + const [selectedTaxaObj, setSelectedTaxaObj] = useState(); + const [filteredSpecies, setFilteredSpecies] = useState({}); + const [filter, setFilter] = useState(); + + const getTaxaTitle = (label, taxa) => { + const taxaToCheck = [ + 'MAMMALS', + 'BIRDS', + 'REPTILES', + 'AMPHIBIANS', + 'CONIFERS', + 'CACTI', + 'PALMS', + 'OTHER PLANTS*', // because the backend sends a * already + ]; + if (taxa) { + if (taxa.match(SPHINGID_MOTHS)) { + return `Old World ${label}*`; + } + if (!taxaToCheck.includes(taxa.toUpperCase())) { + return `${t(label)}*`; + } + } + + return t(label); + }; + + const updateSelectedTaxa = (taxa) => { + if (!taxa) return; + + setFilter(''); + setSelectedTaxa(taxa); + const fc = {}; + + const sto = filteredTaxaList?.find((t) => t.taxa === taxa); + if (sto === undefined) return; + + setInFilter(sto?.species?.length); + + const transformer = (sp) => ({ + ...sp, + visible: true, + filterString: [sp.scientificname, sp.common, sp.family, sp.family_common] + .join(' ') + .toLocaleLowerCase(), + }); + const transformed = []; + sto?.species?.forEach((sp) => { + let species = sp; + if (!fc[species.family]) { + fc[species.family] = { + total: 1, + visibleCount: 0, + }; + species.first = true; + } else { + species.first = false; + fc[species.family].total += 1; + } + species = transformer(species); + species.familyObj = fc[species.family]; + transformed.push(species); + }); + sto.species = transformed; + setFamilyCounts(fc); + setSelectedTaxaObj(sto); + }; + + const applyFilter = () => { + // this.virtualScroll?.scrollToIndex(0); + const inFilterCheck = (sp) => sp.common_name?.indexOf(filter) > -1; + setInFilter(0); + + // clear the counts + const fc = familyCounts; + Object.keys(fc).forEach((k) => { + fc[k].visibleCount = 0; + }); + + // sort by family common + const familySortedSpecies = sortFilteredSpecies(selectedTaxaObj?.species); + + // group by family common + let groupByFamily = familySortedSpecies?.reduce((group, result) => { + const catName = result.scientific_name[0] ?? '__blank'; + + const updateResult = { ...result }; + + updateResult.visible = inFilterCheck(result); + if (updateResult.visible) { + let inf = inFilter; + setInFilter(inf++); + fc[updateResult.family].visibleCount += 1; + + group[catName] = group[catName] ?? []; + group[catName].push(updateResult); + } + + setFamilyCounts(fc); + return group; + }, {}); + + if (groupByFamily) { + // sort family common by common + const groupKeys = Object.keys(groupByFamily); + + groupKeys.forEach((groupKey) => { + groupByFamily[groupKey].sort((a, b) => { + if (a.scientificname < b.scientificname) { + return -1; + } + if (a.scientificname > b.scientificname) { + return 1; + } + return 0; + }); + }); + + const keyLength = Object.keys(groupByFamily).length - 1; + + // check if there is a group with no name + if (Object.keys(groupByFamily)[keyLength] === '') { + const noNameGroup = Object.values(groupByFamily)[keyLength]; + delete groupByFamily[Object.keys(groupByFamily)[keyLength]]; + groupByFamily = { ...groupByFamily, __blank: noNameGroup }; + } + + setFilteredSpecies(groupByFamily); + } else { + setFilteredSpecies({}); + } + }; + + const handleSearch = (event) => { + setFilter(event.currentTarget.value.toLowerCase().trim()); + }; + + const clearSelection = () => { + setSelectedTaxa(''); + }; + + const sortFilteredSpecies = (species) => { + return species?.sort((a, b) => { + if (a.scientific_name < b.scientific_name) { + return -1; + } + if (a.scientific_name > b.scientific_name) { + return 1; + } + return 0; + }); + }; + + useEffect(() => { + if (!selectedTaxa) return; + updateSelectedTaxa(selectedTaxa); + }, [selectedTaxa, filteredTaxaList]); + + useEffect(() => { + if (!selectedTaxaObj) return; + + applyFilter(); + }, [selectedTaxaObj]); + + useEffect(() => { + const handler = setTimeout(() => { + applyFilter(); + }, 300); + + return () => { + clearTimeout(handler); + }; + }, [filter]); + + return ( +
+
+
{t('Species')}
+ {selectedTaxa && ( +
+
+
+ {!selectedTaxa && + filteredTaxaList?.map((taxa, index) => { + return ( + + ); + })} +
+ {isLoading && } + {!isLoading && selectedTaxa && selectedTaxaObj && ( +
+
+ {selectedTaxaObj?.count} + + {getTaxaTitle(selectedTaxaObj?.title, selectedTaxaObj?.taxa)} + +
+ +
+ {Object.keys(filteredSpecies).map((sp, index) => { + return ( +
+ {/* {filteredSpecies[sp].length > 0 && ( + + )} */} + {filteredSpecies[sp].map( + (v, idx) => + v.visible && ( + + ) + )} +
+ ); + })} +
+
+ )} +
+ ); +} +export default SpeciesListComponent; diff --git a/src/components/species-richness/index.js b/src/components/species-richness/index.js new file mode 100644 index 000000000..f6a16c2e1 --- /dev/null +++ b/src/components/species-richness/index.js @@ -0,0 +1,3 @@ +import Component from './species-richness-component'; + +export default Component; diff --git a/src/components/species-richness/species-richness-component.jsx b/src/components/species-richness/species-richness-component.jsx new file mode 100644 index 000000000..f4ca05006 --- /dev/null +++ b/src/components/species-richness/species-richness-component.jsx @@ -0,0 +1,235 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { useT } from '@transifex/react'; + +import { getCSSVariable } from 'utils/css-utils'; + +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + ArcElement, + BarElement, + Title, + Tooltip, + Legend, +} from 'chart.js'; +import cx from 'classnames'; +import last from 'lodash/last'; + +import AmphibiansBlack from 'images/dashboard/amphibian_icon_black.png?react'; +import AmphibiansWhite from 'images/dashboard/amphibian_icon_white.png?react'; +import BirdsBlack from 'images/dashboard/bird_icon_black.png?react'; +import BirdsWhite from 'images/dashboard/bird_icon_white.png?react'; +import MammalsBlack from 'images/dashboard/mammal_icon_black.png?react'; +import MammalsWhite from 'images/dashboard/mammal_icon_white.png?react'; +import ReptilesBlack from 'images/dashboard/reptile_icon_black.png?react'; +import ReptilesWhite from 'images/dashboard/reptile_icon_white.png?react'; + +import { + NATIONAL_TREND, + PROVINCE_TREND, +} from '../../containers/sidebars/dashboard-trends-sidebar/dashboard-trends-sidebar-component'; +import compStyles from '../../containers/sidebars/dashboard-trends-sidebar/spi/score-distibutions/score-distributions-spi-styles.module.scss'; +import { LightModeContext } from '../../context/light-mode'; +import SpiArcChartComponent from '../charts/spi-arc-chart/spi-arc-chart-component'; + +import styles from './species-richness-styles.module.scss'; + +ChartJS.register( + CategoryScale, + LinearScale, + ArcElement, + BarElement, + Title, + Tooltip, + Legend +); + +function SpeciesRichnessComponent(props) { + const t = useT(); + const { selectedProvince, activeTrend, provinces, countryData } = props; + + const { lightMode } = useContext(LightModeContext); + const [scores, setScores] = useState({ + birds: { + count: 0, + total: 0, + percentage: 0, + }, + mammals: { + count: 0, + total: 0, + percentage: 0, + }, + reptiles: { + count: 0, + total: 0, + percentage: 0, + }, + amphibians: { + count: 0, + total: 0, + percentage: 0, + }, + }); + + const [titleText, setTitleText] = useState( + `NATIONAL ${t(' SPI BY TAXONOMIC GROUP')}` + ); + + const getPercentage = (species) => { + const { count } = scores[species]; + return [count, 100 - count]; + }; + + const getScores = () => { + let data = []; + if (selectedProvince && activeTrend === PROVINCE_TREND) { + const regionData = provinces.find( + (region) => region.region_name === selectedProvince.region_name + ); + data = regionData; + } else { + data = last(countryData); + } + + if (data) { + const { + BirdSpeciesRichness, + BirdSPI, + MammalSpeciesRichness, + MammalSPI, + ReptileSpeciesRichness, + ReptileSPI, + AmphibianSpeciesRichness, + AmphibianSPI, + } = data; + + setScores({ + birds: { + count: BirdSPI, + total: BirdSpeciesRichness, + }, + mammals: { + count: MammalSPI, + total: MammalSpeciesRichness, + }, + reptiles: { + count: ReptileSPI, + total: ReptileSpeciesRichness, + }, + amphibians: { + count: AmphibianSPI, + total: AmphibianSpeciesRichness, + }, + }); + } + }; + + useEffect(() => { + if (!selectedProvince) return; + getScores(); + if (activeTrend === NATIONAL_TREND || !selectedProvince) { + setTitleText(`NATIONAL ${t(' SPI BY TAXONOMIC GROUP')}`); + } else if (activeTrend === PROVINCE_TREND && selectedProvince) { + setTitleText( + `${selectedProvince?.region_name} ${t('SPI BY TAXONOMIC GROUP')}` + ); + } + }, [selectedProvince, activeTrend]); + + const emptyArcColor = lightMode + ? getCSSVariable('dark-opacity') + : getCSSVariable('white-opacity-20'); + + const birdData = { + labels: [t('Birds'), t('Remaining')], + datasets: [ + { + label: '', + data: getPercentage('birds'), + backgroundColor: [getCSSVariable('birds'), emptyArcColor], + borderColor: [getCSSVariable('birds'), emptyArcColor], + borderWidth: 1, + }, + ], + }; + + const mammalsData = { + labels: [t('Mammals'), t('Remaining')], + datasets: [ + { + label: '', + data: getPercentage('mammals'), + backgroundColor: [getCSSVariable('mammals'), emptyArcColor], + borderColor: [getCSSVariable('mammals'), emptyArcColor], + borderWidth: 1, + }, + ], + }; + + const reptilesData = { + labels: [t('Reptiles'), t('Remaining')], + datasets: [ + { + label: '', + data: getPercentage('reptiles'), + backgroundColor: [getCSSVariable('reptiles'), emptyArcColor], + borderColor: [getCSSVariable('reptiles'), emptyArcColor], + borderWidth: 1, + }, + ], + }; + + const amphibianData = { + labels: [t('Amphibians'), t('Remaining')], + datasets: [ + { + label: '', + data: getPercentage('amphibians'), + backgroundColor: [getCSSVariable('amphibians'), emptyArcColor], + borderColor: [getCSSVariable('amphibians'), emptyArcColor], + borderWidth: 1, + }, + ], + }; + + return ( +
+
{titleText}
+
+ + + + +
+
+ ); +} + +export default SpeciesRichnessComponent; diff --git a/src/components/species-richness/species-richness-styles.module.scss b/src/components/species-richness/species-richness-styles.module.scss new file mode 100644 index 000000000..989f60345 --- /dev/null +++ b/src/components/species-richness/species-richness-styles.module.scss @@ -0,0 +1,76 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + padding: 10px 5px 10px 26px; + flex-grow: 1; + align-items: center; + gap: 8px; + width: 100%; + + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + } + + .spis{ + display: flex; + gap: 10px; + align-items: center; + width: 100%; + justify-content: center; + border-radius: 5px; + border: 1.5px solid var(--border-color); + box-shadow: -2px 4px 4px 0px rgba(0, 0, 0, 0.25); + padding: 15px 0; + + .spi{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .score{ + color: var(--font-color); + text-align: center; + font-family: "Open Sans"; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-bottom: -20px; + } + + .taxoGroup{ + display: flex; + flex-direction: column; + margin-top: -60px; + align-items: center; + + .richness{ + color: var(--font-color); + text-align: center; + font-family: "Open Sans"; + font-size: 11px; + font-style: italic; + font-weight: 400; + line-height: normal; + } + + .score{ + font-size: 16px; + font-weight: 600; + text-transform: uppercase; + margin-bottom: 0; + } + } + } + } +} diff --git a/src/components/taxa-image/index.js b/src/components/taxa-image/index.js new file mode 100644 index 000000000..daa94abd0 --- /dev/null +++ b/src/components/taxa-image/index.js @@ -0,0 +1,50 @@ +import React from 'react'; + +import AmphibiansIcon from 'icons/dashboard/amphibian_icon.svg?react'; +import AntIcon from 'icons/dashboard/ant_icon.svg?react'; +import BirdsIcon from 'icons/dashboard/bird_icon.svg?react'; +import ButterFlyIcon from 'icons/dashboard/butterfly_icon.svg?react'; +import MammalsIcon from 'icons/dashboard/mammal_icon.svg?react'; +import OdoanteIcon from 'icons/dashboard/odonate_icon.svg?react'; +import ReptilesImage from 'icons/dashboard/reptile_icon.svg?react'; +import TreeIcon from 'icons/dashboard/tree_icon.svg?react'; + +function TaxaImageComponent(props) { + const { taxa } = props; + + const getTaxaIcon = () => { + let icon = ''; + switch (taxa) { + case 'amphibians': + icon = ; + break; + case 'ants': + icon = ; + break; + case 'birds': + icon = ; + break; + case 'butterflies': + icon = ; + break; + case 'mammals': + icon = ; + break; + case 'odonates': + icon = ; + break; + case 'reptiles': + icon = ; + break; + case 'trees': + icon = ; + break; + default: + break; + } + return icon; + }; + return <>{getTaxaIcon()}; +} + +export default TaxaImageComponent; diff --git a/src/components/top-menu/index.js b/src/components/top-menu/index.js new file mode 100644 index 000000000..a65f92eed --- /dev/null +++ b/src/components/top-menu/index.js @@ -0,0 +1,10 @@ +import React from 'react' +import TopMenuComponent from './top-menu-component'; + +function TopMenuContainer(props) { + return ( + + ) +} + +export default TopMenuContainer; diff --git a/src/components/top-menu/top-menu-component.jsx b/src/components/top-menu/top-menu-component.jsx new file mode 100644 index 000000000..19e083aba --- /dev/null +++ b/src/components/top-menu/top-menu-component.jsx @@ -0,0 +1,46 @@ +import React from 'react'; + +import { useT } from '@transifex/react'; + +import IdentityManager from '@arcgis/core/identity/IdentityManager'; + +import UserAvatar from 'icons/user_icon.svg?react'; + +import styles from './top-menu-styles.module.scss'; + +function UserProfile(props) { + const t = useT(); + const { setLoggedIn } = props; + const logOut = () => { + IdentityManager.destroyCredentials(); + setLoggedIn(false); + }; + return ( + <> + + + + | + + + ); +} + +function TopMenuComponent(props) { + const { user } = props; + return ( + //
{user && }
+
+ +
+ ); +} + +export default TopMenuComponent; diff --git a/src/components/top-menu/top-menu-styles.module.scss b/src/components/top-menu/top-menu-styles.module.scss new file mode 100644 index 000000000..0ce7c745c --- /dev/null +++ b/src/components/top-menu/top-menu-styles.module.scss @@ -0,0 +1,56 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + --font-color: #{$white}; + --bg-color: #{$brand-color-main}; + + &.light{ + --font-color: #{$black}; + --bg-color: #{$white}; + } + + position: absolute; + top: 0; + right: 50px; + display: flex; + align-items: center; + gap: 10px; + background-color: var(--bg-color); + padding: 4px 10px; + border-bottom-right-radius: 8px; + border-bottom-left-radius: 8px; + border-color: var(--font-color); + border-width: 1px; + border-style: solid; + border-top: none; + color: var(--font-color); + + a{ + color: var(--font-color); + text-decoration: none; + font-weight: 700; + + &.profile{ + height: 30px; + } + + svg{ + width: 20px; + height: 30px; + fill: var(--font-color); + + rect{ + width: 50px; + } + } + } + + button{ + color: var(--font-color); + font-weight: 700; + font-size: 16px; + } +} diff --git a/src/constants/dashboard-constants.js b/src/constants/dashboard-constants.js new file mode 100644 index 000000000..2635fd1f4 --- /dev/null +++ b/src/constants/dashboard-constants.js @@ -0,0 +1,66 @@ +export const LAYER_TITLE_TYPES = { + EXPERT_RANGE_MAPS: 'EXPERT RANGE MAPS', + POINT_OBSERVATIONS: 'POINT OBSERVATIONS', + REGIONAL_CHECKLISTS: 'REGIONAL CHECKLISTS', + TREND: 'TREND', +}; + +export const DASHBOARD_LAYER_SLUGS = { + INITIAL_COUNTRY_LAYER: 'INITIAL_COUNTRY_LAYER', + INITIAL_GROUP_LAYER: 'INITIAL_GROUP_LAYER', +}; + +export const INITIAL_LAYERS = [ + DASHBOARD_LAYER_SLUGS.INITIAL_COUNTRY_LAYER, + DASHBOARD_LAYER_SLUGS.INITIAL_GROUP_LAYER, + 'cities_labels_layer', + 'regions_labels_layer', + 'countries_labels_layer', + 'landscape_features_labels_layer', + 'admin_areas_feature_layer', +]; + +export const REGION_OPTIONS = { + PROVINCES: 'PROVINCES', + PROTECTED_AREAS: 'PROTECTED_AREAS', + FORESTS: 'FORESTS', + DRAW: 'DRAW', +}; + +export const SPI_LATEST_YEAR = 2024; +export const SHI_LATEST_YEAR = 2021; +export const SHI_TREND_LATEST_YEAR = 2022; +export const SII_LATEST_YEAR = 2023; + +export const SPECIES_SELECTED_COOKIE = 'species_selected'; + +export const NAVIGATION = { + HOME: 1, + REGION: 2, + SPECIES: 3, + DATA_LAYER: 4, + BIO_IND: 5, + REGION_ANALYSIS: 6, + TRENDS: 7, + EXPLORE_SPECIES: 8, +}; + +export const LAYER_OPTIONS = { + PROTECTED_AREAS: 'PROTECTED_AREAS', + PROPOSED_PROTECTED_AREAS: 'PROPOSED_PROTECTED_AREAS', + PROVINCES: 'PROVINCES', + PROVINCES_VECTOR: 'PROVINCES_VECTOR', + PROVINCES_REGION_VECTOR: 'PROVINCES_REGION_VECTOR', + PRIORITY_AREAS: 'PRIORITY_AREAS', + COMMUNITY_FORESTS: 'COMMUNITY_FORESTS', + HABITAT: 'HABITAT', + FORESTS: 'FORESTS', + POINT_OBSERVATIONS: 'POINT_OBSERVATIONS', + ADMINISTRATIVE_LAYERS: 'ADMINISTRATIVE_LAYERS', + EXPERT_RANGE_MAPS: 'EXPERT_RANGE_MAPS', +}; + +export const TAXA_IMAGE_URL = 'icons/dashboard/'; + +export const SPECIES_IMAGE_URL = + 'https://storage.googleapis.com/mol-assets2/thumbs/'; diff --git a/src/constants/layers-urls.js b/src/constants/layers-urls.js index f303895dd..6d4423cea 100644 --- a/src/constants/layers-urls.js +++ b/src/constants/layers-urls.js @@ -461,3 +461,50 @@ export const LAYERS_URLS = { [SPECIFIC_REGIONS_WDPA_LAYER]: 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/SpecificRegions_wdpa_202401/FeatureServer/0', }; + +export const DASHBOARD_URLS = { + INITIAL_COUNTRY_LAYER: '53a1e68de7e4499cad77c80daba46a94', + COUNTRY_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/ESRI_table1_v2_0/FeatureServer', + SPECIES_OCCURENCE_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/regional_species_COD/FeatureServer', + WDPA_OCCURENCE_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/cd_occ_province_join/FeatureServer', + SPI_PROVINCE_TREND_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/ESRI_table2_v2_2/FeatureServer', + SPI_REGION_SPECIES_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/regional_species_spi_scores_year_2024_v1_2/FeatureServer', + SPI_HISTOGRAM_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/regional_and_national_spi_bin_count_2024/FeatureServer', + SHI_PROVINCE_TREND_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/drc_provinces_spi_join2/FeatureServer', + SHI_SPECIES_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/national_species_shi_scores_year_2024/FeatureServer', + SHI_HISTOGRAM_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/shs_summary_for_stacked_bar_chart_2021/FeatureServer', + SHI_PROVINCE_HISTOGRAM_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/regional_shs_bin_count_2022/FeatureServer', + SHI_PROVINCE_SPECIES_URL: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/regional_species_shs_scores_year_2022/FeatureServer', + AMPHIBIAN_LOOKUP: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/taxa_amphibians_final/FeatureServer', + BIRDS_LOOKUP: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/taxa_birds_final/FeatureServer', + MAMMALS_LOOKUP: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/taxa_mammals_final/FeatureServer', + REPTILES_LOOKUP: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/taxa_reptiles_final/FeatureServer', + PRECALC_AOI: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/gadm1_precalculated_aoi_summaries_updated_20240321/FeatureServer', + WDPA: 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/wdpa_occ_species_COD/FeatureServer', + WDPA_PRECALC: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/wdpa_precalculated_aoi_summaries_updated_20240408/FeatureServer', + ADMIN_AREAS_FEATURE_LAYER: [ + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/gadm0_aoi_summaries_updated_20240326/FeatureServer', + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/gadm1_precalculated_aoi_summaries_updated_20240321/FeatureServer', + ], + FOREST: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/forest_title_refined_range_species/FeatureServer', + PRIORITY_SPECIES: + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/priority_species/FeatureServer', +}; diff --git a/src/containers/menus/globes-menu/component.jsx b/src/containers/menus/globes-menu/component.jsx index 4f5ed8cb5..80fb83f8a 100644 --- a/src/containers/menus/globes-menu/component.jsx +++ b/src/containers/menus/globes-menu/component.jsx @@ -75,7 +75,7 @@ GlobesMenu.propTypes = { GlobesMenu.defaultProps = { className: '', landing: false, - onMouseLeave: () => {}, + onMouseLeave: () => { }, }; export default GlobesMenu; diff --git a/src/containers/menus/sidemenu/component.jsx b/src/containers/menus/sidemenu/component.jsx index faa5db028..f21b37d1c 100644 --- a/src/containers/menus/sidemenu/component.jsx +++ b/src/containers/menus/sidemenu/component.jsx @@ -45,7 +45,6 @@ function SideMenu({ className={styles.searchBtn} handleClick={() => setSearcherOpen(true)} /> - {isSearcherOpen && (
)} -
+ {selectedTab === 1 && ( + + )} + {selectedTab === 2 && ( + + )} + + )} + + ); +} + +export default BioDiversityComponent; diff --git a/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/biodiversity-indicators-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/biodiversity-indicators-styles.module.scss new file mode 100644 index 000000000..a5d8db066 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/biodiversity-indicators-styles.module.scss @@ -0,0 +1,116 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + padding: 5px 10px; + width: 100%; + + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + .sectionTitle{ + font-size: $font-size-tick; + font-weight: 700; + text-transform: uppercase; + line-height: 1.5; + color: var(--font-color); + } + + .speciesName{ + font-size: $font-size-tick; + text-transform: capitalize; + font-style: italic; + line-height: 1.5; + color: var(--font-color); + } + + .tabs{ + display: flex; + justify-content: space-evenly; + margin-top: 10px; + padding-left: 26px; + padding-right: 5px; + border-bottom: solid 2px var(--border-color); + + button{ + display: flex; + flex-direction: column; + align-items: center; + padding: 10px; + + label{ + color: var(--font-color); + text-align: center; + font-family: $font-family-roboto-flex; + font-size: $font-size-rg; + font-style: normal; + font-weight: 600; + line-height: normal; + } + + span{ + color: var(--font-color); + text-align: center; + font-family: $font-family-roboto-condensed; + font-size: $font-size-xs; + font-style: normal; + font-weight: 500; + line-height: normal; + } + + &.selected{ + background-color: var(--border-color); + color: $white; + border: solid 2px var(--border-color); + border-top-right-radius: 8px; + border-top-left-radius: 8px; + border-bottom: none; + + label, + span{ + color: var(--selected-font-color); + } + } + } + } + + p{ + font-size: $font-size-xs; + color: var(--font-color); + } + + .choices{ + display: flex; + flex-direction: column; + gap: 5px; + } + + .search{ + display: flex; + column-gap: 8px; + margin-top: 8px; + + .saveButton { + color: var(--save-button-text); + background-color: var(--border-color); + &:hover { + background-color: var(--border-color-hover); + } + } + } +} diff --git a/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/habitat-component-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/habitat-component-styles.module.scss new file mode 100644 index 000000000..7114d2123 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/habitat-component-styles.module.scss @@ -0,0 +1,247 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + display: flex; + flex-direction: column; + gap: 0.75rem; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + p{ + color: var(--font-color); + } + + .scores { + display: flex; + justify-content: space-between; + margin-top: 1rem; + + .metric { + display: flex; + flex-direction: column; + align-items: flex-start; + + .material-symbols-outlined { + width: 32px; + fill: #ffff33; + + &.arrow_downward { + fill: #c72929; + } + + &.arrow_upward { + fill: #228d19; + } + } + + span { + color: var(--font-color); + text-align: center; + font-size: $font-size-sm; + font-style: italic; + font-weight: 500; + line-height: normal; + } + + .score { + display: flex; + gap: 0.5rem; + + .results { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 125px; + + b { + color: var(--font-color); + text-align: center; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: normal; + } + + .desc { + color: var(--font-color); + font-size: $font-size-sm; + font-style: italic; + font-weight: 500; + line-height: normal; + } + } + } + } + } + + .chart { + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: center; + margin-top: 1rem; + + .compareWrap{ + display: flex; + align-items: center; + width: 75%; + gap: 0.75rem; + + .compare { + color: var(--font-color); + font-size: $font-size-sm; + font-style: italic; + font-weight: 400; + line-height: normal; + white-space: nowrap; + } + } + + .dashed, + .dotted, + .solid { + min-width: 100px; + text-align: center; + color: var(--font-color); + font-size: $font-size-sm; + font-style: normal; + font-weight: 500; + line-height: normal; + } + + .dashed { + border: none; + border-bottom: 3px dashed var(--font-color);; + } + + .dotted { + border: none; + border-bottom: 3px dotted var(--font-color);; + } + + .solid { + border: none; + border-bottom: 3px solid var(--font-color);; + } + + .labels{ + display: flex; + justify-content: space-around; + width: 100%; + } + + span { + color: var(--font-color); + font-size: $font-size-sm; + font-style: normal; + font-weight: 500; + line-height: normal; + } + + .legend{ + display: flex; + align-items: center; + gap: 1rem; + margin-top: 0.25rem; + + .legendBox { + width: 30px; + height: 8px; + + &.blue { + background-color: #2f2cae; + } + + &.green { + background-color: #228d19; + } + } + } + } + + .dataTable { + border-collapse: collapse; + width: 100%; + + th, + td { + font-size: $font-size-sm; + font-style: normal; + font-weight: 400; + line-height: normal; + + &.textLeft{ + text-align: left; + } + + &.w28{ + width: 7rem; + } + + &.w14{ + width: 3.5rem; + } + + &.textCenter{ + text-align: center; + } + } + + th { + vertical-align: bottom; + color: var(--font-color); + } + + tbody { + tr { + height: 30px; + border: 1px solid transparent; + + &:nth-child(odd) { + background-color: #d9d9d9; + } + + &:nth-child(even) { + background-color: #ededed; + } + + &:hover { + border: 1px solid var(--font-color); + cursor: pointer; + } + + &:has(+ :hover) { + border-bottom: 1px solid var(--font-color); + } + + &.highlighted { + background-color: rgba(0, 0, 0, 0.4); + + td{ + color: #{$white}; + } + } + } + + td { + color: #{$black}; + padding: 0 5px; + } + } + } +} diff --git a/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/habitat-component.jsx b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/habitat-component.jsx new file mode 100644 index 000000000..56d38bee8 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/habitat-component.jsx @@ -0,0 +1,132 @@ +import React from 'react'; +import { Line } from 'react-chartjs-2'; + +import { useT } from '@transifex/react'; + +import { + Chart as ChartJS, + LinearScale, + PointElement, + LineElement, + Tooltip, + Legend, + CategoryScale, +} from 'chart.js'; +import cx from 'classnames'; + +import styles from './habitat-component-styles.module.scss'; + +ChartJS.register( + LinearScale, + LineElement, + PointElement, + Tooltip, + Legend, + CategoryScale +); + +const formatValue = (value) => { + return value.toLocaleString(undefined, { + maximumFractionDigits: 2, + }); +}; + +function HabitatComponent(props) { + const t = useT(); + const { + countryName, + habitatTableData, + lightMode, + selectedCountry, + shiCountries, + chartData, + onCountryChange, + chartOptions, + updateCountry, + } = props; + + return ( +
+
+
+ {t('Compare')} + +
+
+ {t('Area')} + {t('Connectivity')} + {t('Total')} +
+
+
+ {t(countryName)} +
+ {selectedCountry} +
+ {chartData && } + {habitatTableData.length && ( + + + + + + + + + + + + {habitatTableData.map((row) => ( + updateCountry({ value: row.country })} + className={ + selectedCountry === row.country || + countryName === row.country + ? styles.highlighted + : '' + } + > + + + + + + + ))} + +
+ {t('Country')} + + {t('Stewardship')} + + {t('Connectivity Score')} + + {t('Area Score')} + + {t('Total SHS')} +
{row.country} + {formatValue(row.stewardship)}% + + {formatValue(row.countryConnectivityScore * 100)} + + {formatValue(row.countryAreaScore * 100)} + {formatValue(row.shs)}%
+ )} +

+ {t( + '*The Species Habitat Score is calculated using the habitat suitable range map and remote sensing layers.' + )} +

+
+
+ ); +} + +export default HabitatComponent; diff --git a/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/index.js b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/index.js new file mode 100644 index 000000000..b2d9638eb --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/habitat/index.js @@ -0,0 +1,283 @@ +import React, { useEffect, useState } from 'react'; + +import { useT } from '@transifex/react'; + +import { getCSSVariable } from 'utils/css-utils'; + +import ArrowDownward from 'icons/arrow-down-solid.svg?react'; +import ArrowUpward from 'icons/arrow-up-solid.svg?react'; +import Stable from 'icons/minus-solid.svg?react'; + +import HabitatComponent from './habitat-component'; + +function HabitatContainer(props) { + const t = useT(); + const { + lightMode, + habitatTableData, + dataByCountry, + countryName, + habitatScore, + globalHabitatScore, + } = props; + + const [selectedCountry, setSelectedCountry] = useState('Global'); + const [shiCountries, setShiCountries] = useState([]); + const [chartData, setChartData] = useState(); + const [globalTrend, setGlobalTrend] = useState(''); + const [countryTrend, setCountryTrend] = useState(''); + const [globalTrendIcon, setGlobalTrendIcon] = useState(); + const [countryTrendIcon, setCountryTrendIcon] = useState(); + + const TRENDS = { + UPWARD: 'arrow_upward', + DOWNWARD: 'arrow_downward', + STABLE: '', + }; + + const chartOptions = { + plugins: { + title: { + display: false, + }, + legend: { + display: false, + }, + }, + scales: { + x: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Year'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + }, + }, + y: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Species Habitat Score'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + }, + }, + }, + }; + + const getChartData = (countrySelected) => { + const dates = []; + const currentCountry = dataByCountry[countryName]; + // const globalCountry = dataByCountry.Global; + + const defaultCountryScores = { area: [], connectivity: [], total: [] }; + const selectedCountryScores = { area: [], connectivity: [], total: [] }; + + if (currentCountry) { + currentCountry.shs?.forEach((row) => { + defaultCountryScores.area.push(row.propchange * 100); + }); + + dataByCountry[countrySelected]?.shs.forEach((row) => { + dates.push(row.year); + selectedCountryScores.area.push(row.propchange * 100); + }); + + if (currentCountry.frag.length > 0) { + const fragYear = currentCountry.frag?.[0].gisfrag; + currentCountry?.frag?.forEach((row) => { + defaultCountryScores.connectivity.push( + (row.gisfrag / fragYear) * 100 + ); + }); + + const selectedFragYear = + dataByCountry[countrySelected]?.frag?.[0].gisfrag; + dataByCountry[countrySelected]?.frag?.forEach((row) => { + selectedCountryScores.connectivity.push( + (row.gisfrag / selectedFragYear) * 100 + ); + }); + } + + for (let index = 0; index < dates.length; index += 1) { + const dcTotal = + (defaultCountryScores.area[index] + + defaultCountryScores.connectivity[index]) / + 2; + defaultCountryScores.total.push(dcTotal); + + const scTotal = + (selectedCountryScores.area[index] + + selectedCountryScores.connectivity[index]) / + 2; + selectedCountryScores.total.push(scTotal); + } + } + + setChartData({ + labels: dates, + datasets: [ + { + label: `${countryName} Area`, + fill: false, + backgroundColor: getCSSVariable('habitat-country'), + borderColor: getCSSVariable('habitat-country'), + borderDash: [5, 5], + pointBackgroundColor: getCSSVariable('habitat-country'), + pointBorderColor: getCSSVariable('habitat-country'), + pointStyle: false, + data: defaultCountryScores.area, + }, + { + label: `${countryName} Connectivity`, + fill: false, + borderDash: [3, 3], + backgroundColor: getCSSVariable('habitat-country'), + borderColor: getCSSVariable('habitat-country'), + pointBackgroundColor: getCSSVariable('habitat-country'), + pointBorderColor: getCSSVariable('habitat-country'), + pointStyle: false, + data: defaultCountryScores.connectivity, + }, + { + label: `${countryName} Total`, + fill: false, + backgroundColor: getCSSVariable('habitat-country'), + borderColor: getCSSVariable('habitat-country'), + pointBackgroundColor: getCSSVariable('habitat-country'), + pointBorderColor: getCSSVariable('habitat-country'), + pointStyle: false, + data: defaultCountryScores.total, + }, + { + label: `${countrySelected} Area`, + fill: false, + backgroundColor: getCSSVariable('habitat-country-compare'), + borderColor: getCSSVariable('habitat-country-compare'), + pointBackgroundColor: getCSSVariable('habitat-country-compare'), + pointBorderColor: getCSSVariable('habitat-country-compare'), + borderDash: [5, 5], + pointStyle: false, + data: selectedCountryScores.area, + }, + { + label: `${countrySelected} Connectivity`, + fill: false, + backgroundColor: getCSSVariable('habitat-country-compare'), + borderColor: getCSSVariable('habitat-country-compare'), + pointBackgroundColor: getCSSVariable('habitat-country-compare'), + pointBorderColor: getCSSVariable('habitat-country-compare'), + borderDash: [3, 3], + pointStyle: false, + data: selectedCountryScores.connectivity, + }, + { + label: `${countrySelected} Total`, + fill: false, + backgroundColor: getCSSVariable('habitat-country-compare'), + borderColor: getCSSVariable('habitat-country-compare'), + pointBackgroundColor: getCSSVariable('habitat-country-compare'), + pointBorderColor: getCSSVariable('habitat-country-compare'), + pointStyle: false, + data: selectedCountryScores.total, + }, + ], + }); + }; + + const onCountryChange = (event) => { + setSelectedCountry(event.currentTarget.value); + getChartData(event.currentTarget.value); + }; + + const updateCountry = (country) => { + setSelectedCountry(country.value); + getChartData(country.value); + }; + + const setTrendArrows = () => { + const hs = habitatScore; + const gs = globalHabitatScore; + + if (hs < 100) { + setCountryTrend(TRENDS.DOWNWARD); + setCountryTrendIcon(); + } else if (hs > 100) { + setCountryTrend(TRENDS.UPWARD); + setCountryTrendIcon(); + } else { + setCountryTrend(TRENDS.STABLE); + setCountryTrendIcon(); + } + + if (gs < 100) { + setGlobalTrend(TRENDS.DOWNWARD); + setGlobalTrendIcon(); + } else if (gs > 100) { + setGlobalTrend(TRENDS.DOWNWARD); + setGlobalTrendIcon(); + } else { + setGlobalTrend(TRENDS.DOWNWARD); + setGlobalTrendIcon(); + } + }; + + useEffect(() => { + if (habitatTableData.length) { + const countries = habitatTableData.map((item) => item.country); + + const sortedCountries = countries.sort((a, b) => { + const nameA = a.toUpperCase(); + const nameB = b.toUpperCase(); + if (nameA === 'GLOBAL' || nameB === 'GLOBAL') { + return -2; + } + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + return 0; + }); + + setShiCountries(sortedCountries); + + getChartData('Global'); + + setTrendArrows(); + } + }, [habitatTableData]); + + return ( + + ); +} + +export default HabitatContainer; diff --git a/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/index.js b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/index.js new file mode 100644 index 000000000..9236bce41 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/index.js @@ -0,0 +1,250 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { LightModeContext } from 'context/light-mode'; +import last from 'lodash/last'; + +import EsriFeatureService from 'services/esri-feature-service'; + +import { + INITIAL_LAYERS, + LAYER_OPTIONS, + LAYER_TITLE_TYPES, +} from 'constants/dashboard-constants'; + +import BioDiversityComponent from './biodiversity-indicators-component'; + +const roundUpNumber = (val) => { + if (val > 10000) { + return Math.round(val / 1000) * 1000; + } + if (val > 1000) { + return Math.round(val / 100) * 100; + } + return val; +}; + +function BioDiversityContainer(props) { + const { + data, + countryName, + dataByCountry, + regionLayers, + map, + speciesInfo, + setRegionLayers, + countryISO, + } = props; + + const { lightMode } = useContext(LightModeContext); + const [selectedTab, setSelectedTab] = useState(2); + const [habitatScore, setHabitatScore] = useState('0.00'); + const [habitatTableData, setHabitatTableData] = useState([]); + const [globalHabitatScore, setGlobalHabitatScore] = useState(0); + const [protectionScore, setProtectionScore] = useState(); + const [globalProtectionScore, setGlobalProtectionScore] = useState('0.00'); + const [protectionTableData, setProtectionTableData] = useState([]); + const [startYear, setStartYear] = useState(1950); + + const removeRegionLayers = () => { + map.layers.items.forEach((layer) => { + if (!INITIAL_LAYERS.includes(layer.id)) { + map.remove(layer); + } + }); + }; + + const getCountryScores = (country, lastCountryYearValue, startYearValue) => { + let countryAreaScore = 0; + let countryConnectivityScore = 0; + + if (country?.shs[lastCountryYearValue]) { + countryAreaScore = country?.shs[lastCountryYearValue].propchange; + if (country?.frag[lastCountryYearValue]?.gisfrag) { + countryConnectivityScore = + // eslint-disable-next-line no-unsafe-optional-chaining + country?.frag[lastCountryYearValue]?.gisfrag / startYearValue; + } + + return { countryAreaScore, countryConnectivityScore }; + } + return { countryAreaScore: 0, countryConnectivityScore: 0 }; + }; + + const getHabitatScore = () => { + const country = dataByCountry[countryName]; + + // TODO: handle no frag values + const startYearValue = country?.frag[0]?.gisfrag ?? 0; + setStartYear(country?.frag[0]?.year ?? country?.shs[0]?.year ?? 2001); + // eslint-disable-next-line no-unsafe-optional-chaining + const lastCountryYearValue = country?.shs.length - 1; + let globalAreaScore = 0; + let globalConnectivityScore = 0; + + const { countryAreaScore, countryConnectivityScore } = getCountryScores( + country, + lastCountryYearValue, + startYearValue + ); + + if (dataByCountry.Global?.shs[lastCountryYearValue]) { + globalAreaScore = + dataByCountry.Global?.shs[lastCountryYearValue].propchange; + + if (dataByCountry.Global?.frag[lastCountryYearValue]?.gisfrag) { + globalConnectivityScore = + // eslint-disable-next-line no-unsafe-optional-chaining + dataByCountry.Global?.frag[lastCountryYearValue].gisfrag / + startYearValue; + } + } + + const scores = { + habitat: ( + ((countryAreaScore + countryConnectivityScore) / 2) * + 100 + ).toFixed(1), + globalHabitat: ((globalAreaScore + globalConnectivityScore) / 2) * 100, + }; + + return scores; + }; + + const getHabitatTableData = () => { + const tableData = []; + + Object.keys(dataByCountry).forEach((country) => { + let stewardship = 100; + const habitatCountry = dataByCountry[countryName]; + + // const countrySHS = country?.shs; + const startYearValue = habitatCountry?.frag[0]?.gisfrag ?? 0; + // eslint-disable-next-line no-unsafe-optional-chaining + const lastCountryYearValue = habitatCountry?.shs.length - 1; + const countryData = dataByCountry[country]; + const global2001 = dataByCountry.Global?.shs[0]?.val || 0; + const country2001 = roundUpNumber(countryData.shs[0]?.val || 0); + if (country.toUpperCase() === 'GLOBAL') { + stewardship = 100; + } else { + stewardship = (country2001 / global2001) * 100; + } + + const { countryAreaScore, countryConnectivityScore } = getCountryScores( + countryData, + lastCountryYearValue, + startYearValue + ); + const shs = ((countryAreaScore + countryConnectivityScore) / 2) * 100; + + if (!Number.isNaN(shs)) { + tableData.push({ + country, + stewardship, + countryAreaScore, + countryConnectivityScore, + shs, + }); + } + }); + + setHabitatTableData(tableData); + }; + + const getProtectionTableData = (spiData) => { + const tableData = []; + + Object.keys(dataByCountry).forEach((country) => { + const currentCountryData = last( + spiData.filter((row) => row.country_name === country) + ); + // grab stewardship value from habitat table data + if (currentCountryData) { + tableData.push({ + country: currentCountryData.country_name, + stewardship: currentCountryData.stewardship, + rangeProtected: currentCountryData.range_protected.toFixed(1), + targetProtected: currentCountryData.target_protected.toFixed(1), + sps: currentCountryData.shs_score.toFixed(1), + }); + } + }); + + setProtectionTableData(tableData); + }; + + // get habitat score information + useEffect(() => { + if (dataByCountry && data) { + const { habitat, globalHabitat } = getHabitatScore(); + setHabitatScore(habitat); + setGlobalHabitatScore(globalHabitat); + + getHabitatTableData(); + } + }, [dataByCountry, data]); + + // get protection score information + useEffect(() => { + if (data && habitatTableData && dataByCountry) { + getProtectionTableData(data.spiScoreData); + + const globalValues = data.spiScoreData.filter( + (country) => country.country_name.toUpperCase() === 'GLOBAL' + ); + const countryData = data.spiScoreData.filter( + (country) => + country.country_name.toUpperCase() === countryName.toUpperCase() + ); + + const scores = { + protectionScore: last(countryData).shs_score.toFixed(1), + globalProtectionScore: last(globalValues).shs_score.toFixed(1), + }; + + setProtectionScore(scores.protectionScore); + setGlobalProtectionScore(scores.globalProtectionScore); + } + }, [habitatTableData]); + + useEffect(() => { + removeRegionLayers(); + const protectedLayers = EsriFeatureService.addProtectedAreaLayer( + null, + countryISO + ); + const layerName = LAYER_OPTIONS.HABITAT; + const webTileLayer = EsriFeatureService.getXYZLayer( + speciesInfo.scientificname.replace(' ', '_'), + layerName, + LAYER_TITLE_TYPES.TREND + ); + webTileLayer.then((layer) => { + setRegionLayers({ + ...regionLayers, + [layerName]: layer, + [LAYER_OPTIONS.PROTECTED_AREAS]: protectedLayers, + }); + map.add(protectedLayers); + map.add(layer); + }); + }, []); + + return ( + + ); +} + +export default BioDiversityContainer; diff --git a/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/index.js b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/index.js new file mode 100644 index 000000000..38014947e --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/index.js @@ -0,0 +1,173 @@ +import React, { useEffect, useState } from 'react'; +import { getCSSVariable } from 'utils/css-utils'; +import ProtectionComponent from './protection-component'; +import { useT } from '@transifex/react'; + +function ProtectionContainer(props) { + const t = useT(); + const {protectionTableData, countryName, dataByCountry, lightMode, spiDataByCountry} = props; + const [selectedCountry, setSelectedCountry] = useState('Global'); + const [shiCountries, setShiCountries] = useState([]); + const [globalScore, setGlobalScore] = useState(0); + const [chartData, setChartData] = useState(); + + useEffect(() => { + if (protectionTableData.length) { + const countries = protectionTableData.map(item => item.country); + + const sortedCountries = countries.sort((a, b) => { + const nameA = a.toUpperCase(); + const nameB = b.toUpperCase(); + if (nameA === 'GLOBAL' || nameB === 'GLOBAL') { + return -2; + } + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + return 0; + }); + + setShiCountries(sortedCountries); + getChartData('Global'); + } + + }, [protectionTableData]) + + + const chartOptions = { + plugins: { + title: { + display: false, + }, + legend: { + display: false, + }, + }, + scales: { + x: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Year'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + }, + }, + y: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Species Protection Score'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + }, + }, + }, + }; + + const onCountryChange = (event) => { + setSelectedCountry(event.currentTarget.value); + getChartData(event.currentTarget.value); + } + + const getChartData = (countrySelected) => { + const dates = []; + const currentCountry = spiDataByCountry[countryName]; + const globalCountry = spiDataByCountry.Global; + + const defaultCountryScores = { values: [] }; + const selectedCountryScores = { values: [] }; + + if (currentCountry) { + currentCountry.shs?.forEach(row => { + defaultCountryScores.values.push(row.shs_score); + }); + + spiDataByCountry[countrySelected]?.shs.forEach(row => { + dates.push(row.year); + selectedCountryScores.values.push(row.shs_score); + }); + + if (globalCountry?.shs.length) { + setGlobalScore(globalCountry.shs[globalCountry.shs.length - 1].val - 100); + } + } + + setChartData({ + labels: dates, + datasets: [ + { + label: `${countryName}`, + fill: false, + backgroundColor: '#2F2CAE', + borderColor: '#2F2CAE', + pointBackgroundColor: '#2F2CAE', + pointBorderColor: '#2F2CAE', + pointStyle: false, + data: defaultCountryScores.values, + }, + { + label: `${countrySelected}`, + fill: false, + backgroundColor: '#228D19', + borderColor: '#228D19', + pointBackgroundColor: '#228D19', + pointBorderColor: '#228D19', + pointStyle: false, + data: selectedCountryScores.values, + }, + ], + }); + + // suitableCountryProtectedArea = this.translate.instant( + // 'suitable_protected_area_km', + // { + // countryArea: this.protectionTargetArea.toLocaleString( + // [], + // { maximumFractionDigits: 0 }, + // ), + // }, + // ); + // this.suitableGlobalProtectedArea = this.translate.instant( + // 'suitable_protected_area_km', + // { + // countryArea: this.globalProtectionTargetArea.toLocaleString( + // [], + // { maximumFractionDigits: 0 }, + // ), + // }, + // ); + } + + const updateCountry = (country) => { + setSelectedCountry(country.value); + getChartData(country.value); + } + + return ; +} + +export default ProtectionContainer; diff --git a/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/protection-component-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/protection-component-styles.module.scss new file mode 100644 index 000000000..147656bfc --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/protection-component-styles.module.scss @@ -0,0 +1,263 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + display: flex; + flex-direction: column; + gap: 0.75rem; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + p{ + color: var(--font-color); + } + + .scores { + display: flex; + justify-content: space-between; + margin-top: 1rem; + + .metric { + display: flex; + flex-direction: column; + align-items: flex-start; + + .material-symbols-outlined { + font-size: 48px; + color: #c72929; + } + + label { + color: var(--font-color); + text-align: center; + font-family: STIX Two Text; + font-size: $font-size-sm; + font-style: italic; + font-weight: 500; + line-height: normal; + } + + .score { + display: flex; + margin-top: 0.25rem; + + .chartWrapper { + width: 60px; + height: 60px; + position: relative; + + b { + position: absolute; + top: 28px; + left: 48%; + translate: -50% -50%; + color: var(--font-color); + } + } + + .results { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 125px; + + b { + color: var(--font-color); + text-align: center; + font-family: STIX Two Text; + font-size: $font-size-tick; + font-style: normal; + font-weight: 600; + line-height: normal; + } + + .desc { + color: var(--font-color); + font-family: STIX Two Text; + font-size: $font-size-sm; + font-style: italic; + font-weight: 500; + line-height: normal; + } + } + } + } + } + + .chart { + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: center; + margin-top: 1rem; + + .compareWrap{ + display: flex; + align-items: center; + gap: 0.75rem; + + .compare { + color: var(--font-color); + font-family: STIX Two Text; + font-size: $font-size-sm; + font-style: italic; + font-weight: 400; + line-height: normal; + white-space: nowrap; + } + } + + .dashed, + .dotted, + .solid { + min-width: 100px; + text-align: center; + color: var(--font-color); + font-family: STIX Two Text; + font-size: $font-size-sm; + font-style: normal; + font-weight: 500; + line-height: normal; + } + + .dashed { + border: none; + border-bottom: 3px dashed var(--font-color);; + } + + .dotted { + border: none; + border-bottom: 3px dotted var(--font-color);; + } + + .solid { + border: none; + border-bottom: 3px solid var(--font-color);; + } + + .labels{ + display: flex; + justify-content: space-around; + width: 100%; + } + + label { + color: var(--font-color); + font-family: STIX Two Text; + font-size: $font-size-sm; + font-style: normal; + font-weight: 500; + line-height: normal; + } + + .legend{ + display: flex; + align-items: center; + gap: 1rem; + margin-top: 0.25rem; + + .legendBox { + width: 30px; + height: 8px; + + &.blue { + background-color: #2f2cae; + } + + &.green { + background-color: #228d19; + } + } + } + } + + .dataTable { + border-collapse: collapse; + width: 100%; + + th, + td { + font-family: STIX Two Text; + font-size: $font-size-sm; + font-style: normal; + font-weight: 400; + line-height: normal; + + &.textLeft{ + text-align: left; + } + + &.w28{ + width: 7rem; + } + + &.w20{ + width: 5rem; + } + + &.w12{ + width: 3rem; + } + + &.textCenter{ + text-align: center; + } + } + + th { + vertical-align: bottom; + color: var(--font-color); + } + + tbody { + tr { + height: 30px; + border: 1px solid transparent; + + &:nth-child(odd) { + background-color: #d9d9d9; + } + + &:nth-child(even) { + background-color: #ededed; + } + + &:hover { + border: 1px solid var(--font-color); + cursor: pointer; + } + + &:has(+ :hover) { + border-bottom: 1px solid var(--font-color); + } + + &.highlighted { + background-color: rgba(0, 0, 0, 0.4); + + td{ + color: #{$white}; + } + } + } + + td { + color: #{$black}; + padding: 0 5px; + } + } + } +} diff --git a/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/protection-component.jsx b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/protection-component.jsx new file mode 100644 index 000000000..e66bfb238 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/biodiversity-indicators/protection/protection-component.jsx @@ -0,0 +1,134 @@ +import React, { useState } from 'react'; +import styles from './protection-component-styles.module.scss'; +import { T, useT } from '@transifex/react'; +import cx from 'classnames'; + +import { + Chart as ChartJS, + LinearScale, + PointElement, + LineElement, + Tooltip, + Legend, + CategoryScale, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; +ChartJS.register(LinearScale, LineElement, PointElement, Tooltip, Legend, CategoryScale); + +function ProtectionComponent(props) { + const t = useT(); + const { + protectionTableData, + countryName, + protectionScore, + globalProtectionScore, + protectionArea, + globalProtectionArea, + lightMode, + selectedCountry, + updateCountry, + onCountryChange, + shiCountries, + chartData, + chartOptions + } = props; + + const [totalArea, setTotalArea] = useState(0); + const [globalArea, setGlobalArea] = useState(0); + + return ( +
+ {/*
+
+ +
+
+ {protectionScore} +
+
+ {protectionArea.toLocaleString(undefined, { maximumFractionDigits: 2 })} km2 + + 2} + totalArea={totalArea} + /> + +
+
+
+
+ +
+
+ {globalProtectionScore} +
+
+ {globalProtectionArea.toLocaleString(undefined, { maximumFractionDigits: 2 })} km2 + + 2} + globalArea={globalArea} + /> + +
+
+
+
*/} +
+
+ + +
+
+
+ +
+ +
+ {chartData && } + {protectionTableData.length && + + + + + + + + + + + + {protectionTableData.map(row => ( + updateCountry({ value: row.country })} + className={(selectedCountry === row.country || countryName === row.country) ? styles.highlighted : ''} + > + + + + + + + ))} + +
{t('Country')}{t('Stewardship')}{t('Range Protected')}{t('Target Protected')}{t('Total SPS')}
{row.country}{row.stewardship.toLocaleString(undefined, { maximumFractionDigits: 2 })}% + {row.rangeProtected.toLocaleString(undefined, { maximumFractionDigits: 2 })} km2 + + {row.targetProtected.toLocaleString(undefined, { maximumFractionDigits: 2 })} km2 + {row.sps.toLocaleString(undefined, { maximumFractionDigits: 2 })}%
+ } + +

+ {t('*The Species Protection Score is calculated using the habitat suitable range map and the WDPA Protected Areas.')} +

+
+
+ ) +} + +export default ProtectionComponent diff --git a/src/containers/sidebars/dashboard-sidebar/dashboard-home/dashboard-home-component.jsx b/src/containers/sidebars/dashboard-sidebar/dashboard-home/dashboard-home-component.jsx new file mode 100644 index 000000000..681e4a0b3 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/dashboard-home/dashboard-home-component.jsx @@ -0,0 +1,148 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { useLocale, useT } from '@transifex/react'; + +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; + +import Button from 'components/button'; +import SearchInput from 'components/search-input'; + +import { + NAVIGATION, + SPECIES_SELECTED_COOKIE, +} from 'constants/dashboard-constants.js'; + +import hrTheme from 'styles/themes/hr-theme.module.scss'; + +import styles from './dashboard-home-styles.module.scss'; + +function DashboardHomeComponent(props) { + const t = useT(); + const locale = useLocale(); + const { setSelectedIndex, setScientificName, prioritySpeciesList } = props; + + const { lightMode } = useContext(LightModeContext); + const [searchInput, setSearchInput] = useState(''); + const [searchResults, setSearchResults] = useState([]); + + const searchURL = 'https://dev-api.mol.org/2.x/species/groupsearch'; + // 'https://next-api-dot-api-2-x-dot-map-of-life.appspot.com/2.x/spatial/regions/spatial_species_search'; + + const handleSearch = (searchText) => { + setSearchInput(searchText.currentTarget.value); + }; + + const handleSearchSelect = (searchItem) => { + setScientificName(searchItem.scientificname); + localStorage.setItem(SPECIES_SELECTED_COOKIE, searchItem.scientificname); + setSelectedIndex(NAVIGATION.DATA_LAYER); + }; + + const handleExploreAllSpecies = () => { + setSelectedIndex(NAVIGATION.EXPLORE_SPECIES); + }; + + const getSearchResults = async () => { + const searchParams = { + query: searchInput, + limit: 100, + page: 0, + lang: locale || 'en', + // region_id: '1eff8980-479e-4eac-b386-b4db859b275d', + }; + const params = new URLSearchParams(searchParams); + const searchSpecies = await fetch(`${searchURL}?${params}`); + const results = await searchSpecies.json(); + setSearchResults(results); + }; + + const selectSpecies = (scientificname) => { + setSelectedIndex(NAVIGATION.DATA_LAYER); + setScientificName(scientificname); + localStorage.setItem(SPECIES_SELECTED_COOKIE, scientificname); + }; + + useEffect(() => { + if (!searchInput) return; + const handler = setTimeout(() => { + getSearchResults(); + }, 300); + + return () => clearTimeout(handler); + }, [searchInput]); + + return ( +
+
+ + {t('Biodiversity Dashboard')} + +
+

+ {t( + 'Explore species spatial data, view conservation status, and analyze regions where a species occurs' + )} +

+
+ 0 ? styles.showResults : '' + )} + placeholder={t('Search for a species by name')} + onChange={handleSearch} + value={searchInput} + /> + {searchInput && searchResults.length > 0 && ( +
    + {searchResults.map((item, index) => ( +
  • + +
  • + ))} +
+ )} +
+ {prioritySpeciesList.length > 0 && ( +
+ {t('Popular Species')} +
+ {prioritySpeciesList.map((species) => ( + + ))} +
+
+ )} +
+ {/* */} +
+ ); +} + +export default DashboardHomeComponent; diff --git a/src/containers/sidebars/dashboard-sidebar/dashboard-home/dashboard-home-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/dashboard-home/dashboard-home-styles.module.scss new file mode 100644 index 000000000..960f39192 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/dashboard-home/dashboard-home-styles.module.scss @@ -0,0 +1,192 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container { + --font-color: #{$white}; + --dark-color: #{$black}; + + &.light{ + --font-color: #{$black}; + --dark-color: #{$white}; + } + + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + padding: 5px 10px; + + .content{ + flex-grow: 1; + height: 100%; + color: var(--font-color); + + .sectionTitle{ + font-size: $font-size-tick; + font-weight: 700; + text-transform: uppercase; + line-height: 1.5; + color: var(--font-color); + } + + p{ + color: var(--font-color); + } + + .explore{ + position: relative; + display: flex; + justify-content: center; + align-items: center; + gap: 2rem; + margin-bottom: 20px; + + .search{ + border: 1px solid var(--border-color); + padding: 5px; + border-radius: 8px; + flex-grow: 1; + background: #{$white}; + + input[type="text"]{ + color: var(--border-color); + width: 100%; + } + + ::placeholder{ + color: var(--border-color); + } + + svg{ + fill: var(--border-color); + color: var(--border-color); + + path{ + fill: var(--border-color); + color: var(--border-color); + } + } + + &.showResults{ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + } + + .searchResults{ + position: absolute; + top: 100%; + left: 5px; + background-color: var(--border-color); + width: 329px; + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; + padding: 8px 10px 5px; + margin: -2px 0 0 -1px; + list-style: none; + + li{ + button{ + cursor: pointer; + padding: 5px; + color: #{$white}; + + span{ + font-size: $font-size-sm; + font-style: italic; + } + + &:hover{ + background-color:#{$white}; + color: var(--border-color); + } + } + } + } + + button{ + width:auto; + color: var(--save-button-text); + background-color: var(--border-color); + &:hover { + background-color: var(--border-color-hover); + } + } + } + + .mostPopular{ + .species{ + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 50px; + margin-top: 10px; + + .navCard{ + display: flex; + flex-direction: column; + border-radius: 15px; + height: 200px; + width: 200px; + cursor: pointer; + background-position: center top; + background-repeat: no-repeat; + background-size: cover; + padding: 0; + align-items: center; + + &.regions{ + background-image: url('../../../../assets/images/drc_region_logos.jpg'); + } + + &.trends{ + background-image: url('../../../../assets/images/drc_metrics_logos.jpg'); + } + + .outline{ + border-bottom: none; + height: 100%; + // margin: 12px 12px 0; + border-top-left-radius: 15px; + border-top-right-radius: 15px; + outline-style: none; + } + + span{ + font-weight: 600; + font-size: $font-size-base; + letter-spacing: 0em; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + color: var(--dark-color); + background-color: var(--font-color); + margin: 0 12px; + transition: color .3s; + cursor: pointer; + width: 100%; + } + + p{ + background-color: var(--font-color); + border-bottom-left-radius: 15px; + border-bottom-right-radius: 15px; + padding: 5px 15px 9px; + margin: 0; + text-align: center; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + color: var(--dark-color); + font-size: $font-size-sm; + width: 100%; + } + } + } + } + } +} diff --git a/src/containers/sidebars/dashboard-sidebar/dashboard-home/index.js b/src/containers/sidebars/dashboard-sidebar/dashboard-home/index.js new file mode 100644 index 000000000..25e7681bd --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/dashboard-home/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import DashboardHomeComponent from './dashboard-home-component'; + +function DashboardHomeContainer(props) { + return ; +} + +export default DashboardHomeContainer; diff --git a/src/containers/sidebars/dashboard-sidebar/dashboard-sidebar-component.jsx b/src/containers/sidebars/dashboard-sidebar/dashboard-sidebar-component.jsx new file mode 100644 index 000000000..48ff2dfe2 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/dashboard-sidebar-component.jsx @@ -0,0 +1,106 @@ +import React, { useContext, useEffect } from 'react'; + +import { useT } from '@transifex/react'; + +import DarkModeIcon from '@mui/icons-material/DarkMode'; +import LightModeIcon from '@mui/icons-material/LightMode'; +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; + +import DashboardTrendsSidebarContainer from 'containers/sidebars/dashboard-trends-sidebar'; + +import { NAVIGATION } from 'constants/dashboard-constants.js'; + +import DashboardNav from '../../../components/dashboard-nav'; + +import BioDiversityContainer from './biodiversity-indicators'; +import DashboardHomeContainer from './dashboard-home'; +import styles from './dashboard-sidebar-styles.module.scss'; +import DataLayerContainer from './data-layers'; +import RegionsAnalysisContainer from './regions-analysis'; +import SpeciesFilterContainer from './species-filter'; + +function DashboardSidebar(props) { + const t = useT(); + const { + countryName, + selectedIndex, + map, + regionLayers, + setRegionLayers, + scientificName, + } = props; + + const { lightMode, toggleLightMode } = useContext(LightModeContext); + + const removeRegionLayers = () => { + const layers = regionLayers; + Object.keys(layers).forEach((region) => { + const foundLayer = map.layers.items.find((item) => item.id === region); + if (foundLayer) { + map.remove(foundLayer); + } + }); + }; + + useEffect(() => { + // TODO: find out why this doesn't work + if ( + selectedIndex !== NAVIGATION.TRENDS && + selectedIndex !== NAVIGATION.EXPLORE_SPECIES + ) { + removeRegionLayers(); + setRegionLayers({}); + } + }, [selectedIndex]); + + return ( +
+ +

{countryName}

+ +
+ + {selectedIndex === NAVIGATION.HOME && ( + + )} + {selectedIndex === NAVIGATION.EXPLORE_SPECIES && ( + + )} + {selectedIndex === NAVIGATION.REGION && ( + + )} + {!scientificName && selectedIndex === NAVIGATION.DATA_LAYER && ( + + )} + {scientificName && + (selectedIndex === NAVIGATION.SPECIES || + selectedIndex === NAVIGATION.DATA_LAYER) && ( + + )} + {selectedIndex === NAVIGATION.BIO_IND && ( + + )} + {selectedIndex === NAVIGATION.TRENDS && ( + + )} +
+
+ ); +} + +export default DashboardSidebar; diff --git a/src/containers/sidebars/dashboard-sidebar/dashboard-sidebar-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/dashboard-sidebar-styles.module.scss new file mode 100644 index 000000000..2aa92e9e2 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/dashboard-sidebar-styles.module.scss @@ -0,0 +1,144 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +$top: 25px; +$sidebar-species-width: 600px; +$trends-width: 1000px; + +.container { + @extend %verticalScrollbar; + @include backdropBlur(); + position: absolute; + top: $top; + left: $site-gutter; + display: flex; + flex-direction: column; + border: $sidebar-border; + border-radius: $sidebar-border-radius; + height: calc(100vh - #{$top} - 25px); + width: $sidebar-species-width; + z-index: $bring-to-front; + + &.trends{ + width: $trends-width; + } + + --nav-button-bg: #{$white}; + --svg-fill: #{$white}; + --svg-selected-fill: #{$black}; + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + &.light{ + --nav-button-bg: #{$black}; + --svg-fill: #{$black}; + --svg-selected-fill: #{$white}; + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + @include lightBackdropBlur(); + border: none; + } + + .darkMode{ + position: absolute; + top: 3px; + right: 3px; + + svg{ + width: 20px; + height: 20px; + fill: var(--svg-fill); + } + } + + h1{ + color: var(--font-color); + margin-left: 15px; + } + + .sidenav{ + display: flex; + height: 100%; + } + + .icons{ + display: flex; + flex-direction: column; + height: 100%; + border-right: 1px solid; + + button{ + width: 50px; + height: 50px; + + svg{ + width: 30px; + height: 30px; + fill: var(--svg-fill); + } + + &.selected, + &:hover{ + background: var(--nav-button-bg); + + svg{ + fill: var(--svg-selected-fill); + } + } + } + } + + .btnGroup{ + display: flex; + gap: 3px; + padding: 0 2px; + border-bottom: 1px solid var(--border-color); + + .saveButton{ + border: none; + } + } + + .saveButton { + color: var(--save-button-text); + background-color: var(--border-color); + &:hover { + background-color: var(--border-color-hover); + color: var(--save-button-text); + } + } + + .notActive{ + background-color: transparent; + color: var(--font-color); + border: 1px solid var(--border-color); + } + + .regionFilter{ + display: flex; + height: 100%; + + .filters{ + padding: 5px 10px; + gap: 20px; + display: flex; + align-items: flex-start; + justify-content: center; + width: 100%; + } + } + + .wrapper{ + width: 100%; + + .back{ + color: var(--font-color); + } + } +} diff --git a/src/containers/sidebars/dashboard-sidebar/data-layers/data-layers-component.jsx b/src/containers/sidebars/dashboard-sidebar/data-layers/data-layers-component.jsx new file mode 100644 index 000000000..3feab05d2 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/data-layers/data-layers-component.jsx @@ -0,0 +1,302 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { Line } from 'react-chartjs-2'; + +import { useT } from '@transifex/react'; + +import { getCSSVariable } from 'utils/css-utils'; + +import { + Chart as ChartJS, + LinearScale, + PointElement, + LineElement, + Tooltip, + Legend, + CategoryScale, + Filler, +} from 'chart.js'; +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; +import { Loading } from 'he-components'; + +import Button from 'components/button'; + +import { LAYER_OPTIONS, NAVIGATION } from 'constants/dashboard-constants.js'; + +import hrTheme from 'styles/themes/hr-theme.module.scss'; + +import SpeciesInfoContainer from '../species-info'; + +import styles from './data-layers-styles.module.scss'; +import DataLayersGroupedList from './grouped-list'; + +ChartJS.register( + LinearScale, + LineElement, + PointElement, + Tooltip, + Filler, + Legend, + CategoryScale +); + +function DataLayerComponent(props) { + const t = useT(); + const { speciesInfo, dataLayerData, selectedRegion, setSelectedIndex } = + props; + + const { lightMode } = useContext(LightModeContext); + const [dataPoints, setDataPoints] = useState(); + const [privateDataPoints, setPrivateDataPoints] = useState([ + { + label: t('Point Observations'), + items: [], + total_no_rows: '', + isActive: false, + showChildren: false, + id: LAYER_OPTIONS.POINT_OBSERVATIONS, + }, + ]); + const [regionsData, setRegionsData] = useState([ + { + label: t('Protected Areas'), + items: [], + total_no_rows: '', + isActive: false, + showChildren: false, + id: LAYER_OPTIONS.PROTECTED_AREAS, + }, + // 'Proposed Protection': { + // items: [], + // total_no_rows: '', + // isActive: false, + // showChildren: false, + // }, + { + label: t('Administrative Layers'), + items: [], + total_no_rows: '', + isActive: false, + showChildren: false, + id: LAYER_OPTIONS.ADMINISTRATIVE_LAYERS, + }, + ]); + const [isLoading, setIsLoading] = useState(true); + const [chartData, setChartData] = useState(); + const [showHabitatChart, setShowHabitatChart] = useState(false); + const [isHabitatChartLoading, setIsHabitatChartLoading] = useState(false); + + const chartOptions = { + plugins: { + title: { + display: false, + }, + legend: { + display: false, + }, + }, + scales: { + x: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Year'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + }, + }, + y: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Habitat Suitable Range'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + }, + }, + }, + }; + + const groupByTypeTitle = (objects) => { + const grouped = {}; + + objects.forEach((obj) => { + const groupKey = obj.type_title; + if ( + groupKey.toUpperCase() === 'EXPERT RANGE MAPS' || + groupKey.toUpperCase() === 'POINT OBSERVATIONS' + ) { + if (!grouped[groupKey]) { + grouped[groupKey] = { + label: obj.type_title, + total_no_rows: 0, + isActive: false, + showChildren: false, + items: [], + id: + groupKey.toUpperCase() === 'EXPERT RANGE MAPS' + ? LAYER_OPTIONS.EXPERT_RANGE_MAPS + : LAYER_OPTIONS.POINT_OBSERVATIONS, + }; // Create a new object with the original object's properties and an empty 'item' array + } + obj.isActive = false; + obj.parentId = grouped[groupKey].id; + grouped[groupKey].items.push(obj); // Push the current object into the 'item' array of the matching group + grouped[groupKey].total_no_rows += obj.no_rows || 0; // Summing the no_rows property + } + }); + + return Object.values(grouped); + }; + + const handleBack = () => { + if (selectedRegion) { + setSelectedIndex(NAVIGATION.EXPLORE_SPECIES); + } else { + setSelectedIndex(NAVIGATION.HOME); + } + }; + + const getHabitatMapData = async () => { + const habitatMapUrl = `https://next-api-dot-api-2-x-dot-map-of-life.appspot.com/2.x/species/indicators/habitat-trends/map?scientificname=${speciesInfo.scientificname}`; + const response = await fetch(habitatMapUrl); + const d = await response.json(); + + // remove Year row + d.data.shift(); + + setChartData({ + labels: d.data.map((item) => item[0]), + datasets: [ + { + fill: false, + backgroundColor: 'rgba(24, 186, 180, 1)', + borderColor: 'rgba(24, 186, 180, 1)', + pointStyle: false, + data: d.data.map((item) => item[2]), + }, + { + fill: '-1', + backgroundColor: 'rgba(24, 186, 180, 0.7)', + borderColor: 'rgba(24, 186, 180, 1)', + pointStyle: false, + data: d.data.map((item) => item[3]), + }, + ], + }); + }; + + useEffect(() => { + if (!speciesInfo) return; + getHabitatMapData(); + }, [speciesInfo]); + + useEffect(() => { + if (!dataLayerData) return; + const publicData = [ + ...groupByTypeTitle(dataLayerData), + { + label: 'Habitat Loss/Gain', + items: [], + id: LAYER_OPTIONS.HABITAT, + total_no_rows: '', + isActive: false, + showChildren: false, + }, + ]; + setDataPoints(publicData); + }, [dataLayerData]); + + useEffect(() => { + if (!dataPoints) return; + setIsLoading(false); + }, [dataPoints]); + + return ( +
+
+ {t('Data Layers')} +
+
+ + +
+ {isLoading && } + {!isLoading && dataPoints && ( + <> + + + {chartData && isHabitatChartLoading && } + {chartData && showHabitatChart && ( + + )} + +
+ + +
+ + + + )} +
+ ); +} + +export default DataLayerComponent; diff --git a/src/containers/sidebars/dashboard-sidebar/data-layers/data-layers-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/data-layers/data-layers-styles.module.scss new file mode 100644 index 000000000..9ba020d5b --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/data-layers/data-layers-styles.module.scss @@ -0,0 +1,105 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + padding: 5px 10px; + width: 100%; + + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + .sectionTitle{ + font-size: $font-size-tick; + font-weight: 700; + text-transform: uppercase; + line-height: 1.5; + color: var(--font-color); + } + + .data{ + display: flex; + flex-direction: column; + row-gap: 10px; + + .saveButton { + color: var(--save-button-text); + background-color: var(--border-color); + &:hover { + background-color: var(--border-color-hover); + color: var(--save-button-text); + } + } + } + + .back{ + color: var(--font-color); + } + + .areaNameEdit { + @extend %display2; + background-color: transparent; + border-width: 0 0 2px 0; + color: var(--font-color); + margin: 0; + max-width: 300px; + } + + .searchInput { + background-color: transparent; + min-width: 300px; + border: none; + margin-left: 1em; + color: var(--font-color); + &::placeholder { + color: $grey-text; + text-transform: uppercase; + } + } + + .distributionTitle{ + display: flex; + gap: 8px; + align-items: center; + font-size: $font-size-sm; + font-weight: 700; + text-transform: uppercase; + line-height: 1.5; + color: var(--font-color); + } + + .arrowIcon { + fill: var(--font-color); + transform: rotate(0deg); + + &.isOpened { + transform:rotate(90deg); + } + + transition: transform 0.2s ease; + + > path { + fill: inherit; + } + } + + .productTypeLogo { + height: 25px; + width: 25px; + } +} + diff --git a/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/grouped-list-component.jsx b/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/grouped-list-component.jsx new file mode 100644 index 000000000..16b0906dc --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/grouped-list-component.jsx @@ -0,0 +1,502 @@ +import React, { useContext } from 'react'; + +import { useT } from '@transifex/react'; + +import { + PROVINCE_FEATURE_GLOBAL_OUTLINE_ID, + SPECIES_LAYER_IDS, +} from 'utils/dashboard-utils'; +import { GBIF_OCCURENCE_URL } from 'utils/dashboard-utils.js'; + +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; + +import EsriFeatureService from 'services/esri-feature-service'; + +import { + LAYER_TITLE_TYPES, + LAYER_OPTIONS, +} from 'constants/dashboard-constants.js'; + +import ArrowIcon from 'icons/arrow_right.svg?react'; + +import styles from './grouped-list-styles.module.scss'; + +function GroupedListComponent(props) { + const { + dataPoints, + countryISO, + setDataPoints, + map, + view, + speciesInfo, + regionLayers, + setRegionLayers, + setShowHabitatChart, + setIsHabitatChartLoading, + isPrivate, + } = props; + const t = useT(); + const { lightMode } = useContext(LightModeContext); + + const expertRangeMapIds = [ + 'ec694c34-bddd-4111-ba99-926a5f7866e8', + '0ed89f4f-3ed2-41c2-9792-7c7314a55455', + '98f229de-6131-41ef-aff1-7a52212b5a15', + 'd542e050-2ae5-457e-8476-027741538965', + ]; + + const pointObservationIds = [ + '9905692e-6a28-4310-b01e-476a471e5bf8', + '794adb49-7458-41c4-a1c0-56537fdbec1d', + ]; + + const searchForLayers = (layerName) => { + return map.layers.items.findIndex((item) => item.id === layerName); + }; + + const displayChildren = (key) => { + const showChildren = !key.showChildren; + + const updatedDataPoints = dataPoints.map((dp) => { + if (dp.id === key.id) { + return { ...dp, showChildren }; + } + return dp; + }); + setDataPoints(updatedDataPoints); + }; + + const displaySingleLayer = (item) => { + if (item.items?.length === 0) { + const updatedDataPoints = dataPoints.map((dp) => { + if (dp.id === item.id) { + return { ...dp, isActive: !dp.isActive }; + } + return dp; + }); + setDataPoints(updatedDataPoints); + } else { + const updatedDataPoints = dataPoints.map((dp) => { + if (dp.id === item.parentId) { + return { + ...dp, + items: [ + ...dp.items.map((point) => { + return point?.dataset_id === item.dataset_id + ? { ...point, isActive: !point.isActive } + : point; + }), + ], + }; + } + return dp; + }); + setDataPoints(updatedDataPoints); + } + + const id = item.id ?? item.parentId; + if (id === LAYER_OPTIONS.PROTECTED_AREAS) { + if (!item.isActive) { + const layers = EsriFeatureService.addProtectedAreaLayer( + null, + countryISO + ); + + setRegionLayers((rl) => ({ + ...rl, + [id]: layers, + })); + map.add(layers); + } else { + const layer = regionLayers[id]; + setRegionLayers((rl) => { + const { [id]: name, ...rest } = rl; + return rest; + }); + map.remove(layer); + } + } else if (id === LAYER_OPTIONS.ADMINISTRATIVE_LAYERS) { + if (!item.isActive) { + const layer = EsriFeatureService.getFeatureLayer( + PROVINCE_FEATURE_GLOBAL_OUTLINE_ID, + countryISO, + id + ); + + setRegionLayers((rl) => ({ + ...rl, + [id]: layer, + })); + map.add(layer); + } else { + const layer = regionLayers[id]; + setRegionLayers((rl) => { + const { [id]: name, ...rest } = rl; + return rest; + }); + map.remove(layer); + } + } else if (id === LAYER_OPTIONS.HABITAT) { + const layerName = id; + if (!item.isActive) { + setIsHabitatChartLoading(true); + const webTileLayer = EsriFeatureService.getXYZLayer( + speciesInfo.scientificname.replace(' ', '_'), + layerName, + LAYER_TITLE_TYPES.TREND + ); + + let layerIndex = searchForLayers(LAYER_OPTIONS.HABITAT) - 1; + + if (layerIndex < 0) { + layerIndex = searchForLayers('GBIF (2023)') - 1; + } + + if (layerIndex < 0) { + layerIndex = map.layers.items.length; + } + + webTileLayer.then((layer) => { + setRegionLayers((rl) => ({ + ...rl, + [layerName]: layer, + })); + map.add(layer); + + view.whenLayerView(layer).then((layerView) => { + layerView.watch('updating', (val) => { + if (!val) { + setIsHabitatChartLoading(false); + setShowHabitatChart(true); + } + }); + }); + }); + } else { + setShowHabitatChart(false); + setRegionLayers((rl) => { + const { [layerName]: name, ...rest } = rl; + return rest; + }); + map.remove(regionLayers[layerName]); + } + } else if (id === LAYER_OPTIONS.POINT_OBSERVATIONS) { + if (isPrivate) { + if (!item.isActive) { + let url; + + switch (speciesInfo.scientificname.toLowerCase()) { + case 'myotis bocagii': + url = SPECIES_LAYER_IDS.Myotis_bocagii; + break; + case 'hyperolius castaneus': + url = SPECIES_LAYER_IDS.Hyperolius_castaneus; + break; + case 'chiromantis rufescens': + url = SPECIES_LAYER_IDS.Chiromantis_rufescens; + break; + default: + break; + } + const layer = EsriFeatureService.getFeatureLayer(url, null, id); + + setRegionLayers((rl) => ({ + ...rl, + [id]: layer, + })); + map.add(layer); + } else { + const layer = regionLayers.POINT_OBSERVATIONS; + setRegionLayers((rl) => { + const { [id]: name, ...rest } = rl; + return rest; + }); + map.remove(layer); + } + } + } + }; + + const findLayerToShow = (item) => { + const layerParent = item.type_title.toUpperCase(); + const layerName = item.dataset_title.toUpperCase(); + let layer; + + if (layerParent === LAYER_TITLE_TYPES.EXPERT_RANGE_MAPS) { + if (!item.isActive) { + if (expertRangeMapIds.find((id) => id === item.dataset_id)) { + const webTileLayer = EsriFeatureService.getXYZLayer( + speciesInfo.scientificname.replace(' ', '_'), + layerName, + LAYER_TITLE_TYPES.EXPERT_RANGE_MAPS + ); + + let layerIndex = searchForLayers(LAYER_OPTIONS.HABITAT) - 1; + + if (layerIndex < 0) { + layerIndex = searchForLayers('GBIF (2023)') - 1; + } + + if (layerIndex < 0) { + layerIndex = map.layers.items.length; + } + + webTileLayer.then((l) => { + setRegionLayers((rl) => ({ + ...rl, + [layerName]: l, + })); + map.add(l, layerIndex); + }); + } + } else { + setRegionLayers((rl) => { + const { [layerName]: name, ...rest } = rl; + return rest; + }); + map.remove(regionLayers[layerName]); + } + } + + if (layerParent === LAYER_TITLE_TYPES.POINT_OBSERVATIONS) { + if (!item.isActive) { + if (pointObservationIds.find((id) => id === item.dataset_id)) { + let name; + + if (layerName.match(/EBIRD/)) { + name = `ebird_${speciesInfo.scientificname.replace(' ', '_')}`; + + layer = EsriFeatureService.getGeoJsonLayer( + name, + layerName, + countryISO + ); + setRegionLayers((rl) => ({ + ...rl, + [layerName]: layer, + })); + } else if (layerName.match(/GBIF/)) { + name = `gbif_${speciesInfo.scientificname.replace(' ', '_')}`; + + layer = EsriFeatureService.getFeatureOccurenceLayer( + GBIF_OCCURENCE_URL, + speciesInfo.scientificname, + layerName + ); + + setRegionLayers((rl) => ({ + ...rl, + [layerName]: layer, + })); + } + + let layerIndex = searchForLayers(LAYER_OPTIONS.HABITAT); + + if (layerIndex < 0) { + layerIndex = map.layers.items.length; + } + map.add(layer, layerIndex + 1); + } + } else { + // const { [layerName]: name, ...rest } = regionLayers; + setRegionLayers((rl) => { + const { [layerName]: name, ...rest } = rl; + return rest; + }); + + map.remove(regionLayers[layerName]); + } + } + + if (layerParent === LAYER_TITLE_TYPES.REGIONAL_CHECKLISTS) { + if (!item.isActive) { + const layer = EsriFeatureService.getMVTSource(); + + setRegionLayers((rl) => ({ + ...rl, + [layerName]: layer, + })); + + map.addSource('mapTiles', { + type: 'vector', + tiles: [ + 'https://production-dot-tiler-dot-map-of-life.appspot.com/0.x/tiles/regions/regions/{proj}/{z}/{x}/{y}.pbf?region_id=1673cab0-c717-4367-9db0-5c63bf26944d', + ], + }); + + map.add({ + id: 'mvt-fill', + type: 'fill', + source: 'mapTiles', + }); + } else { + setRegionLayers((rl) => { + const { [layerName]: name, ...rest } = rl; + return rest; + }); + map.remove(regionLayers[layerName]); + } + } + + displaySingleLayer(item); + }; + + // update value of all children + const updateChildren = (item) => { + const isActive = !item.isActive; + + const typeItems = item.items; + + typeItems.map((i) => { + findLayerToShow(i); + i.isActive = isActive; + }); + + const updatedDataPoints = dataPoints.map((dp) => { + if (dp.id === item.id) { + return { ...dp, isActive, items: [...typeItems] }; + } + return dp; + }); + setDataPoints(updatedDataPoints); + + // setDataPoints({ + // ...dataPoints, + // [item]: { + // ...item, + // isActive, + // items: [...typeItems], + // }, + // }); + }; + + // check if some but not all children are selected + const checkIfSomeChecked = (key) => { + const { items } = key; + const typeItems = + items.some((item) => item.isActive === true) && + !items.every((item) => item.isActive === true); + return typeItems; + }; + + // check if all children are selected + const checkIfAllChecked = (key) => { + const { items } = key; + const typeItems = items.every((item) => item.isActive === true); + key.isActive = typeItems; + return typeItems; + }; + + const getCheckbox = (item) => { + let control = ( + findLayerToShow(item)} + checked={item.isActive} + /> + } + /> + ); + + if (item.parentId === LAYER_OPTIONS.EXPERT_RANGE_MAPS) { + if (!expertRangeMapIds.find((id) => id === item.dataset_id)) { + control = ( + } + /> + ); + } + } + + if (item.parentId === LAYER_OPTIONS.POINT_OBSERVATIONS) { + if (!pointObservationIds.find((id) => id === item.dataset_id)) { + control = ( + } + /> + ); + } + } + + return control; + }; + + return ( +
+ {dataPoints.map((key) => ( +
+ {key.items?.length > 0 && ( + <> +
+ + updateChildren(key)} + /> + } + /> + {/* Place holder, remove span when new icons are available */} + + {/* */} + {key.total_no_rows} +
+ {key.showChildren && ( +
    + {key.items.map((item) => ( +
  • + {getCheckbox(item)} + + + {item.no_rows} +
  • + ))} +
+ )} + + )} + {key.items?.length === 0 && ( + + )} +
+ ))} +
+ ); +} + +export default GroupedListComponent; diff --git a/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/grouped-list-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/grouped-list-styles.module.scss new file mode 100644 index 000000000..320294a85 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/grouped-list-styles.module.scss @@ -0,0 +1,84 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + --disabled: rgba(255, 255, 255, 0.3); + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + --disabled: rgba(0,0,0,0.5); + } + + .arrowIcon { + fill: var(--font-color); + transform: rotate(0deg); + + &.isOpened { + transform:rotate(90deg); + } + + transition: transform 0.2s ease; + + > path { + fill: inherit; + } + } + + ul{ + margin:0; + list-style-type: none; + } + + .parent, + .children{ + display: grid; + column-gap: 15px; + align-items: center; + margin-left: 15px; + color: var(--font-color); + text-transform: capitalize; + + span{ + color: var(--font-color); + + &[aria-disabled="true"]{ + color: var(--disabled); + } + } + + > span{ + text-align: center; + } + } + + .disabled{ + + span{ + color: var(--disabled) !important; + } + } + + .parent{ + grid-template-columns: 15px 1fr 30px 50px; + } + + .children{ + grid-template-columns: 1fr 30px 50px; + } + + .productTypeLogo { + height: 25px; + width: 25px; + } +} + diff --git a/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/index.js b/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/index.js new file mode 100644 index 000000000..8c7e41a3a --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/data-layers/grouped-list/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import GroupedListComponent from './grouped-list-component'; + +function DataLayersGroupedList(props) { + return ; +} + +export default DataLayersGroupedList; diff --git a/src/containers/sidebars/dashboard-sidebar/data-layers/index.js b/src/containers/sidebars/dashboard-sidebar/data-layers/index.js new file mode 100644 index 000000000..2ef6dd758 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/data-layers/index.js @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { connect } from 'react-redux'; +import metadataActions from 'redux_modules/metadata'; + +import * as urlActions from 'actions/url-actions'; + +import { layerManagerToggle } from 'utils/layer-manager-utils'; + +import { LAYERS_CATEGORIES } from 'constants/mol-layers-configs'; + +import DataLayerComponent from './data-layers-component'; + +const actions = { ...metadataActions, ...urlActions }; + +function DataLayerContainer(props) { + const { activeLayers, changeGlobe } = props; + + const [selectedLayers, setSelectedLayers] = useState([]); + + const handleLayerToggle = (option) => { + if (selectedLayers.find((layer) => layer === option.value)) { + setSelectedLayers( + selectedLayers.filter((layer) => layer !== option.value) + ); + } else { + setSelectedLayers([...selectedLayers, option.value]); + } + + layerManagerToggle( + option.value, + activeLayers, + changeGlobe, + LAYERS_CATEGORIES.PROTECTION + ); + }; + + return ( + + ); +} + +export default connect(null, actions)(DataLayerContainer); diff --git a/src/containers/sidebars/dashboard-sidebar/index.js b/src/containers/sidebars/dashboard-sidebar/index.js new file mode 100644 index 000000000..e44f4205a --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { connect } from 'react-redux'; + +import Component from './dashboard-sidebar-component.jsx'; +import mapStateToProps from './selectors'; + +function DashboardSidebarContainer(props) { + return ; +} + +export default connect(mapStateToProps, null)(DashboardSidebarContainer); diff --git a/src/containers/sidebars/dashboard-sidebar/mol-country-attributes.json b/src/containers/sidebars/dashboard-sidebar/mol-country-attributes.json new file mode 100644 index 000000000..48d3c3081 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/mol-country-attributes.json @@ -0,0 +1 @@ +[{"GEO_ID":1,"ISO3":"ABW","NAME":"Aruba","REGION_ID":"fee99b31-465c-4edc-acbe-0ca31cce4d0e"},{"GEO_ID":2,"ISO3":"AFG","NAME":"Afghanistan","REGION_ID":"f1b0a8bd-ccbc-43ad-82c6-d665598fcbf7"},{"GEO_ID":3,"ISO3":"AGO","NAME":"Angola","REGION_ID":"a01885ee-84fa-4ccd-a4af-feedeb02750c"},{"GEO_ID":4,"ISO3":"AIA","NAME":"Anguilla","REGION_ID":"eb9a67f8-c6ee-4bf1-a310-c41c1fcf1704"},{"GEO_ID":5,"ISO3":"ALA","NAME":"Åland","REGION_ID":"6f680399-366c-41a1-be03-3c70d06fbfe6"},{"GEO_ID":6,"ISO3":"ALB","NAME":"Albania","REGION_ID":"910dd100-dd79-467d-a6a1-0fdfd49ac6d4"},{"GEO_ID":7,"ISO3":"AND","NAME":"Andorra","REGION_ID":"24971000-3230-4eab-9866-5ae8ff7ed2a8"},{"GEO_ID":8,"ISO3":"ARE","NAME":"United Arab Emirates","REGION_ID":"01f5669a-bd35-46eb-b4d6-48b1e00296fd"},{"GEO_ID":9,"ISO3":"ARG","NAME":"Argentina","REGION_ID":"897d05e6-6dfc-426c-927c-99bbb5218a77"},{"GEO_ID":10,"ISO3":"ARM","NAME":"Armenia","REGION_ID":"a7969e69-adaa-40d8-ba02-cbdd98084a78"},{"GEO_ID":11,"ISO3":"ASM","NAME":"American Samoa","REGION_ID":"2f4eb1ad-4698-494e-a01e-97c9544b31ab"},{"GEO_ID":12,"ISO3":"ATA","NAME":"Antarctica","REGION_ID":"9fb0cae7-ee8f-4475-89c0-bc1c64b87db6"},{"GEO_ID":13,"ISO3":"ATF","NAME":"French Southern Territories","REGION_ID":"9b86c26a-8230-4c59-8464-db0865abbb0c"},{"GEO_ID":14,"ISO3":"ATG","NAME":"Antigua and Barbuda","REGION_ID":"ad0f6204-f4dd-4aa9-80e2-5d52016a4d06"},{"GEO_ID":15,"ISO3":"AUS","NAME":"Australia","REGION_ID":"52113000-c475-4db4-a46c-3203ba706054"},{"GEO_ID":16,"ISO3":"AUT","NAME":"Austria","REGION_ID":"0c89fafd-0896-4abd-a5fe-c41cdb4ac4ac"},{"GEO_ID":17,"ISO3":"AZE","NAME":"Azerbaijan","REGION_ID":"42b69ecf-0f24-40c4-b493-ffabea3c4ce3"},{"GEO_ID":18,"ISO3":"BDI","NAME":"Burundi","REGION_ID":"4771400a-f813-44be-953b-c4a5a17b0877"},{"GEO_ID":19,"ISO3":"BEL","NAME":"Belgium","REGION_ID":"85f90819-4add-484b-b980-284882a7635d"},{"GEO_ID":20,"ISO3":"BEN","NAME":"Benin","REGION_ID":"f00533f8-4726-40b9-af1e-d40ffee23b0d"},{"GEO_ID":21,"ISO3":"BES","NAME":"Bonaire, Sint Eustatius and Saba","REGION_ID":"82721b3b-390d-4462-95a9-f02f03aff561"},{"GEO_ID":22,"ISO3":"BFA","NAME":"Burkina Faso","REGION_ID":"c189ad16-8f17-4b77-9ae0-4e04a230acdd"},{"GEO_ID":23,"ISO3":"BGD","NAME":"Bangladesh","REGION_ID":"979aa058-c5d2-4ba0-9879-d68387542dff"},{"GEO_ID":24,"ISO3":"BGR","NAME":"Bulgaria","REGION_ID":"99986896-05e9-403f-be52-afd6407f6a6a"},{"GEO_ID":25,"ISO3":"BHR","NAME":"Bahrain","REGION_ID":"a5ab7864-ba9d-4147-b054-1ffbe4153ed0"},{"GEO_ID":26,"ISO3":"BHS","NAME":"Bahamas","REGION_ID":"007d3ec6-b50d-4ad2-963b-e82d3e6e25e8"},{"GEO_ID":27,"ISO3":"BIH","NAME":"Bosnia and Herzegovina","REGION_ID":"598adc43-b1b3-4eed-b951-4a122b509ac2"},{"GEO_ID":28,"ISO3":"BLM","NAME":"Saint-Barthélemy","REGION_ID":"8e3b3c8a-112f-477a-95c9-7ccdfc5505d7"},{"GEO_ID":29,"ISO3":"BLR","NAME":"Belarus","REGION_ID":"b9706318-c602-4c94-8fec-707b818774a2"},{"GEO_ID":30,"ISO3":"BLZ","NAME":"Belize","REGION_ID":"7524378b-e95d-4afe-9954-6e97ddfbdaf8"},{"GEO_ID":31,"ISO3":"BMU","NAME":"Bermuda","REGION_ID":"78172139-419d-4b5b-89ce-3a2d1bc3dd6f"},{"GEO_ID":32,"ISO3":"BOL","NAME":"Bolivia","REGION_ID":"b324f90b-25f5-4ad0-86b7-0bf39eea64f8"},{"GEO_ID":33,"ISO3":"BRA","NAME":"Brazil","REGION_ID":"ca3e23e7-1d60-464e-9e1a-61ae9e1cb006"},{"GEO_ID":34,"ISO3":"BRB","NAME":"Barbados","REGION_ID":"2d6e1b52-5620-416d-b6dc-b6b885508a8f"},{"GEO_ID":35,"ISO3":"BRN","NAME":"Brunei","REGION_ID":"83a421a3-6da2-4c89-9af5-5170b3085d6f"},{"GEO_ID":36,"ISO3":"BTN","NAME":"Bhutan","REGION_ID":"b58ab549-4ee2-47c6-a611-ca0c30ec94fd"},{"GEO_ID":37,"ISO3":"BVT","NAME":"Bouvet Island","REGION_ID":"31592207-25e0-471d-a8ee-e0f624cd5f38"},{"GEO_ID":38,"ISO3":"BWA","NAME":"Botswana","REGION_ID":"794637ce-4182-44b7-bf5b-b7dd625ade74"},{"GEO_ID":39,"ISO3":"CAF","NAME":"Central African Republic","REGION_ID":"d286bae6-afee-43a3-a490-d13356c0057f"},{"GEO_ID":40,"ISO3":"CAN","NAME":"Canada","REGION_ID":"8c90cf0d-2b0c-42ab-864d-43ff2f7f1145"},{"GEO_ID":41,"ISO3":"CCK","NAME":"Cocos Islands","REGION_ID":"b5de4439-9143-4ef8-8e57-e04b1c132af6"},{"GEO_ID":42,"ISO3":"CHE","NAME":"Switzerland","REGION_ID":"1121bb2d-fd3e-49ea-8e61-e5b4aab8a538"},{"GEO_ID":43,"ISO3":"CHL","NAME":"Chile","REGION_ID":"f8207868-1091-4e18-9680-e6e54220fd22"},{"GEO_ID":44,"ISO3":"CHN","NAME":"China","REGION_ID":"10befcf4-155b-46ef-a6a8-ca3a8e68fd19"},{"GEO_ID":45,"ISO3":"CIV","NAME":"Côte d'Ivoire","REGION_ID":"ce75cfca-a5e8-44e6-95a3-1467e26b4639"},{"GEO_ID":46,"ISO3":"CMR","NAME":"Cameroon","REGION_ID":"3fda604a-28a1-40ae-93b5-4e852c398e09"},{"GEO_ID":47,"ISO3":"COD","NAME":"Democratic Republic of the Congo","REGION_ID":"90b03e87-3880-4164-a310-339994e3f919"},{"GEO_ID":48,"ISO3":"COG","NAME":"Republic of Congo","REGION_ID":"0c98b276-f38a-4a2e-abab-0acfad46ac69"},{"GEO_ID":49,"ISO3":"COK","NAME":"Cook Islands","REGION_ID":"dcc14246-ee6e-4a5e-abff-2216536894b4"},{"GEO_ID":50,"ISO3":"COL","NAME":"Colombia","REGION_ID":"5eeefdc1-03d2-4686-a447-d79402a93425"},{"GEO_ID":51,"ISO3":"COM","NAME":"Comoros","REGION_ID":"ec7907ed-6de5-4f63-b570-beba1207baa5"},{"GEO_ID":52,"ISO3":"CPV","NAME":"Cape Verde","REGION_ID":"1a45fe3b-6b5f-4ab4-9a3a-7c901bd8c357"},{"GEO_ID":53,"ISO3":"CRI","NAME":"Costa Rica","REGION_ID":"fba67d08-ee5d-4d7f-a5ab-4ddd7be134df"},{"GEO_ID":54,"ISO3":"CUB","NAME":"Cuba","REGION_ID":"15ba9e24-fac6-465c-a5d1-0034cdf7456f"},{"GEO_ID":55,"ISO3":"CUW","NAME":"Curaçao","REGION_ID":"46cf7bba-1db8-45d0-a8ca-c47d22d2e48c"},{"GEO_ID":56,"ISO3":"CXR","NAME":"Christmas Island","REGION_ID":"c505d69a-9382-4a65-9b37-38190c619675"},{"GEO_ID":57,"ISO3":"CYM","NAME":"Cayman Islands","REGION_ID":"6a560fd5-59ef-4426-bfee-7801c9c6fbd6"},{"GEO_ID":58,"ISO3":"CYP","NAME":"Cyprus","REGION_ID":"f86004b3-d504-4934-92d8-92ef2f808492"},{"GEO_ID":59,"ISO3":"CZE","NAME":"Czech Republic","REGION_ID":"00f56e11-6f89-4b61-8a65-62f722c8b738"},{"GEO_ID":60,"ISO3":"DEU","NAME":"Germany","REGION_ID":"48dbf7cf-b380-4c33-8a4d-5b8c892d710f"},{"GEO_ID":61,"ISO3":"DJI","NAME":"Djibouti","REGION_ID":"e1454f19-ee49-4268-a4f7-c0314914f48a"},{"GEO_ID":62,"ISO3":"DMA","NAME":"Dominica","REGION_ID":"a234c9da-5dfa-4171-8ebe-2dcb8faf2358"},{"GEO_ID":63,"ISO3":"DNK","NAME":"Denmark","REGION_ID":"d6ab9c75-0607-41ed-9f7e-64213f853cba"},{"GEO_ID":64,"ISO3":"DOM","NAME":"Dominican Republic","REGION_ID":"6529d262-30b0-4dc2-ac8c-b14db4760c2d"},{"GEO_ID":65,"ISO3":"DZA","NAME":"Algeria","REGION_ID":"96abdaec-88f3-43d0-9193-fcb36d7f05c8"},{"GEO_ID":66,"ISO3":"ECU","NAME":"Ecuador","REGION_ID":"820244c0-44c5-4fe3-81d3-f01556fde5c5"},{"GEO_ID":67,"ISO3":"EGY","NAME":"Egypt","REGION_ID":"5ea33967-35d9-4ca4-b1fa-9b3f10f53641"},{"GEO_ID":68,"ISO3":"ERI","NAME":"Eritrea","REGION_ID":"fb302f98-520b-45ac-9334-96de07f93c78"},{"GEO_ID":69,"ISO3":"ESH","NAME":"Western Sahara","REGION_ID":"880c22a0-9375-48e6-b208-7c3af0663c44"},{"GEO_ID":70,"ISO3":"ESP","NAME":"Spain","REGION_ID":"15393966-6b57-4447-9f0c-e50077c516d4"},{"GEO_ID":71,"ISO3":"EST","NAME":"Estonia","REGION_ID":"f72e2031-af5e-4fa6-af37-e4309936742f"},{"GEO_ID":72,"ISO3":"ETH","NAME":"Ethiopia","REGION_ID":"b74ac2e3-f8ca-4d46-81b9-f1a685ba0657"},{"GEO_ID":73,"ISO3":"FIN","NAME":"Finland","REGION_ID":"eae62a5b-f89a-4f84-98c2-f9efc3e2ca97"},{"GEO_ID":74,"ISO3":"FJI","NAME":"Fiji","REGION_ID":"52423050-b862-400f-b944-ec49b8f8e261"},{"GEO_ID":75,"ISO3":"FLK","NAME":"Falkland Islands","REGION_ID":"d21a9cb7-32dc-415f-a555-6d6dbf9ae948"},{"GEO_ID":76,"ISO3":"FRA","NAME":"France","REGION_ID":"4a400958-7ee5-4619-b1e9-5f0b2469e349"},{"GEO_ID":77,"ISO3":"FRO","NAME":"Faroe Islands","REGION_ID":"3e07160b-9ebc-408e-9d4c-3d37b7044688"},{"GEO_ID":78,"ISO3":"FSM","NAME":"Micronesia","REGION_ID":"97d0b1f9-cc34-4614-9ef4-5499ea67dcab"},{"GEO_ID":79,"ISO3":"GAB","NAME":"Gabon","REGION_ID":"30810b40-0044-46dd-a1cd-5a3217749738"},{"GEO_ID":80,"ISO3":"GBR","NAME":"United Kingdom","REGION_ID":"21a8e228-fc67-4e87-96bd-ee7f8beaeb47"},{"GEO_ID":81,"ISO3":"GEO","NAME":"Georgia","REGION_ID":"7eb6c4ea-688f-428e-9924-5821b106df77"},{"GEO_ID":82,"ISO3":"GGY","NAME":"Guernsey","REGION_ID":"64b547cb-8318-4284-a061-b6edb52efe84"},{"GEO_ID":83,"ISO3":"GHA","NAME":"Ghana","REGION_ID":"43fd0520-1126-45d2-8dd4-ca9bcbf27735"},{"GEO_ID":84,"ISO3":"GIB","NAME":"Gibraltar","REGION_ID":"f4d513e0-cfa7-490b-93b0-e186e17b0fc9"},{"GEO_ID":85,"ISO3":"GIN","NAME":"Guinea","REGION_ID":"22200606-e907-497f-96db-e7cfd95d61b5"},{"GEO_ID":86,"ISO3":"GLP","NAME":"Guadeloupe","REGION_ID":"e10b34e0-c00e-4752-b023-0ca37bf0767f"},{"GEO_ID":87,"ISO3":"GMB","NAME":"Gambia","REGION_ID":"dc86208f-3e05-4fa2-8a61-19c2ba90b903"},{"GEO_ID":88,"ISO3":"GNB","NAME":"Guinea-Bissau","REGION_ID":"15c15d29-31f0-47cd-b22d-38ffc3764b84"},{"GEO_ID":89,"ISO3":"GNQ","NAME":"Equatorial Guinea","REGION_ID":"35657c66-a459-4131-a767-348bb2687742"},{"GEO_ID":90,"ISO3":"GRC","NAME":"Greece","REGION_ID":"a8511cc6-dd5e-4616-a045-32031f3a86ac"},{"GEO_ID":91,"ISO3":"GRD","NAME":"Grenada","REGION_ID":"7cef5f1a-3dbe-4a59-be66-c550ad4dced9"},{"GEO_ID":92,"ISO3":"GRL","NAME":"Greenland","REGION_ID":"73f872b4-7723-41db-be54-7ce29919357c"},{"GEO_ID":93,"ISO3":"GTM","NAME":"Guatemala","REGION_ID":"6e20d4c8-9c19-4cbd-b8ac-96107776a65e"},{"GEO_ID":94,"ISO3":"GUF","NAME":"French Guiana","REGION_ID":"2a779654-4086-430a-89e0-8dcb54772845"},{"GEO_ID":95,"ISO3":"GUM","NAME":"Guam","REGION_ID":"87a30e40-4c04-487a-acae-3b35c9ba4728"},{"GEO_ID":96,"ISO3":"GUY","NAME":"Guyana","REGION_ID":"25ca0de8-c215-4cda-aafd-f6d644ed9e4a"},{"GEO_ID":97,"ISO3":"HKG","NAME":"Hong Kong","REGION_ID":"f1ea9f6f-b616-49c4-a1a8-6b0ee7d032bf"},{"GEO_ID":98,"ISO3":"HMD","NAME":"Heard Island and McDonald Islands","REGION_ID":"de029d28-87a9-4cb5-ad0e-de8d82fe6529"},{"GEO_ID":99,"ISO3":"HND","NAME":"Honduras","REGION_ID":"933013bd-2a70-44f4-83a7-d3c1f4086cbd"},{"GEO_ID":100,"ISO3":"HRV","NAME":"Croatia","REGION_ID":"9b794015-a7c8-4a2b-b7a0-12dded43b3f9"},{"GEO_ID":101,"ISO3":"HTI","NAME":"Haiti","REGION_ID":"f447037c-09cf-47f3-be49-c260544c3aa1"},{"GEO_ID":102,"ISO3":"HUN","NAME":"Hungary","REGION_ID":"5647081c-7f77-4a3d-962f-2c39d66b0be5"},{"GEO_ID":103,"ISO3":"IDN","NAME":"Indonesia","REGION_ID":"3ccb99e1-0f2c-4ed9-a35b-3fbcfaa5ed11"},{"GEO_ID":104,"ISO3":"IMN","NAME":"Isle of Man","REGION_ID":"fcd94225-2fc6-4a2a-8017-4621a392b192"},{"GEO_ID":105,"ISO3":"IND","NAME":"India","REGION_ID":"7410608a-f752-4b63-9698-34f2a0ce4eb3"},{"GEO_ID":106,"ISO3":"IOT","NAME":"British Indian Ocean Territory","REGION_ID":"aca525ef-0ede-4ce7-9de2-fba79b1f418f"},{"GEO_ID":107,"ISO3":"IRL","NAME":"Ireland","REGION_ID":"59eae850-c83f-4927-aa41-195e50bc9bc7"},{"GEO_ID":108,"ISO3":"IRN","NAME":"Iran","REGION_ID":"fdee96ad-6852-4f1a-9760-84ae8e92e001"},{"GEO_ID":109,"ISO3":"IRQ","NAME":"Iraq","REGION_ID":"106a1e38-0ced-461a-886b-1f1248c09b5f"},{"GEO_ID":110,"ISO3":"ISL","NAME":"Iceland","REGION_ID":"db119bef-4f14-4315-9647-bf7c77125e8a"},{"GEO_ID":111,"ISO3":"ISR","NAME":"Israel","REGION_ID":"7ffaa41d-1d6b-4595-8ca4-2e8d4878da54"},{"GEO_ID":112,"ISO3":"ITA","NAME":"Italy","REGION_ID":"22c5e5d3-b096-40fb-8982-5a3a2c3396f3"},{"GEO_ID":113,"ISO3":"JAM","NAME":"Jamaica","REGION_ID":"d3e41119-d06c-4ece-adcb-aa4afe7b3de5"},{"GEO_ID":114,"ISO3":"JEY","NAME":"Jersey","REGION_ID":"c99a8cf8-6150-46f9-84a8-9d6276fe396b"},{"GEO_ID":115,"ISO3":"JOR","NAME":"Jordan","REGION_ID":"fc3b331c-f81d-4bb4-a7aa-d8973247dce2"},{"GEO_ID":116,"ISO3":"JPN","NAME":"Japan","REGION_ID":"aa03f64e-0044-485f-9371-e6730ef97dee"},{"GEO_ID":117,"ISO3":"KAZ","NAME":"Kazakhstan","REGION_ID":"2cffda41-307f-4a2c-b675-04a4794b3e5a"},{"GEO_ID":118,"ISO3":"KEN","NAME":"Kenya","REGION_ID":"f839d936-e194-4a3a-b6e8-1812a7b4f827"},{"GEO_ID":119,"ISO3":"KGZ","NAME":"Kyrgyzstan","REGION_ID":"8d385ef3-735f-4e59-b986-b814a59beedd"},{"GEO_ID":120,"ISO3":"KHM","NAME":"Cambodia","REGION_ID":"40be0146-9389-41f5-a733-0d7d1863da49"},{"GEO_ID":121,"ISO3":"KIR","NAME":"Kiribati","REGION_ID":"2095b81b-bb09-461c-a05b-9255f010c4e8"},{"GEO_ID":122,"ISO3":"KNA","NAME":"Saint Kitts and Nevis","REGION_ID":"4d3a087c-aa07-4c4b-a0c6-8250a567efff"},{"GEO_ID":123,"ISO3":"KOR","NAME":"South Korea","REGION_ID":"90acab3e-5a1e-432e-abf2-e09f42ab3fc1"},{"GEO_ID":124,"ISO3":"KWT","NAME":"Kuwait","REGION_ID":"8ae9cbeb-0c40-4f20-b95f-059f785bddf0"},{"GEO_ID":125,"ISO3":"LAO","NAME":"Laos","REGION_ID":"905bbee6-1713-4a73-8192-93b3bf58a611"},{"GEO_ID":126,"ISO3":"LBN","NAME":"Lebanon","REGION_ID":"cc32db84-5638-49d5-9acc-804db1fce973"},{"GEO_ID":127,"ISO3":"LBR","NAME":"Liberia","REGION_ID":"50e1557e-fc47-481a-b090-66d5cba5be70"},{"GEO_ID":128,"ISO3":"LBY","NAME":"Libya","REGION_ID":"cbb78d4b-7d5b-4869-abf8-93a2079143ef"},{"GEO_ID":129,"ISO3":"LCA","NAME":"Saint Lucia","REGION_ID":"bd644c87-31dd-422f-9462-4669fb83f652"},{"GEO_ID":130,"ISO3":"LIE","NAME":"Liechtenstein","REGION_ID":"3429fc32-b221-4a24-b35a-98a57098eeb4"},{"GEO_ID":131,"ISO3":"LKA","NAME":"Sri Lanka","REGION_ID":"57921c75-d2bd-4b57-9618-f5b9061954e5"},{"GEO_ID":132,"ISO3":"LSO","NAME":"Lesotho","REGION_ID":"42a0f001-470f-4e3f-9eed-fe27b2b716fe"},{"GEO_ID":133,"ISO3":"LTU","NAME":"Lithuania","REGION_ID":"922b96b0-9038-4a9b-93dd-a56e8dc5098e"},{"GEO_ID":134,"ISO3":"LUX","NAME":"Luxembourg","REGION_ID":"a258ae26-83da-4acd-a762-d5e44ea0fa66"},{"GEO_ID":135,"ISO3":"LVA","NAME":"Latvia","REGION_ID":"c883de73-23f4-4a52-b42c-b60a8da5eaa1"},{"GEO_ID":136,"ISO3":"MAC","NAME":"Macao","REGION_ID":"be21ece8-003f-4e65-8137-7a94ef9d5bc3"},{"GEO_ID":137,"ISO3":"MAF","NAME":"Saint-Martin","REGION_ID":"8a9a037f-b077-4751-bb10-218f89e60d87"},{"GEO_ID":138,"ISO3":"MAR","NAME":"Morocco","REGION_ID":"96ece8fa-cdf8-41cb-8b7a-473559652f93"},{"GEO_ID":139,"ISO3":"MCO","NAME":"Monaco","REGION_ID":"ed4a7687-4f95-4cfe-86f4-c2a388e9246d"},{"GEO_ID":140,"ISO3":"MDA","NAME":"Moldova","REGION_ID":"a3b7cdc4-8707-428b-b303-2e0767462329"},{"GEO_ID":141,"ISO3":"MDG","NAME":"Madagascar","REGION_ID":"72b6baa0-823b-4116-95aa-d61674de995b"},{"GEO_ID":142,"ISO3":"MDV","NAME":"Maldives","REGION_ID":"b5686747-c24e-4bae-8e2a-de02f2249019"},{"GEO_ID":143,"ISO3":"MEX","NAME":"Mexico","REGION_ID":"7d8e3429-a5b5-42ce-968d-d86106254ff8"},{"GEO_ID":144,"ISO3":"MHL","NAME":"Marshall Islands","REGION_ID":"3a7318fb-94cd-4885-a76a-eebd90ee48ee"},{"GEO_ID":145,"ISO3":"MKD","NAME":"Macedonia","REGION_ID":"6f2a53e6-eb0e-4a6d-8998-d48b45b11e4b"},{"GEO_ID":146,"ISO3":"MLI","NAME":"Mali","REGION_ID":"60fa4f95-7415-47b7-abb3-f86ec028f5f3"},{"GEO_ID":147,"ISO3":"MLT","NAME":"Malta","REGION_ID":"9b6c3861-711a-43a8-8152-205d3cbf7844"},{"GEO_ID":148,"ISO3":"MMR","NAME":"Myanmar","REGION_ID":"d636b801-89a2-4eaf-9d58-4ece3a4d0594"},{"GEO_ID":149,"ISO3":"MNE","NAME":"Montenegro","REGION_ID":"127e5960-7a62-4b2c-ba59-4743da1bc9fe"},{"GEO_ID":150,"ISO3":"MNG","NAME":"Mongolia","REGION_ID":"b5a7cc4f-deda-400c-b48f-e4a24a7604be"},{"GEO_ID":151,"ISO3":"MNP","NAME":"Northern Mariana Islands","REGION_ID":"7cc11db2-dfc5-4bc1-b55a-1959710f3a4b"},{"GEO_ID":152,"ISO3":"MOZ","NAME":"Mozambique","REGION_ID":"9071dde7-4ede-4f50-a649-d5219d9224d2"},{"GEO_ID":153,"ISO3":"MRT","NAME":"Mauritania","REGION_ID":"ea3ae466-cfd7-4e63-9262-bd651c5bab90"},{"GEO_ID":154,"ISO3":"MSR","NAME":"Montserrat","REGION_ID":"ab7acba8-13e7-43a5-9452-0d249327b691"},{"GEO_ID":155,"ISO3":"MTQ","NAME":"Martinique","REGION_ID":"d5867dfa-020e-42ac-b3ca-abac5b4660a3"},{"GEO_ID":156,"ISO3":"MUS","NAME":"Mauritius","REGION_ID":"df8090e3-7f1d-421a-b28b-c135cf848d01"},{"GEO_ID":157,"ISO3":"MWI","NAME":"Malawi","REGION_ID":"f7e0587a-6546-4635-b1b4-cf93d7d60190"},{"GEO_ID":158,"ISO3":"MYS","NAME":"Malaysia","REGION_ID":"e7d859d2-73fc-4a13-b226-fae746c2b885"},{"GEO_ID":159,"ISO3":"MYT","NAME":"Mayotte","REGION_ID":"aa86460a-3d33-4f76-8ccb-b91e22462314"},{"GEO_ID":160,"ISO3":"NAM","NAME":"Namibia","REGION_ID":"f1c5b971-7d97-4135-bf6e-d7e1d03c938e"},{"GEO_ID":161,"ISO3":"NCL","NAME":"New Caledonia","REGION_ID":"072810da-5139-4b0d-a0a3-5243220ad318"},{"GEO_ID":162,"ISO3":"NER","NAME":"Niger","REGION_ID":"26091048-c0c4-4fd6-b8dc-8de60184c3e8"},{"GEO_ID":163,"ISO3":"NFK","NAME":"Norfolk Island","REGION_ID":"c3df8035-0fd3-44a0-8340-ad64c4407b2f"},{"GEO_ID":164,"ISO3":"NGA","NAME":"Nigeria","REGION_ID":"e3a8b6ab-f383-4694-9aa7-0c13877ce4c1"},{"GEO_ID":165,"ISO3":"NIC","NAME":"Nicaragua","REGION_ID":"7c8ac8c1-cd3b-4bcf-8bfc-42b8335ce0e4"},{"GEO_ID":166,"ISO3":"NIU","NAME":"Niue","REGION_ID":"9d3b8c5f-a295-4608-8368-71c27c772a00"},{"GEO_ID":167,"ISO3":"NLD","NAME":"Netherlands","REGION_ID":"121e9800-122d-421e-9005-59da97e0256c"},{"GEO_ID":168,"ISO3":"NOR","NAME":"Norway","REGION_ID":"b49746f5-9d85-46e9-95d8-b4757fe67f54"},{"GEO_ID":169,"ISO3":"NPL","NAME":"Nepal","REGION_ID":"b3a102f6-49b9-4976-83d9-6e2d60fa33ac"},{"GEO_ID":170,"ISO3":"NRU","NAME":"Nauru","REGION_ID":"b3d973cb-55e6-4229-a352-77d18a4d8f98"},{"GEO_ID":171,"ISO3":"NZL","NAME":"New Zealand","REGION_ID":"5379e7a6-063d-41bf-acc2-9f6c25e98caa"},{"GEO_ID":172,"ISO3":"OMN","NAME":"Oman","REGION_ID":"15781f4d-228a-4fcc-be8b-22c693c6c44c"},{"GEO_ID":173,"ISO3":"PAK","NAME":"Pakistan","REGION_ID":"e7161a9b-e35b-45da-b043-3e9c7256a56e"},{"GEO_ID":174,"ISO3":"PAN","NAME":"Panama","REGION_ID":"2869bb42-3828-4cd5-bea4-3bbf97181f4f"},{"GEO_ID":175,"ISO3":"PCN","NAME":"Pitcairn Islands","REGION_ID":"fe1174ce-0a54-4a58-92bd-1db5ed6ee531"},{"GEO_ID":176,"ISO3":"PER","NAME":"Peru","REGION_ID":"d782caea-8e79-40f3-b938-47e3006b0766"},{"GEO_ID":177,"ISO3":"PHL","NAME":"Philippines","REGION_ID":"4f8d1572-0139-412f-8f5f-3561a955cbf1"},{"GEO_ID":178,"ISO3":"PLW","NAME":"Palau","REGION_ID":"e121d7ad-1db2-4851-9a80-aac58612d4b1"},{"GEO_ID":179,"ISO3":"PNG","NAME":"Papua New Guinea","REGION_ID":"48a653ba-9433-4a15-80e6-c356b5d28f42"},{"GEO_ID":180,"ISO3":"POL","NAME":"Poland","REGION_ID":"99e309bf-2876-471e-9510-99cd543291f2"},{"GEO_ID":181,"ISO3":"PRI","NAME":"Puerto Rico","REGION_ID":"22b8bcc2-709b-4684-9935-8c1bc15e376b"},{"GEO_ID":182,"ISO3":"PRK","NAME":"North Korea","REGION_ID":"2ab0886c-8b01-498b-a988-76bc01e99a7e"},{"GEO_ID":183,"ISO3":"PRT","NAME":"Portugal","REGION_ID":"1df34786-540b-46e1-93d1-d7d60eeb54f1"},{"GEO_ID":184,"ISO3":"PRY","NAME":"Paraguay","REGION_ID":"30bd42c0-b3ce-409d-a3fd-87cd1599dc9c"},{"GEO_ID":185,"ISO3":"PSE","NAME":"Palestina","REGION_ID":"db3d3322-bb51-4537-8343-a019bd2915e2"},{"GEO_ID":186,"ISO3":"PYF","NAME":"French Polynesia","REGION_ID":"0d1945f2-a8e6-4cad-a569-2060ddf84405"},{"GEO_ID":187,"ISO3":"QAT","NAME":"Qatar","REGION_ID":"5913271a-8182-4254-9a71-f1f49bddfaa5"},{"GEO_ID":188,"ISO3":"REU","NAME":"Reunion","REGION_ID":"16f4ccaa-f541-4093-b805-97be3d040d13"},{"GEO_ID":189,"ISO3":"ROU","NAME":"Romania","REGION_ID":"954adfc5-3a5d-4cb9-bac8-d8a9bd480cf2"},{"GEO_ID":190,"ISO3":"RUS","NAME":"Russia","REGION_ID":"a091317c-6c52-4c48-a1e3-d5b5bb59bbd7"},{"GEO_ID":191,"ISO3":"RWA","NAME":"Rwanda","REGION_ID":"5bb91b6f-47c2-4d1e-9600-fd6717ca2cb3"},{"GEO_ID":192,"ISO3":"SAU","NAME":"Saudi Arabia","REGION_ID":"16c0b06a-ca43-471e-9b89-b4abf3db8227"},{"GEO_ID":193,"ISO3":"SDN","NAME":"Sudan","REGION_ID":"51c6bdb0-3ae5-46fc-a151-3578816c8771"},{"GEO_ID":194,"ISO3":"SEN","NAME":"Senegal","REGION_ID":"ae5557b5-5936-420b-845b-88d61b3ef489"},{"GEO_ID":195,"ISO3":"SGP","NAME":"Singapore","REGION_ID":"a5ed5449-35a1-47e2-bf5c-ec3fc63e108c"},{"GEO_ID":196,"ISO3":"SGS","NAME":"South Georgia and the South Sandwich Islands","REGION_ID":"96663a45-e3a9-40fa-80ff-81f95e78d206"},{"GEO_ID":197,"ISO3":"SHN","NAME":"Saint Helena","REGION_ID":"e576a169-053a-46e4-a0a1-487754cf8014"},{"GEO_ID":198,"ISO3":"SJM","NAME":"Svalbard and Jan Mayen","REGION_ID":"b67390a1-0120-4930-8eac-a45edcaf1507"},{"GEO_ID":199,"ISO3":"SLB","NAME":"Solomon Islands","REGION_ID":"0bb5dc7e-ad4a-44c9-b2b2-03c4a5264948"},{"GEO_ID":200,"ISO3":"SLE","NAME":"Sierra Leone","REGION_ID":"c506eda9-5d3e-43f2-b5d6-34f7f152952f"},{"GEO_ID":201,"ISO3":"SLV","NAME":"El Salvador","REGION_ID":"66778a3f-a00b-4d2e-b97c-7ee9be22e216"},{"GEO_ID":202,"ISO3":"SMR","NAME":"San Marino","REGION_ID":"dc4c5e0b-6900-42b0-9738-a4072d3bd112"},{"GEO_ID":203,"ISO3":"SOM","NAME":"Somalia","REGION_ID":"98d8ead3-0f43-41e5-891c-78b049b7d0fc"},{"GEO_ID":204,"ISO3":"SPM","NAME":"Saint Pierre and Miquelon","REGION_ID":"cb5c0600-60f2-4997-b2da-91701f0f2228"},{"GEO_ID":205,"ISO3":"SRB","NAME":"Serbia","REGION_ID":"c4196d60-c7eb-4616-8838-21dde119e388"},{"GEO_ID":206,"ISO3":"SSD","NAME":"South Sudan","REGION_ID":"3a70ba24-1544-4e46-bb43-62017ef1ae08"},{"GEO_ID":207,"ISO3":"STP","NAME":"São Tomé and Príncipe","REGION_ID":"65adc315-dfb5-45f2-8a44-a480fa6137f7"},{"GEO_ID":208,"ISO3":"SUR","NAME":"Suriname","REGION_ID":"c647b2f4-25fc-47ee-be74-58f76a3080b1"},{"GEO_ID":209,"ISO3":"SVK","NAME":"Slovakia","REGION_ID":"44885edd-14e0-414f-99e5-40a1383079ad"},{"GEO_ID":210,"ISO3":"SVN","NAME":"Slovenia","REGION_ID":"bdb4acba-2c0c-4f26-bfd9-8c9dde9f2c81"},{"GEO_ID":211,"ISO3":"SWE","NAME":"Sweden","REGION_ID":"1968dda2-b750-445d-9bb3-7aab12906c84"},{"GEO_ID":212,"ISO3":"SWZ","NAME":"Swaziland","REGION_ID":"618e02db-743e-4b1b-81fa-85474d5e2ccd"},{"GEO_ID":213,"ISO3":"SXM","NAME":"Sint Maarten","REGION_ID":"5a574ec1-9797-4876-b0db-769a3871ad74"},{"GEO_ID":214,"ISO3":"SYC","NAME":"Seychelles","REGION_ID":"02adb491-b63a-47f3-8b3a-f8d2ee05f28a"},{"GEO_ID":215,"ISO3":"SYR","NAME":"Syria","REGION_ID":"73c61f2f-b0b4-4a05-8dbc-e1b4ade722ff"},{"GEO_ID":216,"ISO3":"TCA","NAME":"Turks and Caicos Islands","REGION_ID":"9a97a370-4bc5-4f7e-a0f3-e9f5fa2696ce"},{"GEO_ID":217,"ISO3":"TCD","NAME":"Chad","REGION_ID":"fe58733a-57fd-4b15-b0bd-99c3b4d0ec0b"},{"GEO_ID":218,"ISO3":"TGO","NAME":"Togo","REGION_ID":"d4b3d957-e92c-4bf4-bd41-6dac22c529c5"},{"GEO_ID":219,"ISO3":"THA","NAME":"Thailand","REGION_ID":"8d16c6bd-851a-45cd-8e4a-05147dbd2c08"},{"GEO_ID":220,"ISO3":"TJK","NAME":"Tajikistan","REGION_ID":"e027565b-3195-409b-b08f-d0db45438bf0"},{"GEO_ID":221,"ISO3":"TKL","NAME":"Tokelau","REGION_ID":"4c67906c-4156-4a35-8916-80495b0af7fd"},{"GEO_ID":222,"ISO3":"TKM","NAME":"Turkmenistan","REGION_ID":"3c1135ab-07bc-4d63-9a34-951c7091ccf6"},{"GEO_ID":223,"ISO3":"TLS","NAME":"Timor-Leste","REGION_ID":"442096e1-3ca8-4ab0-b2f8-8341c31ede27"},{"GEO_ID":224,"ISO3":"TON","NAME":"Tonga","REGION_ID":"654784dc-403f-4959-a05c-40c0ed0ec6f8"},{"GEO_ID":225,"ISO3":"TTO","NAME":"Trinidad and Tobago","REGION_ID":"69470e73-9179-40ac-a9b9-568fc0a7c327"},{"GEO_ID":226,"ISO3":"TUN","NAME":"Tunisia","REGION_ID":"24a497ce-fd07-4be2-b8c7-9eb5609c2fb6"},{"GEO_ID":227,"ISO3":"TUR","NAME":"Turkey","REGION_ID":"a95e0b9a-d8ba-4b8e-af33-3362f5b5e749"},{"GEO_ID":228,"ISO3":"TUV","NAME":"Tuvalu","REGION_ID":"26565c93-f604-4519-ba5b-e8addc96dcaa"},{"GEO_ID":229,"ISO3":"TWN","NAME":"Taiwan","REGION_ID":"0d81db60-be14-4380-b5b7-575f0aeeb892"},{"GEO_ID":230,"ISO3":"TZA","NAME":"Tanzania","REGION_ID":"c9725d3a-f95d-4c06-8c0a-2e451376f5ba"},{"GEO_ID":231,"ISO3":"UGA","NAME":"Uganda","REGION_ID":"4f78f663-3c54-4bf8-aea2-883efff761bb"},{"GEO_ID":232,"ISO3":"UKR","NAME":"Ukraine","REGION_ID":"ad54efce-e9c5-4e62-8535-189c1b9efc8a"},{"GEO_ID":233,"ISO3":"UMI","NAME":"United States Minor Outlying Islands","REGION_ID":"56001e8a-32f4-4ede-88f0-2123150a1903"},{"GEO_ID":234,"ISO3":"URY","NAME":"Uruguay","REGION_ID":"41a9e7e5-fa4e-4284-84fd-06452e0e08b4"},{"GEO_ID":235,"ISO3":"USA","NAME":"United States","REGION_ID":"0ac8d80c-ec74-490d-8e7d-5742400b035e"},{"GEO_ID":236,"ISO3":"UZB","NAME":"Uzbekistan","REGION_ID":"6df440da-5f21-4836-9844-d5fc34aa9b02"},{"GEO_ID":237,"ISO3":"VAT","NAME":"Vatican City","REGION_ID":"6a657615-04e7-4dcc-9cb4-1a139090cd7a"},{"GEO_ID":238,"ISO3":"VCT","NAME":"Saint Vincent and the Grenadines","REGION_ID":"9f2aff88-7b07-458d-a492-b8200e0c2097"},{"GEO_ID":239,"ISO3":"VEN","NAME":"Venezuela","REGION_ID":"3ae8db96-5654-4a7b-b21c-10df34e56812"},{"GEO_ID":240,"ISO3":"VGB","NAME":"British Virgin Islands","REGION_ID":"f94c07f3-3314-4328-9696-9aee46e482fe"},{"GEO_ID":241,"ISO3":"VIR","NAME":"Virgin Islands, U.S.","REGION_ID":"a5d68447-5daf-4966-8ade-0932f0bf17af"},{"GEO_ID":242,"ISO3":"VNM","NAME":"Vietnam","REGION_ID":"9142f2db-04de-4d79-87a2-09853627e851"},{"GEO_ID":243,"ISO3":"VUT","NAME":"Vanuatu","REGION_ID":"6cb772fa-28f8-43eb-9a2f-dcc3197b2ffa"},{"GEO_ID":244,"ISO3":"WLF","NAME":"Wallis and Futuna","REGION_ID":"6098e39f-c718-4fa1-91bb-77cf725c0e24"},{"GEO_ID":245,"ISO3":"WSM","NAME":"Samoa","REGION_ID":"2f5529ec-b481-437a-8d9f-aeb53b4f74ea"},{"GEO_ID":246,"ISO3":"XAD","NAME":"Akrotiri and Dhekelia","REGION_ID":"14030021-4de5-48bc-894c-409ec3c1e32d"},{"GEO_ID":247,"ISO3":"XCA","NAME":"Caspian Sea","REGION_ID":"be0dde06-0299-4936-bfdb-3961d200eb91"},{"GEO_ID":248,"ISO3":"XCL","NAME":"Clipperton Island","REGION_ID":"3f2070c4-045f-4017-a804-4688d8005464"},{"GEO_ID":249,"ISO3":"XKO","NAME":"Kosovo","REGION_ID":"7aadbf26-e559-4f19-9f55-8a78a7eb15cf"},{"GEO_ID":250,"ISO3":"XNC","NAME":"Northern Cyprus","REGION_ID":"b09ec9d4-c1fd-4779-b550-0aa3780430b7"},{"GEO_ID":251,"ISO3":"XPI","NAME":"Paracel Islands","REGION_ID":"66d94ead-934f-460a-8832-4b57b8f7ec1c"},{"GEO_ID":252,"ISO3":"XSP","NAME":"Spratly Islands","REGION_ID":"1ed51369-3119-4834-8f18-4eb6f0dc86fb"},{"GEO_ID":253,"ISO3":"YEM","NAME":"Yemen","REGION_ID":"9023dbfd-918b-4c07-a8e3-bd98c2bf8546"},{"GEO_ID":254,"ISO3":"ZAF","NAME":"South Africa","REGION_ID":"0c36c8f0-53f7-46d8-993c-6adb4bcee5e7"},{"GEO_ID":255,"ISO3":"ZMB","NAME":"Zambia","REGION_ID":"deddc8f1-7ff6-4c53-a4cb-de3c4bcfccc1"},{"GEO_ID":256,"ISO3":"ZWE","NAME":"Zimbabwe","REGION_ID":"2dfa435e-7455-4381-9ac5-c11739ac3053"}] diff --git a/src/containers/sidebars/dashboard-sidebar/regions-analysis/index.js b/src/containers/sidebars/dashboard-sidebar/regions-analysis/index.js new file mode 100644 index 000000000..1b2acf3e3 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/regions-analysis/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import RegionsAnalysisComponent from './regions-analysis-component'; + +function RegionsAnalysisContainer(props) { + return ; +} + +export default RegionsAnalysisContainer; diff --git a/src/containers/sidebars/dashboard-sidebar/regions-analysis/regions-analysis-component.jsx b/src/containers/sidebars/dashboard-sidebar/regions-analysis/regions-analysis-component.jsx new file mode 100644 index 000000000..89218ec15 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/regions-analysis/regions-analysis-component.jsx @@ -0,0 +1,191 @@ +import React, { useContext, useEffect } from 'react'; + +import { DASHBOARD } from 'router'; + +import { useT } from '@transifex/react'; + +import { + PROVINCE_FEATURE_GLOBAL_OUTLINE_ID, + DRC_REGION_FEATURE_ID, +} from 'utils/dashboard-utils'; + +import FormControlLabel from '@mui/material/FormControlLabel'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; + +import EsriFeatureService from 'services/esri-feature-service'; + +import { + LAYER_OPTIONS, + NAVIGATION, + REGION_OPTIONS, +} from 'constants/dashboard-constants.js'; + +import hrTheme from 'styles/themes/hr-theme.module.scss'; + +import styles from './regions-analysis-styles.module.scss'; + +function RegionsAnalysisComponent(props) { + const t = useT(); + const { + map, + regionLayers, + browsePage, + setRegionLayers, + setSelectedIndex, + selectedRegion, + setSelectedRegion, + scientificName, + selectedIndex, + selectedRegionOption, + setSelectedRegionOption, + countryISO, + } = props; + const { lightMode } = useContext(LightModeContext); + + const displayLayer = (option) => { + let featureLayer; + if (option === REGION_OPTIONS.PROTECTED_AREAS) { + featureLayer = EsriFeatureService.addProtectedAreaLayer(null, countryISO); + + setRegionLayers(() => ({ + [LAYER_OPTIONS.PROTECTED_AREAS]: featureLayer, + })); + map.add(featureLayer); + } else if (option === REGION_OPTIONS.PROVINCES) { + featureLayer = EsriFeatureService.getFeatureLayer( + PROVINCE_FEATURE_GLOBAL_OUTLINE_ID, + countryISO + ); + + setRegionLayers(() => ({ + [LAYER_OPTIONS.PROVINCES]: featureLayer, + })); + map.add(featureLayer); + } else if (option === REGION_OPTIONS.FORESTS) { + featureLayer = EsriFeatureService.getFeatureLayer( + DRC_REGION_FEATURE_ID, + null, + LAYER_OPTIONS.FORESTS + ); + + setRegionLayers(() => ({ + [LAYER_OPTIONS.FORESTS]: featureLayer, + })); + map.add(featureLayer); + } + + browsePage({ + type: DASHBOARD, + payload: { iso: countryISO.toLowerCase() }, + query: { + scientificName, + selectedIndex, + regionLayers, + selectedRegionOption: option, + }, + }); + }; + + const removeRegionLayers = () => { + const protectedAreaLayer = map.layers.items.find( + (layer) => layer.id === LAYER_OPTIONS.PROTECTED_AREAS + ); + const provinceLayer = map.layers.items.find( + (layer) => layer.id === LAYER_OPTIONS.PROVINCES + ); + const forestLayer = map.layers.items.find( + (layer) => layer.id === LAYER_OPTIONS.FORESTS + ); + + map.remove(protectedAreaLayer); + map.remove(provinceLayer); + map.remove(forestLayer); + }; + + const optionSelected = (event) => { + setSelectedRegion(null); + removeRegionLayers(); + + const option = event.currentTarget.value; + displayLayer(option); + setSelectedRegionOption(option); + }; + + useEffect(() => { + removeRegionLayers(); + if (selectedRegionOption && selectedRegion) { + setSelectedIndex(NAVIGATION.EXPLORE_SPECIES); + } else { + setSelectedRegionOption(null); + } + }, []); + + return ( +
+ {t('Regions Analysis')} +
+

+ {t( + 'Select a region type below to display on the map and explore species lists for each region.' + )} +

+
+ + } + label={t('Protected Areas')} + /> + {/* } label={t('Proposed Protected Areas')} /> */} + } + label={t('Provinces')} + /> + {countryISO === 'COD' && ( + } + label={t('Forest Titles')} + /> + )} + } + className={styles.disabled} + disabled + label={t('Draw on Map - Coming soon')} + /> + {/* } label={t('Priority Areas')} /> */} + {/* } label={t('Community Forests')} /> */} + +
+ {/*
+ +
*/} +
+ ); +} + +export default RegionsAnalysisComponent; diff --git a/src/containers/sidebars/dashboard-sidebar/regions-analysis/regions-analysis-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/regions-analysis/regions-analysis-styles.module.scss new file mode 100644 index 000000000..32dfbb94f --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/regions-analysis/regions-analysis-styles.module.scss @@ -0,0 +1,71 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + padding: 5px 10px; + width: 100%; + + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --selected-font-color: #{$white}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --selected-font-color: #{$white}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + .sectionTitle{ + font-size: $font-size-tick; + font-weight: 700; + text-transform: uppercase; + line-height: 1.5; + color: var(--font-color); + } + + p{ + font-size: $font-size-xs; + color: var(--font-color); + } + + .choices{ + display: flex; + flex-direction: column; + gap: 5px; + color: var(--font-color); + + span{ + color: var(--font-color); + } + + .disabled{ + span{ + color: var(--font-color); + font-style: italic; + opacity: 0.5; + } + } + } + + .search{ + display: flex; + column-gap: 8px; + margin-top: 8px; + + .saveButton { + color: var(--save-button-text); + background-color: var(--border-color); + &:hover { + background-color: var(--border-color-hover); + } + } + } +} diff --git a/src/containers/sidebars/dashboard-sidebar/selectors.js b/src/containers/sidebars/dashboard-sidebar/selectors.js new file mode 100644 index 000000000..ea1aec3d9 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/selectors.js @@ -0,0 +1,4 @@ +/* eslint-disable max-len */ +import { createStructuredSelector } from 'reselect'; + +export default createStructuredSelector({}); diff --git a/src/containers/sidebars/dashboard-sidebar/species-filter/index.js b/src/containers/sidebars/dashboard-sidebar/species-filter/index.js new file mode 100644 index 000000000..f3c726170 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/species-filter/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import SpeciesFilterComponent from './species-filter-component'; + +function SpeciesFilterContainer(props) { + return ; +} + +export default SpeciesFilterContainer; diff --git a/src/containers/sidebars/dashboard-sidebar/species-filter/species-filter-component.jsx b/src/containers/sidebars/dashboard-sidebar/species-filter/species-filter-component.jsx new file mode 100644 index 000000000..e104765be --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/species-filter/species-filter-component.jsx @@ -0,0 +1,159 @@ +import React, { useState } from 'react'; + +import { useT } from '@transifex/react'; + +import Button from 'components/button'; +import FilterContainer from 'components/filters'; +import SpeciesListContainer from 'components/species-list'; + +import { NAVIGATION } from 'constants/dashboard-constants.js'; + +import styles from '../dashboard-sidebar-styles.module.scss'; + +function SpeciesFilterComponent(props) { + const t = useT(); + const { + selectedRegionOption, + setSelectedRegionOption, + setSelectedIndex, + setSelectedTaxa, + speciesListLoading, + } = props; + + const filterStart = [ + { + name: 'dataset', + title: 'Expected Sources', + filters: [ + { + name: 'Refined Range Map', + active: false, + test: (species) => species.source.indexOf('range') > -1, + count: 0, + type: 'and', + result: false, + }, + ], + }, + { + name: 'dataset', + title: 'Recorded Sources', + filters: [ + { + name: 'Occurrence', + active: false, + test: (species) => species.source.indexOf('GBIF') > -1, + count: 0, + result: false, + type: 'and', + }, + // { + // name: 'Local Inventory', + // active: false, + // test: (species) => + // species.datasetList.map((d) => d.product_type).indexOf('localinv') > + // -1, + // result: false, + // count: 0, + // type: 'and', + // }, + ], + }, + { + name: 'threat', + title: 'IUCN Status', + filters: [ + { + name: 'Critically Endangered', + active: false, + test: (species) => + species.threat_status.toUpperCase() === 'CRITICALLY ENDANGERED', + count: 0, + result: false, + type: 'or', + }, + { + name: 'Endangered', + result: false, + active: false, + test: (species) => + species.threat_status.toUpperCase() === 'ENDANGERED', + count: 0, + type: 'or', + }, + { + name: 'Vulnerable', + active: false, + test: (species) => + species.threat_status.toUpperCase() === 'VULNERABLE', + count: 0, + type: 'or', + result: false, + }, + { + name: 'Least Concern', + active: false, + test: (species) => + species.threat_status.toUpperCase() === 'LEAST CONCERN', + count: 0, + type: 'or', + result: false, + }, + { + name: 'Unknown', + active: false, + result: false, + test: (species) => species.threat_status === undefined, + count: 0, + type: 'or', + }, + ], + }, + ]; + + const [filters, setFilters] = useState(filterStart); + + const handleBack = () => { + setSelectedTaxa(null); + setSelectedRegionOption(null); + setSelectedIndex(NAVIGATION.REGION); + }; + + const updateActiveFilter = (filter) => { + const newFilters = filters.map((filterGroup) => { + const newFilterGroup = { ...filterGroup }; + newFilterGroup.filters = filterGroup.filters.map((f) => { + if (f.name === filter.name) { + f.active = !f.active; + } + return f; + }); + return newFilterGroup; + }); + setFilters(newFilters); + }; + + return ( +
+ {selectedRegionOption && ( +
+ ); +} + +export default SpeciesFilterComponent; diff --git a/src/containers/sidebars/dashboard-sidebar/species-filter/species-filter-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/species-filter/species-filter-styles.module.scss new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/species-filter/species-filter-styles.module.scss @@ -0,0 +1 @@ + diff --git a/src/containers/sidebars/dashboard-sidebar/species-info/index.js b/src/containers/sidebars/dashboard-sidebar/species-info/index.js new file mode 100644 index 000000000..827bf970d --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/species-info/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import SpeciesInfoComponent from './species-info-component'; + +function SpeciesInfoContainer(props) { + return ; +} + +export default SpeciesInfoContainer; diff --git a/src/containers/sidebars/dashboard-sidebar/species-info/species-info-component.jsx b/src/containers/sidebars/dashboard-sidebar/species-info/species-info-component.jsx new file mode 100644 index 000000000..ed3ed5118 --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/species-info/species-info-component.jsx @@ -0,0 +1,54 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; + +import TaxaImageComponent from 'components/taxa-image'; + +import { TAXA_IMAGE_URL } from 'constants/dashboard-constants'; + +import CongoImage from 'images/dashboard/Congolacerta_vauereselli_hendrick_hinkel.jpg'; +import HypImage from 'images/dashboard/Hyperolius_tuberculatus_brian_gratwicke.jpg'; +import LepImage from 'images/dashboard/Leptopelis_christyi_gauvain_saucy.jpeg'; + +import styles from './species-info-styles.module.scss'; + +function SpeciesInfoComponent(props) { + const { speciesInfo } = props; + const [speciesImage, setSpeciesImage] = useState(); + + const { lightMode } = useContext(LightModeContext); + useEffect(() => { + if (speciesInfo) { + const spImage = + speciesInfo?.image.url ?? + `${TAXA_IMAGE_URL}${speciesInfo?.taxa}_icon.svg`; + + if (speciesInfo.scientificname === 'Leptopelis christyi') { + setSpeciesImage(LepImage); + } else if (speciesInfo.scientificname === 'Congolacerta vauereselli') { + setSpeciesImage(CongoImage); + } else if (speciesInfo.scientificname === 'Hyperolius tuberculatus') { + setSpeciesImage(HypImage); + } else { + setSpeciesImage(spImage); + } + } + }, [speciesInfo]); + + return ( +
+
+ species +
+ {speciesInfo?.commonname} + {speciesInfo?.scientificname} + +
+
+

{speciesInfo?.info?.[0].content}

+
+ ); +} + +export default SpeciesInfoComponent; diff --git a/src/containers/sidebars/dashboard-sidebar/species-info/species-info-styles.module.scss b/src/containers/sidebars/dashboard-sidebar/species-info/species-info-styles.module.scss new file mode 100644 index 000000000..dc7d2d69b --- /dev/null +++ b/src/containers/sidebars/dashboard-sidebar/species-info/species-info-styles.module.scss @@ -0,0 +1,65 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.species{ + display: flex; + flex-direction: column; + padding: $paragraph-gutter; + + --font-color: #{$white}; + + &.light{ + --font-color: #{$black}; + } + + .title{ + display: flex; + gap: 10px; + + img{ + width: 130px; + height: 130px; + margin-top: 8px; + } + + .info{ + display: flex; + flex-direction: column; + gap: 3px; + color: var(--font-color); + margin: 0 0 calc($site-gutter / 2) 0; + + .commonName{ + @extend %display1; + font-size: $font-size-rg; + line-height: 1.5; + } + + svg{ + width: 50px; + height: 50px; + fill: var(--font-color); + } + + .taxa{ + font-style: italic; + font-size: $font-size-tick; + line-height: 1.5; + } + + img{ + width: 60px; + height: 60px; + } + } + } + + .description { + @extend %bodyText; + color: var(--font-color); + margin: 0 0 calc($site-gutter / 2) 0; + font-size: $font-size-sm; + } +} diff --git a/src/containers/sidebars/dashboard-trends-sidebar/dashboard-trends-sidebar-component.jsx b/src/containers/sidebars/dashboard-trends-sidebar/dashboard-trends-sidebar-component.jsx new file mode 100644 index 000000000..5fc7a23a9 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/dashboard-trends-sidebar-component.jsx @@ -0,0 +1,147 @@ +import React from 'react'; + +import { useT } from '@transifex/react'; + +import cx from 'classnames'; + +import { REGION_OPTIONS } from 'constants/dashboard-constants.js'; + +import styles from './dashboard-trends-sidebar-styles.module.scss'; +import ShiContainer from './shi'; +import SiiContainer from './sii'; +import SpiContainer from './spi'; + +export const NATIONAL_TREND = 'NATIONAL'; +export const PROVINCE_TREND = 'PROVINCE'; + +const TABS = { + SHI: 1, + SPI: 2, + SII: 3, +}; + +function DashboardTrendsSidebar(props) { + const t = useT(); + const { + shiValue, + siiValue, + spiValue, + tabOption, + setTabOption, + regionLayers, + countryISO, + map, + } = props; + + const showHideLayers = (tabClicked) => { + const layers = regionLayers; + // Object.keys(layers).forEach((region) => { + // const foundLayer = map.layers.items.find( + // (item) => item.id === region || item.id === `${countryISO}-outline` + // ); + // if (foundLayer) { + // map.remove(foundLayer); + if (tabClicked === TABS.SII) { + const foundLayer = map.layers.items.find( + (item) => item.id === REGION_OPTIONS.PROVINCES + ); + if (foundLayer) { + foundLayer.visible = false; + } + + const outlineLayer = map.layers.items.find( + (item) => item.id === `${countryISO}-outline` + ); + if (outlineLayer) { + outlineLayer.visible = false; + } + } else if (tabClicked === TABS.SHI) { + const foundLayer = map.layers.items.find( + (item) => item.id === REGION_OPTIONS.PROVINCES + ); + if (foundLayer) { + foundLayer.visible = false; + } + + const outlineLayer = map.layers.items.find( + (item) => item.id === `${countryISO}-outline` + ); + if (outlineLayer) { + outlineLayer.visible = true; + } + } else { + Object.keys(layers).forEach(() => { + const foundLayer = map.layers.items.find( + (item) => item.id === REGION_OPTIONS.PROVINCES + ); + if (foundLayer) { + foundLayer.visible = true; + } + + const outlineLayer = map.layers.items.find( + (item) => item.id === `${countryISO}-outline` + ); + if (outlineLayer) { + outlineLayer.visible = false; + } + }); + } + + setTabOption(tabClicked); + }; + + return ( +
+
+
+ {/* {t('Conservation Metrics')} + */} +
+
+ + + + +
+
+ {tabOption === 1 && } + {tabOption === 2 && } + {tabOption === 3 && } +
+ ); +} + +export default DashboardTrendsSidebar; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/dashboard-trends-sidebar-styles.module.scss b/src/containers/sidebars/dashboard-trends-sidebar/dashboard-trends-sidebar-styles.module.scss new file mode 100644 index 000000000..7b64ff02a --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/dashboard-trends-sidebar-styles.module.scss @@ -0,0 +1,241 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +$top: 25px; +$trends-width: 1000px; +$info-width: 300px; + +.container{ + width: $trends-width; + + header{ + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 10px; + + .title{ + display: flex; + flex-direction: column; + width: 310px; + color: var(--font-color); + } + + .tabs{ + display: flex; + justify-content: space-evenly; + margin-top: 10px; + padding-left: 26px; + padding-right: 5px; + flex-grow: 1; + border-bottom: solid 2px var(--border-color); + + button{ + display: flex; + flex-direction: column; + align-items: center; + padding: 10px; + + label{ + color: var(--font-color); + text-align: center; + font-family: "Roboto Flex"; + font-size: 22px; + font-style: normal; + font-weight: 600; + line-height: normal; + } + + span{ + color: var(--font-color); + text-align: center; + font-family: "Roboto Condensed"; + font-size: $font-size-xs; + font-style: normal; + font-weight: 500; + line-height: normal; + } + + &.selected{ + background-color: var(--border-color); + color: $white; + border: solid 2px var(--border-color); + border-top-right-radius: 8px; + border-top-left-radius: 8px; + border-bottom: none; + + label, + span{ + color: var(--selected-font-color); + } + } + } + } + } +} + +.trends{ + display: flex; + padding: $paragraph-gutter; + gap: 10px; + + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + --save-button-text: #{$dark-text}; + --border-color-hover: #{$brand-color-main-hover}; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + --save-button-text: #{$white}; + --border-color-hover: #{$mol-blue}; + } + + .info{ + width: $info-width; + + .title{ + @extend %display1; + display: flex; + gap: 10px; + color: var(--font-color); + margin: 0 0 calc($site-gutter / 2) 0; + font-size: $font-size-rg; + line-height: 1.5; + } + + .description { + @extend %bodyText; + color: var(--font-color); + margin: 0 0 calc($site-gutter / 2) 0; + font-size: $font-size-sm; + } + + .spsSpeciesTitle{ + color: var(--font-color); + font-family: $font-family-1; + font-size: 15px; + font-style: normal; + font-weight: 400; + line-height: normal; + } + + .spsSpecies{ + list-style: none; + margin: 0; + padding: 0; + color: var(--font-color); + + li{ + button{ + display: flex; + gap: 12px; + margin: 6px 0; + cursor: pointer; + width: 100%; + + img{ + width: 50px; + height: 50px; + } + + .spsInfo{ + display: flex; + flex-direction: column; + flex-grow: 1; + width: 100%; + text-align: left; + + .name{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-xs; + font-style: normal; + font-weight: 600; + line-height: normal; + } + + .scientificname{ + color: var(--font-color); + font-family: $font-family-1; + font-size: 11px; + font-style: italic; + font-weight: 400; + line-height: normal; + flex-grow: 1; + } + + .addToMap{ + color: var(--font-color); + font-family: "Roboto Mono"; + font-size: $font-size-xxxs; + font-style: normal; + font-weight: 300; + line-height: normal; + text-transform: uppercase; + width: fit-content; + padding: 0; + text-decoration: underline; + } + } + + .spsScore{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-sm; + font-style: normal; + font-weight: 700; + line-height: normal; + width: 100px; + text-align: right; + } + } + } + } + + .options{ + display: flex; + flex-direction: column; + row-gap: 8px; + margin-top: 10px; + + .helpText{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-xxxs; + font-style: italic; + font-weight: 400; + line-height: normal; + } + } + } + + .btnGroup{ + display: flex; + gap: 3px; + padding: 2px; + border: 1px solid var(--border-color); + border-radius: 5px; + + .saveButton{ + border: none; + } + } + + .saveButton { + color: var(--save-button-text); + background-color: var(--border-color); + &:hover { + background-color: var(--border-color-hover); + color: var(--save-button-text); + } + } + + .notActive{ + background-color: transparent; + color: var(--font-color); + border: 1px solid var(--border-color); + } +} diff --git a/src/containers/sidebars/dashboard-trends-sidebar/index.js b/src/containers/sidebars/dashboard-trends-sidebar/index.js new file mode 100644 index 000000000..d6c938f9b --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/index.js @@ -0,0 +1,310 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; + +import { + PROVINCE_FEATURE_GLOBAL_SPI_LAYER_ID, + SHI_LAYER_ID, +} from 'utils/dashboard-utils.js'; + +import last from 'lodash/last'; + +import EsriFeatureService from 'services/esri-feature-service'; + +import { + LAYER_OPTIONS, + REGION_OPTIONS, + SHI_LATEST_YEAR, + SPI_LATEST_YEAR, + SII_LATEST_YEAR, +} from 'constants/dashboard-constants.js'; +import { + COUNTRIES_DATA_SERVICE_URL, + DASHBOARD_URLS, +} from 'constants/layers-urls'; + +import Component, { + NATIONAL_TREND, + PROVINCE_TREND, +} from './dashboard-trends-sidebar-component.jsx'; +import mapStateToProps from './selectors'; + +function DashboardTrendsSidebarContainer(props) { + const { + countryISO, + view, + map, + regionLayers, + setRegionLayers, + geometry, + setSelectedRegionOption, + selectedProvince, + tabOption, + } = props; + + const [geo, setGeo] = useState(null); + const [countryData, setCountryData] = useState([]); + const [provinces, setProvinces] = useState([]); + const [activeTrend, setActiveTrend] = useState(PROVINCE_TREND); + const [shiActiveTrend, setShiActiveTrend] = useState(NATIONAL_TREND); + const [spiScoresData, setSpiScoresData] = useState([]); + const [shiScoresData, setShiScoresData] = useState([]); + const [selectSpiSpeciesData, setSpiSelectSpeciesData] = useState([]); + const [selectShiSpeciesData, setShiSelectSpeciesData] = useState([]); + const [shiProvinceTrendData, setShiProvinceTrendData] = useState([]); + + const [shiValue, setShiValue] = useState(0); + const [spiValue, setSpiValue] = useState(0); + const [siiValue, setSiiValue] = useState(0); + + const removeRegionLayers = () => { + const layers = regionLayers; + Object.keys(layers).forEach((region) => { + const foundLayer = map.layers.items.find((item) => item.id === region); + if (foundLayer) { + map.remove(foundLayer); + } + }); + }; + + const getProvinceData = (provinceURL) => { + EsriFeatureService.getFeatures(provinceURL).then((features) => { + const regions = features.map((f) => f.attributes); + setProvinces(regions); + }); + }; + + const getCountryData = (countryURL) => { + EsriFeatureService.getFeatures(countryURL).then((features) => { + const countries = features.map((f) => f.attributes); + + setSpiValue(last(countries).SPI.toFixed(1)); + + const shiValues = countries.find((item) => item.Year === SHI_LATEST_YEAR); + const siiValues = countries.find((item) => item.Year === SII_LATEST_YEAR); + setShiValue(parseFloat(shiValues.SHI_AvgHabitatScore * 100).toFixed(1)); + setSiiValue((siiValues.SII * 100).toFixed(1)); + setCountryData(countries); + }); + }; + + const getSpiScoreData = (scoresDataURL) => { + EsriFeatureService.getFeatures(scoresDataURL).then((features) => { + const data = features.map((f) => f.attributes); + setSpiScoresData(data); + }); + }; + + const getScoresData = (scoresDataURL) => { + EsriFeatureService.getFeatures(scoresDataURL).then((features) => { + const data = features.map((f) => f.attributes); + setShiScoresData(data); + }); + }; + + const getSpiSelectSpeciesData = (selectSpeciesURL) => { + EsriFeatureService.getFeatures(selectSpeciesURL).then((features) => { + const data = features.map((f) => f.attributes); + setSpiSelectSpeciesData(data); + }); + }; + + const getShiProvinceData = (provinceURL) => { + EsriFeatureService.getFeatures(provinceURL).then((features) => { + const data = features.map((f) => f.attributes); + setShiProvinceTrendData(data); + }); + }; + + const getShiSelectSpeciesData = (scoresDataURL) => { + EsriFeatureService.getFeatures(scoresDataURL).then((features) => { + const data = features.map((f) => f.attributes); + setShiSelectSpeciesData(data); + }); + }; + + // find and zoom to region + useEffect(() => { + if (countryISO === 'COD') { + setShiActiveTrend(PROVINCE_TREND); + } + + EsriFeatureService.getFeatures({ + url: COUNTRIES_DATA_SERVICE_URL, + whereClause: `GID_0 = '${countryISO}'`, + returnGeometry: true, + }).then((features) => { + // eslint-disable-next-line no-shadow + const { geometry } = features[0]; + + if (geometry && view) { + setGeo(geometry); + } + }); + }, [view, countryISO]); + + useEffect(() => { + if (!map && !view) return; + + // if (tabOption === 2) { + const layer = EsriFeatureService.getFeatureLayer( + PROVINCE_FEATURE_GLOBAL_SPI_LAYER_ID, + countryISO + ); + map.add(layer); + + // eslint-disable-next-line no-shadow + setRegionLayers((regionLayers) => ({ + ...regionLayers, + [LAYER_OPTIONS.PROVINCES]: layer, + })); + // } + + if (tabOption === 2) { + layer.visible = true; + } else { + layer.visible = false; + } + + // if (tabOption === 1) { + const outlineFeatureLayer = EsriFeatureService.getFeatureLayer( + SHI_LAYER_ID, + countryISO, + `${countryISO}-outline` + ); + map.add(outlineFeatureLayer); + + // eslint-disable-next-line no-shadow + setRegionLayers((regionLayers) => ({ + ...regionLayers, + [`${countryISO}-outline`]: outlineFeatureLayer, + })); + + if (tabOption === 1) { + outlineFeatureLayer.visible = true; + } else { + outlineFeatureLayer.visible = false; + } + // } + + if (tabOption === 3) { + layer.visible = false; + outlineFeatureLayer.visible = false; + } + + // rezoom to country + if (geometry) { + view.goTo({ + target: geometry, + center: [geometry.longitude - 20, geometry.latitude], + zoom: 5.5, + extent: geometry.clone(), + }); + } + }, [map, view]); + + useEffect(() => { + removeRegionLayers(); + setSelectedRegionOption(REGION_OPTIONS.PROVINCES); + + const countryURL = { + url: DASHBOARD_URLS.COUNTRY_URL, + whereClause: `ISO3 = '${countryISO}'`, + orderByFields: ['year'], + }; + getCountryData(countryURL); + + // GET SPI Province Trend Data + const provinceURL = { + url: DASHBOARD_URLS.SPI_PROVINCE_TREND_URL, + whereClause: `iso3 = '${countryISO}' and Year = ${SPI_LATEST_YEAR}`, + orderByFields: ['region_name'], + }; + getProvinceData(provinceURL); + + // GET SHI Province Trend Data + const shiProvinceURL = { + url: DASHBOARD_URLS.SHI_PROVINCE_TREND_URL, + whereClause: `GID_0 = '${countryISO}'`, + orderByFields: ['region_name'], + }; + getShiProvinceData(shiProvinceURL); + }, []); + + useEffect(() => { + if (!selectedProvince) return; + + let whereClause = `ISO3_regional = '${ + selectedProvince.iso3_regional ?? selectedProvince.GID_1 + }'`; + let shiWhereClause = `iso3 = '${countryISO}' and iso3_regional = '${ + selectedProvince.iso3_regional ?? selectedProvince.GID_1 + }'`; + if (activeTrend === NATIONAL_TREND || shiActiveTrend === NATIONAL_TREND) { + whereClause = `ISO3 = '${countryISO}' and ISO3_regional = 'XXX'`; + shiWhereClause = `iso3 = '${countryISO}'`; + } + + // GET SPI + const scoreDataURL = { + url: DASHBOARD_URLS.SPI_HISTOGRAM_URL, + whereClause: `${whereClause}`, + }; + getSpiScoreData(scoreDataURL); + + const selectSpeciesURL = { + url: DASHBOARD_URLS.SPI_REGION_SPECIES_URL, + whereClause: `${whereClause} and species_protection_score_all >= 0 and species_protection_score_all <= 5 and species_url IS NOT NULL`, + }; + getSpiSelectSpeciesData(selectSpeciesURL); + + if (shiActiveTrend === NATIONAL_TREND) { + const shiScoresDataURL = { + url: DASHBOARD_URLS.SHI_HISTOGRAM_URL, + whereClause: `${shiWhereClause}`, + }; + getScoresData(shiScoresDataURL); + + const shiSpeciesScoresURL = { + url: DASHBOARD_URLS.SHI_SPECIES_URL, + whereClause: `${shiWhereClause} and HabitatScore >= 1 and HabitatScore <= 5 and SpeciesImage IS NOT NULL`, + }; + getShiSelectSpeciesData(shiSpeciesScoresURL); + } else { + const shiScoresDataURL = { + url: DASHBOARD_URLS.SHI_PROVINCE_HISTOGRAM_URL, + whereClause: `${shiWhereClause}`, + }; + getScoresData(shiScoresDataURL); + + const shiSpeciesScoresURL = { + url: DASHBOARD_URLS.SHI_PROVINCE_SPECIES_URL, + whereClause: `${whereClause} and habitat_score >= 1 and habitat_score <= 5 and species_url IS NOT NULL`, + }; + getShiSelectSpeciesData(shiSpeciesScoresURL); + } + }, [selectedProvince, activeTrend, shiActiveTrend]); + + return ( + + ); +} + +export default connect(mapStateToProps, null)(DashboardTrendsSidebarContainer); diff --git a/src/containers/sidebars/dashboard-trends-sidebar/selectors.js b/src/containers/sidebars/dashboard-trends-sidebar/selectors.js new file mode 100644 index 000000000..ea1aec3d9 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/selectors.js @@ -0,0 +1,4 @@ +/* eslint-disable max-len */ +import { createStructuredSelector } from 'reselect'; + +export default createStructuredSelector({}); diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/index.js b/src/containers/sidebars/dashboard-trends-sidebar/shi/index.js new file mode 100644 index 000000000..4f7d564c3 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import ShiComponent from './shi-component'; + +function ShiContainer(props) { + return ; +} + +export default ShiContainer; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/distributions-table-component.jsx b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/distributions-table-component.jsx new file mode 100644 index 000000000..94dbde9b4 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/distributions-table-component.jsx @@ -0,0 +1,195 @@ +import React, { useEffect } from 'react'; + +import { useT, useLocale } from '@transifex/react'; + +import tableStyles from 'components/protected-areas-table/protected-areas-table-styles.module.scss'; + +import ArrowDown from 'icons/arrow_down.svg?react'; +import ArrowUp from 'icons/arrow_up.svg?react'; + +import styles from './distributions-table-styles.module.scss'; + +function DistributionsTableComponent(props) { + const { chartData } = props; + const t = useT(); + const locale = useLocale(); + + return ( +
+
+ + + + + + + + + + + + + + {chartData && + chartData.map((row) => + // eslint-disable-next-line react/no-array-index-key + row.taxa_scores.map((species, idx) => ( + + + + + + + + + + )) + )} + +
+
+ {t('Taxa')} +
+ + handleSortChange({ value: 'NAME', ascending: true }) + } + /> + + handleSortChange({ value: 'NAME', ascending: false }) + } + /> +
+
+
+
+ {t('Scientific Name')} +
+ + handleSortChange({ value: 'GOV_TYPE', ascending: true }) + } + /> + + handleSortChange({ + value: 'GOV_TYPE', + ascending: false, + }) + } + /> +
+
+
+
+ {t('Stewardship')} +
+ + handleSortChange({ value: 'DESIG', ascending: true }) + } + /> + + handleSortChange({ value: 'DESIG', ascending: false }) + } + /> +
+
+
+
+ {t('Range Size')} +
+ + handleSortChange({ + value: 'DESIG_TYPE', + ascending: true, + }) + } + /> + + handleSortChange({ + value: 'DESIG_TYPE', + ascending: false, + }) + } + /> +
+
+
+
+ {t('Area Score')} +
+ + handleSortChange({ value: 'STATUS', ascending: true }) + } + /> + + handleSortChange({ value: 'STATUS', ascending: false }) + } + /> +
+
+
+
+ {t('Connectivity Score')} +
+ + handleSortChange({ + value: 'STATUS_YR', + ascending: true, + }) + } + /> + + handleSortChange({ + value: 'STATUS_YR', + ascending: false, + }) + } + /> +
+
+
+
+ {t('Habitat Score')} +
+ + handleSortChange({ value: 'AREA_KM', ascending: true }) + } + /> + + handleSortChange({ value: 'AREA_KM', ascending: false }) + } + /> +
+
+
+ {row.speciesgroup} + {species.scientificname}{species.steward_score.toFixed(1)} + {( + species.area_score + + species.connectivity_score / 2 + ).toFixed(1)} + 2 + {species.area_score.toFixed(1)}{species.connectivity_score.toFixed(1)} + {( + (species.area_score + species.connectivity_score) / + 2 + ).toFixed(1)} +
+
+
+ ); +} + +export default DistributionsTableComponent; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/distributions-table-styles.module.scss b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/distributions-table-styles.module.scss new file mode 100644 index 000000000..ad46ac5ec --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/distributions-table-styles.module.scss @@ -0,0 +1,31 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + padding: 10px 5px 10px 26px; + flex-grow: 1; + align-items: center; + gap: 8px; + width: 100%; + + .tableWrapper{ + background-color: rgba(255, 255, 255, 0.3); + height: 300px; + overflow-y: auto; + + table{ + font-size: $font-size-xxs; + + th{ + span{ + color: $white; + text-align: center; + } + } + } + } +} diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/index.js b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/index.js new file mode 100644 index 000000000..546156bff --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/distributions-table/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import DistributionsTableComponent from './distributions-table-component'; + +function DistributionsTableContainer(props) { + return ; +} + +export default DistributionsTableContainer; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/score-distributions-shi-component.jsx b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/score-distributions-shi-component.jsx new file mode 100644 index 000000000..8bd7c0b99 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/score-distributions-shi-component.jsx @@ -0,0 +1,437 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { T, useT } from '@transifex/react'; + +import { getCSSVariable } from 'utils/css-utils'; + +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; +import { Loading } from 'he-components'; + +import Button from 'components/button'; +import DistributionsChartComponent from 'components/charts/distribution-chart/distribution-chart-component'; + +import { + NAVIGATION, + SPECIES_SELECTED_COOKIE, +} from 'constants/dashboard-constants.js'; + +import styles from '../../dashboard-trends-sidebar-styles.module.scss'; + +import DistributionsTableContainer from './distributions-table'; +import compStyles from './score-distributions-shi-styles.module.scss'; + +function ScoreDistributionsShiComponent(props) { + const t = useT(); + const { + setScientificName, + setSelectedIndex, + shiScoresData, + selectShiSpeciesData, + shiActiveTrend, + } = props; + + const SCORES = { + HABITAT_SCORE: 'habitat', + AREA_SCORE: 'area', + CONNECTIVITY_SCORE: 'connectivity', + }; + + const NATIONAL_SCORES = { + HABITAT_SCORE: 'habitat', + AREA_SCORE: 'areascore', + CONNECTIVITY_SCORE: 'connectivity', + }; + + const lowAvg = 'Amphibians'; + const highAvg = 'birds'; + + const [chartData, setChartData] = useState(); + const [responseData, setResponseData] = useState(); + const [showTable, setShowTable] = useState(false); + const [activeScore, setActiveScore] = useState(SCORES.HABITAT_SCORE); + const [isLoading, setIsLoading] = useState(true); + const [spsSpecies, setSpsSpecies] = useState(); + const [isSpeciesLoading, setIsSpeciesLoading] = useState(true); + const { lightMode } = useContext(LightModeContext); + const [lowBucket, setLowBucket] = useState(0); + const [highBucket, setHighBucket] = useState(5); + + const options = { + plugins: { + title: { + display: false, + }, + legend: { + display: false, + }, + }, + responsive: true, + interaction: { + mode: 'index', + intersect: false, + }, + scales: { + x: { + type: 'linear', + offset: false, + stacked: true, + display: true, + title: { + display: true, + text: t('Score'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + display: false, + offset: false, + }, + ticks: { + color: getCSSVariable('oslo-gray'), + stepSize: 5, + }, + }, + y: { + stacked: true, + display: true, + title: { + display: true, + text: t('Number of Species'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + stepSize: 10, + }, + }, + }, + // onClick: (event, elements) => { + // if (elements.length > 0) { + // console.log(elements); + // const datasetIndex = elements[0].datasetIndex; + // const dataIndex = elements[0].index; + // const value = chartData.datasets[datasetIndex].data[dataIndex]; + // console.log(value); + + // setLowBucket(dataIndex * bucketSize); + // setHighBucket((dataIndex * bucketSize) + bucketSize) + // } + // } + }; + + const displayData = (score) => { + if (shiActiveTrend === 'NATIONAL') { + if (score === SCORES.HABITAT_SCORE) { + score = NATIONAL_SCORES.HABITAT_SCORE; + } else if (score === SCORES.AREA_SCORE) { + score = NATIONAL_SCORES.AREA_SCORE; + } else if (score === SCORES.CONNECTIVITY_SCORE) { + score = NATIONAL_SCORES.CONNECTIVITY_SCORE; + } + + const taxaSet = { + amphibians: { + [NATIONAL_SCORES.AREA_SCORE]: {}, + [NATIONAL_SCORES.HABITAT_SCORE]: {}, + [NATIONAL_SCORES.CONNECTIVITY_SCORE]: {}, + }, + birds: { + [NATIONAL_SCORES.AREA_SCORE]: {}, + [NATIONAL_SCORES.HABITAT_SCORE]: {}, + [NATIONAL_SCORES.CONNECTIVITY_SCORE]: {}, + }, + mammals: { + [NATIONAL_SCORES.AREA_SCORE]: {}, + [NATIONAL_SCORES.HABITAT_SCORE]: {}, + [NATIONAL_SCORES.CONNECTIVITY_SCORE]: {}, + }, + reptiles: { + [NATIONAL_SCORES.AREA_SCORE]: {}, + [NATIONAL_SCORES.HABITAT_SCORE]: {}, + [NATIONAL_SCORES.CONNECTIVITY_SCORE]: {}, + }, + }; + + // Loop through each number and place it in the appropriate bucket + shiScoresData.forEach((a) => { + const bin = a.binned.split(',')[0].replace(/ /gi, ''); + + taxaSet.amphibians[score][bin] = a[`${score}_amphibians`]; + taxaSet.birds[score][bin] = a[`${score}_birds`]; + taxaSet.mammals[score][bin] = a[`${score}_mammals`]; + taxaSet.reptiles[score][bin] = a[`${score}_reptiles`]; + }); + + const uniqueKeys = new Set([ + ...Object.keys(taxaSet.birds[score]), + ...Object.keys(taxaSet.mammals[score]), + ...Object.keys(taxaSet.reptiles[score]), + ...Object.keys(taxaSet.amphibians[score]), + ]); + + setChartData({ + labels: [...uniqueKeys].map((key) => key), + datasets: [ + { + label: t('Birds'), + data: Object.values(taxaSet.birds[score]), + backgroundColor: getCSSVariable('birds'), + }, + { + label: t('Mammals'), + data: Object.values(taxaSet.mammals[score]), + backgroundColor: getCSSVariable('mammals'), + }, + { + label: t('Reptiles'), + data: Object.values(taxaSet.reptiles[score]), + backgroundColor: getCSSVariable('reptiles'), + }, + { + label: t('Amphibians'), + data: Object.values(taxaSet.amphibians[score]), + backgroundColor: getCSSVariable('amphibians'), + }, + ], + }); + } else { + const taxaSet = { + amphibians: { + [SCORES.AREA_SCORE]: {}, + [SCORES.HABITAT_SCORE]: {}, + [SCORES.CONNECTIVITY_SCORE]: {}, + }, + birds: { + [SCORES.AREA_SCORE]: {}, + [SCORES.HABITAT_SCORE]: {}, + [SCORES.CONNECTIVITY_SCORE]: {}, + }, + mammals: { + [SCORES.AREA_SCORE]: {}, + [SCORES.HABITAT_SCORE]: {}, + [SCORES.CONNECTIVITY_SCORE]: {}, + }, + reptiles: { + [SCORES.AREA_SCORE]: {}, + [SCORES.HABITAT_SCORE]: {}, + [SCORES.CONNECTIVITY_SCORE]: {}, + }, + }; + + // Loop through each number and place it in the appropriate bucket + shiScoresData.forEach((a) => { + const bin = a.binned.split(',')[0].replace(/ /gi, ''); + + taxaSet.amphibians[score][bin] = a[`${score}_amphibians`]; + taxaSet.birds[score][bin] = a[`${score}_birds`]; + taxaSet.mammals[score][bin] = a[`${score}_mammals`]; + taxaSet.reptiles[score][bin] = a[`${score}_reptiles`]; + }); + + const uniqueKeys = new Set([ + ...Object.keys(taxaSet.birds[score]), + ...Object.keys(taxaSet.mammals[score]), + ...Object.keys(taxaSet.reptiles[score]), + ...Object.keys(taxaSet.amphibians[score]), + ]); + + setChartData({ + labels: [...uniqueKeys].map((key) => key), + datasets: [ + { + label: t('Birds'), + data: Object.values(taxaSet.birds[score]), + backgroundColor: getCSSVariable('birds'), + }, + { + label: t('Mammals'), + data: Object.values(taxaSet.mammals[score]), + backgroundColor: getCSSVariable('mammals'), + }, + { + label: t('Reptiles'), + data: Object.values(taxaSet.reptiles[score]), + backgroundColor: getCSSVariable('reptiles'), + }, + { + label: t('Amphibians'), + data: Object.values(taxaSet.amphibians[score]), + backgroundColor: getCSSVariable('amphibians'), + }, + ], + }); + } + setIsLoading(false); + }; + + const handleActiveChange = (score) => { + setActiveScore(score); + displayData(score); + }; + + const loadSpecies = () => { + const species = [ + selectShiSpeciesData[0], + selectShiSpeciesData[1], + selectShiSpeciesData[2], + selectShiSpeciesData[3], + ]; + setSpsSpecies(species); + setIsSpeciesLoading(false); + }; + + const selectSpecies = (scientificname) => { + setSelectedIndex(NAVIGATION.DATA_LAYER); + setScientificName(scientificname); + localStorage.setItem(SPECIES_SELECTED_COOKIE, scientificname); + }; + + const getChartData = async () => { + // setResponseData(selectShiSpeciesData); + // loadSpecies(selectShiSpeciesData); + displayData(SCORES.HABITAT_SCORE); + }; + + useEffect(() => { + if (!shiScoresData.length) return; + + setIsLoading(true); + getChartData(); + }, [shiScoresData]); + + useEffect(() => { + if (!selectShiSpeciesData.length) return; + setIsSpeciesLoading(true); + loadSpecies(); + }, [selectShiSpeciesData]); + + return ( +
+
+ {t('Score Distributions')} + +

+ {lowAvg}} + highAvgBold={{highAvg}} + /> +

+ + + {t('Species with SHS between')}{' '} + + {lowBucket} - {highBucket}: + + +
+ {isSpeciesLoading && } + {!isSpeciesLoading && ( +
    + {spsSpecies.map((s) => { + return ( +
  • + +
  • + ); + })} +
+ )} +
+ {/* {!showTable && ( +
+
+
+ {!showTable && ( + <> + {/* */} +
+
+ {isLoading && } + {!isLoading && ( + + )} + + )} + {showTable && ( + <> + {/* */} + + + )} +
+
+ ); +} + +export default ScoreDistributionsShiComponent; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/score-distributions-shi-styles.module.scss b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/score-distributions-shi-styles.module.scss new file mode 100644 index 000000000..bb6ddb8b7 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/score-distributions/score-distributions-shi-styles.module.scss @@ -0,0 +1,15 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.chartArea{ + display: flex; + flex-direction: column; + align-items: center; + flex-grow: 1; + + .btnGroup{ + width: 75%; + } +} diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/shi-component.jsx b/src/containers/sidebars/dashboard-trends-sidebar/shi/shi-component.jsx new file mode 100644 index 000000000..a98fc6056 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/shi-component.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import ScoreDistributionsShiComponent from './score-distributions/score-distributions-shi-component'; +import TemporalTrendsShiComponent from './temporal-trends/temporal-trends-shi-component'; + +function ShiComponent(props) { + return ( + <> + + + + ); +} + +export default ShiComponent; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/index.js b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/index.js new file mode 100644 index 000000000..06e126933 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import NationalChartComponent from './national-chart-component'; + +function NationalChartContainer(props) { + return ; +} + +export default NationalChartContainer; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/national-chart-component.jsx b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/national-chart-component.jsx new file mode 100644 index 000000000..6cf189eb3 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/national-chart-component.jsx @@ -0,0 +1,234 @@ +/* eslint-disable camelcase */ +import React, { useContext, useEffect, useState } from 'react'; +import { Line } from 'react-chartjs-2'; + +import { useT } from '@transifex/react'; + +import { getCSSVariable } from 'utils/css-utils'; + +import { + Chart as ChartJS, + LinearScale, + PointElement, + LineElement, + Tooltip, + Legend, +} from 'chart.js'; +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; +import { Loading } from 'he-components'; + +import SpiArcChartComponent from 'components/charts/spi-arc-chart/spi-arc-chart-component'; + +import { SHI_LATEST_YEAR } from 'constants/dashboard-constants.js'; + +import SHILegend from '../../../../../../assets/images/dashboard/shi_legend.png'; + +import styles from './national-chart-styles.module.scss'; + +ChartJS.register(LinearScale, LineElement, PointElement, Tooltip, Legend); + +function NationalChartComponent(props) { + const t = useT(); + const { countryData } = props; + const { lightMode } = useContext(LightModeContext); + const [data, setData] = useState(); + const [shiValue, setShiValue] = useState(0); + const [nationalScores, setNationalScores] = useState({ + areaScore: 0, + connecivityScore: 0, + year: SHI_LATEST_YEAR, + }); + const emptyArcColor = lightMode + ? getCSSVariable('dark-opacity') + : getCSSVariable('white-opacity-20'); + const blankData = { + labels: [t('Global SPI'), t('Remaining')], + datasets: [ + { + label: '', + data: [0, 0], + backgroundColor: [getCSSVariable('habitat'), emptyArcColor], + borderColor: [getCSSVariable('habitat'), emptyArcColor], + borderWidth: 1, + }, + ], + }; + const [shiData, setShiData] = useState(blankData); + const [isLoading, setIsLoading] = useState(true); + const shiStartYear = 2001; + + const options = { + plugins: { + title: { + display: false, + }, + legend: { + display: false, + }, + }, + scales: { + x: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Year'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + maxTicksLimit: 8, + }, + }, + y: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Species Habitat Index'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + maxTicksLimit: 10, + }, + }, + }, + }; + + useEffect(() => { + if (!countryData.length) return; + + const filteredData = countryData.filter( + (item) => item.Year >= shiStartYear && item.Year <= SHI_LATEST_YEAR + ); + + setData({ + labels: filteredData.map((item) => item.Year), + datasets: [ + { + label: t('Average Area Score'), + data: filteredData.map((item) => item.SHI_AreaScore), + borderColor: getCSSVariable('area'), + }, + { + label: t('Average Connectivity Score'), + data: filteredData.map((item) => item.SHI_AvgConnectivityScore), + borderColor: getCSSVariable('connectivity'), + }, + { + label: t('Average Habitat Score'), + data: filteredData.map( + (item) => + (parseFloat(item.SHI_AreaScore) + + parseFloat(item.SHI_AvgConnectivityScore)) / + 2 + ), + borderColor: getCSSVariable('habitat'), + }, + ], + }); + + const headerValues = countryData.find( + (item) => item.Year === SHI_LATEST_YEAR + ); + const { + SHI_AreaScore, + SHI_AvgConnectivityScore, + SHI_GlobalRanking, + SHI_AvgHabitatScore, + } = headerValues; + + setNationalScores({ + areaScore: parseFloat(SHI_AreaScore).toFixed(1), + connecivityScore: parseFloat(SHI_AvgConnectivityScore).toFixed(1), + year: SHI_LATEST_YEAR, + globalRanking: SHI_GlobalRanking, + }); + + const shi = { + labels: [t('Global SHI'), t('Remaining')], + datasets: [ + { + label: '', + data: [SHI_AvgHabitatScore * 100, 100 - SHI_AvgHabitatScore * 100], + backgroundColor: [getCSSVariable('habitat'), emptyArcColor], + borderColor: [getCSSVariable('habitat'), emptyArcColor], + borderWidth: 1, + }, + ], + }; + + setShiValue(parseFloat(SHI_AvgHabitatScore * 100)); + setShiData(shi); + setIsLoading(false); + }, [countryData]); + + return ( +
+ {isLoading && } + {!isLoading && ( + <> +
+
+
+ {nationalScores.year} + {t('Year')} +
+
+ {nationalScores.areaScore} + {t('Area Score')} +
+ +
+ {nationalScores.connecivityScore} + {t('Connectivity Score')} +
+
+ {nationalScores.globalRanking} + {t('Global Ranking')} +
+ + + {t('SHI')} +
+
+ {data && ( +
+
+ spi-legend +
+ 0 + 100 +
+
+
+
+ {t('Habitat Score')} +
+ {t('Area Score')} +
+ {t('Connectivity Score')} +
+ +
+ )} + + )} +
+ ); +} + +export default NationalChartComponent; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/national-chart-styles.module.scss b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/national-chart-styles.module.scss new file mode 100644 index 000000000..0aa75e224 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/national-chart/national-chart-styles.module.scss @@ -0,0 +1,163 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + } + + display: flex; + flex-direction: column; + padding: 10px 5px 10px 26px; + flex-grow: 1; + + .info{ + display: flex; + gap: 15px; + + .specs{ + display: flex; + flex-direction: column; + row-gap: 8px; + + span{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-xs; + font-style: italic; + font-weight: 400; + line-height: normal; + + b{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-sm; + font-style: normal; + font-weight: 600; + line-height: normal; + } + } + } + + .arcGrid{ + display: grid; + grid-template-columns: repeat(5, 1fr); + grid-template-rows: 85px 10px; + width: 100%; + justify-items: center; + border-radius: 5px; + height: 120px; + border: 1.5px solid var(--border-color); + align-items: center; + + .values{ + display: flex; + flex-direction: column; + align-items: center; + align-self: flex-end; + + b{ + font-size: 20px; + } + } + + .spi{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .score{ + color: var(--font-color); + text-align: center; + font-family: $font-family-1; + font-size: $font-size-ml; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-top: -35px; + } + } + + b { + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-base; + font-style: normal; + font-weight: 600; + line-height: normal; + } + + span{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-xs; + font-style: italic; + font-weight: 600; + line-height: normal; + text-transform: uppercase; + text-align: center; + } + } + } + .chart{ + width: 100%; + margin-top: 25px; + max-width: 580px; + display: flex; + flex-direction: column; + align-items: center; + + .legend{ + display: flex; + align-items: center; + gap: 1rem; + margin-top: 0.25rem; + color: var(--font-color); + font-weight: 600; + + .legendBox { + width: 30px; + height: 8px; + color: var(--font-color); + + &.habitat { + background-color: $habitat; + } + + &.area { + background-color: $area; + } + + &.connectivity{ + background-color: $connectivity; + } + } + } + + .mapLegend{ + height: 15px; + display: flex; + flex-direction: column; + margin-bottom: 20px; + width: 100%; + + img{ + width: 100%; + height: 15px; + } + + .legendValues{ + color: var(--font-color); + display: flex; + justify-content: space-between; + } + } + } +} diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/province-chart/index.js b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/province-chart/index.js new file mode 100644 index 000000000..4f795d4a8 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/province-chart/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import ProvinceChartComponent from './province-chart-component'; + +function ProvinceChartContainer(props) { + return ; +} + +export default ProvinceChartContainer; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/province-chart/province-chart-component.jsx b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/province-chart/province-chart-component.jsx new file mode 100644 index 000000000..c5fd95fad --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/shi/temporal-trends/province-chart/province-chart-component.jsx @@ -0,0 +1,293 @@ +/* eslint-disable camelcase */ +import React, { useContext, useEffect, useState } from 'react'; +import { Line } from 'react-chartjs-2'; +import Select from 'react-select'; + +import { useT } from '@transifex/react'; + +import { getCSSVariable } from 'utils/css-utils'; + +import { + Chart as ChartJS, + LinearScale, + PointElement, + LineElement, + Tooltip, + Legend, +} from 'chart.js'; +import cx from 'classnames'; +import { LightModeContext } from 'context/light-mode'; +import { Loading } from 'he-components'; +import last from 'lodash/last'; + +import SpiArcChartComponent from 'components/charts/spi-arc-chart/spi-arc-chart-component'; + +import SHILegend from '../../../../../../assets/images/dashboard/shi_legend.png'; + +import styles from './province-chart-styles.module.scss'; + +ChartJS.register(LinearScale, LineElement, PointElement, Tooltip, Legend); + +function ProvinceChartComponent(props) { + const t = useT(); + const { lightMode } = useContext(LightModeContext); + const { + setSelectedProvince, + selectedProvince, + clickedRegion, + provinces, + setClickedRegion, + shiProvinceTrendData, + provinceName, + setProvinceName, + handleRegionSelected, + layerView, + } = props; + + const emptyArcColor = lightMode + ? getCSSVariable('dark-opacity') + : getCSSVariable('white-opacity-20'); + const blankData = { + labels: [t('Global SPI'), t('Remaining')], + datasets: [ + { + label: '', + data: [0, 0], + backgroundColor: [getCSSVariable('habitat'), emptyArcColor], + borderColor: [getCSSVariable('habitat'), emptyArcColor], + borderWidth: 1, + }, + ], + }; + const [shiData, setShiData] = useState(blankData); + + const [isLoading, setIsLoading] = useState(true); + const [foundIndex, setFoundIndex] = useState(0); + const [data, setData] = useState(); + const [filteredProvince, setFilteredProvince] = useState(); + + const getProvinceScores = (province) => { + const { region_name } = province; + + setSelectedProvince(province); + setProvinceName(region_name); + setFoundIndex( + shiProvinceTrendData.findIndex( + (region) => region.region_name === region_name + ) + ); + }; + + const handleProvinceSelected = async (province) => { + const searchQuery = { + returnGeometry: true, + outFields: ['*'], + }; + + const results = await layerView?.queryFeatures(searchQuery); + + const foundRegion = results?.features.filter( + (feat) => + (feat.attributes.NAME_1 ?? feat.attributes.region_nam) === + province.region_name + ); + handleRegionSelected({ graphic: foundRegion?.[0] }); + getProvinceScores(province); + setClickedRegion(null); + }; + + const options = { + plugins: { + title: { + display: false, + }, + legend: { + display: false, + }, + }, + scales: { + x: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Year'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + maxTicksLimit: 8, + }, + }, + y: { + beginAtZero: false, + display: true, + title: { + display: true, + text: t('Species Habitat Index'), + color: lightMode ? getCSSVariable('black') : getCSSVariable('white'), + }, + grid: { + color: getCSSVariable('oslo-gray'), + }, + ticks: { + color: getCSSVariable('oslo-gray'), + maxTicksLimit: 10, + }, + }, + }, + }; + + const getChartData = () => { + const filteredData = shiProvinceTrendData.filter( + (prov) => prov.region_name === selectedProvince.region_name + ); + + const currentValue = last(filteredData); + setFilteredProvince(currentValue); + + const shi = { + labels: [t('Global SHI'), t('Remaining')], + datasets: [ + { + label: '', + data: [ + currentValue.shi_clipped_scaled, + 100 - currentValue.shi_clipped_scaled, + ], + backgroundColor: [getCSSVariable('habitat'), emptyArcColor], + borderColor: [getCSSVariable('habitat'), emptyArcColor], + borderWidth: 1, + }, + ], + }; + setShiData(shi); + + setData({ + labels: filteredData.map((item) => item.year), + datasets: [ + { + label: t('Average Area Score'), + data: filteredData.map((item) => item.area_clipped_scaled), + borderColor: getCSSVariable('area'), + }, + { + label: t('Average Connectivity Score'), + data: filteredData.map((item) => item.connectivity_clipped_scaled), + borderColor: getCSSVariable('connectivity'), + }, + { + label: t('Average Habitat Score'), + data: filteredData.map((item) => item.shi_clipped_scaled), + borderColor: getCSSVariable('habitat'), + }, + ], + }); + }; + + useEffect(() => { + if (selectedProvince && shiProvinceTrendData.length) { + setIsLoading(false); + getChartData(); + } + }, [selectedProvince, shiProvinceTrendData]); + + useEffect(() => { + if (shiProvinceTrendData.length && provinces.length) { + if (provinceName) { + const region = shiProvinceTrendData.find( + (item) => item.region_name === provinceName + ); + handleProvinceSelected(region); + } else if (selectedProvince) { + handleProvinceSelected(selectedProvince); + } else { + handleProvinceSelected(provinces[0]); + } + } + }, [shiProvinceTrendData, provinces]); + + useEffect(() => { + if (clickedRegion && shiProvinceTrendData.length) { + const region = provinces.find((item) => { + return ( + item.region_name === clickedRegion.NAME_1 || clickedRegion.region_name + ); + }); + getProvinceScores(region); + } + }, [clickedRegion, shiProvinceTrendData]); + + return ( +
+ {isLoading && } + {!isLoading && ( +
+
+ x.region_name} + getOptionValue={(x) => x.region_name} + options={provinces} + onChange={handleProvinceSelected} + placeholder={t('Select Region')} + /> + + #{spiRank} {t('in SPI')} + + + #{speciesRank} {t('in vertebrate species richness')} + + + #{areaRank} {t('in size')} + +
+
+
+ {currentYear} + {t('Year')} +
+ +
+ {percentAreaProtected.toFixed(1)}% + {t('Area Protected')} +
+ + {t('SPI')} +
+
+ )} +
+ {bubbleData && ( + <> +
+ spi-legend +
+ 0 + 100 +
+
+ + + )} +
+
+ ); +} + +export default ProvinceChartComponent; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/province-chart/province-chart-styles.module.scss b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/province-chart/province-chart-styles.module.scss new file mode 100644 index 000000000..32aeb85a1 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/province-chart/province-chart-styles.module.scss @@ -0,0 +1,132 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + padding: 10px 5px 10px 26px; + flex-grow: 1; + + --font-color: #{$white}; + --border-color: #{$brand-color-main}; + + &.light{ + --font-color: #{$black}; + --border-color: #{$mol-blue}; + } + + .info{ + display: flex; + gap: 15px; + + .specs{ + display: flex; + flex-direction: column; + row-gap: 8px; + max-width: 180px; + min-width: 180px; + + span{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-xs; + font-style: italic; + font-weight: 400; + line-height: normal; + + b{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-sm; + font-style: normal; + font-weight: 600; + line-height: normal; + } + } + } + + .arcGrid{ + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: 70px 20px; + justify-items: center; + border-radius: 5px; + height: 100px; + border: 1.5px solid #18bab4; + align-items: center; + + .values{ + display: flex; + flex-direction: column; + align-items: center; + align-self: flex-end; + + b{ + font-size: $font-size-ml; + } + } + + .spi{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .score{ + color: var(--font-color); + text-align: center; + font-family: $font-family-1; + font-size: $font-size-ml; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-top: -35px; + } + } + + b { + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-base; + font-style: normal; + font-weight: 600; + line-height: normal; + } + + span{ + color: var(--font-color); + font-family: $font-family-1; + font-size: $font-size-xs; + font-style: italic; + font-weight: 600; + line-height: normal; + text-transform: uppercase; + } + } + } + .chart{ + width: 100%; + margin-top: 25px; + max-width: 580px; + + .mapLegend{ + height: 15px; + display: flex; + flex-direction: column; + margin-bottom: 20px; + + img{ + width: 100%; + height: 15px; + } + + .legendValues{ + color: var(--font-color); + display: flex; + justify-content: space-between; + } + } + } +} diff --git a/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/temporal-trends-spi-component.jsx b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/temporal-trends-spi-component.jsx new file mode 100644 index 000000000..7ccb596b4 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/temporal-trends-spi-component.jsx @@ -0,0 +1,143 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { useT, T } from '@transifex/react'; + +import cx from 'classnames'; +import last from 'lodash/last'; + +import Button from 'components/button'; + +import { LightModeContext } from '../../../../../context/light-mode'; +import { + NATIONAL_TREND, + PROVINCE_TREND, +} from '../../dashboard-trends-sidebar-component'; +import styles from '../../dashboard-trends-sidebar-styles.module.scss'; + +import NationalChartContainer from './national-chart'; +import ProvinceChartContainer from './province-chart'; +import TrendTableComponent from './trend-table/trend-table-component'; + +function TemporalTrendsSpiComponent(props) { + const t = useT(); + const { lightMode } = useContext(LightModeContext); + const { countryName, activeTrend, setActiveTrend, countryData } = props; + + const [showTable, setShowTable] = useState(false); + const [spiInfo, setSpiInfo] = useState(); + const [areaProtectedPercent, setAreaProtectedPercent] = useState(); + const [areaProtected, setAreaProtected] = useState(0); + const [startYear, setStartYear] = useState('1980'); + + const getNationalData = async () => { + if (countryData) { + const firstScore = countryData[0]; + setStartYear(firstScore.Year); + const currentScore = last(countryData); + setAreaProtectedPercent(currentScore.PercentAreaProtected.toFixed(1)); + setSpiInfo( + `${firstScore.SPI.toFixed(1)} in ${ + firstScore.Year + } to ${currentScore.SPI.toFixed(1)} in ${currentScore.Year}` + ); + + const spiChange = currentScore.SPI - firstScore.SPI; + const totalRegionAreas = currentScore.AreaProtected; + const areaProtectedChange = totalRegionAreas / spiChange; + setAreaProtected(areaProtectedChange); + } + }; + + const handleActionChange = (event) => { + setShowTable(false); + setActiveTrend(event.currentTarget.innerText); + }; + + useEffect(() => { + if (!countryData.length) return; + getNationalData(); + }, [countryData]); + + return ( +
+
+ {t('Temporal Trends')} +

+ + {areaProtected.toFixed(1)} km2 + + } + areaProtectedPercentBold={{areaProtectedPercent}%} + spiInfoBold={{spiInfo}} + /> +

+
+
+
+ + {t('Toggle national SPI and province-level breakdown.')} + + {activeTrend !== NATIONAL_TREND && ( + <> + {!showTable && ( +
+
+ {!showTable && ( + <> + {activeTrend === NATIONAL_TREND && ( + + )} + {activeTrend === PROVINCE_TREND && ( + + )} + + )} + {showTable && } +
+ ); +} + +export default TemporalTrendsSpiComponent; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/temporal-trends-spi-styles.module.scss b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/temporal-trends-spi-styles.module.scss new file mode 100644 index 000000000..c709bfc60 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/temporal-trends-spi-styles.module.scss @@ -0,0 +1,4 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/index.js b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/index.js new file mode 100644 index 000000000..466bd94af --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import TrendTableComponent from './trend-table-component'; + +function TrendTableContainer(props) { + return ; +} + +export default TrendTableContainer; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/trend-table-component.jsx b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/trend-table-component.jsx new file mode 100644 index 000000000..6b72df4fd --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/trend-table-component.jsx @@ -0,0 +1,147 @@ +import React from 'react'; + +import { useT } from '@transifex/react'; + +import tableStyles from 'components/protected-areas-table/protected-areas-table-styles.module.scss'; + +import ArrowDown from 'icons/arrow_down.svg?react'; +import ArrowUp from 'icons/arrow_up.svg?react'; + +import styles from './trend-table-styles.module.scss'; + +function TrendTableComponent(props) { + const t = useT(); + const { provinces } = props; + + const handleSortChange = () => {}; + + return ( +
+
+ + + + + + + + + + + + {provinces && + provinces.map((row, index) => ( + // eslint-disable-next-line react/no-array-index-key + + + + + + + + ))} + +
+
+ {t('Province')} +
+ + handleSortChange({ value: 'NAME', ascending: true }) + } + /> + + handleSortChange({ value: 'NAME', ascending: false }) + } + /> +
+
+
+
+ {t('SPI')} +
+ + handleSortChange({ value: 'GOV_TYPE', ascending: true }) + } + /> + + handleSortChange({ + value: 'GOV_TYPE', + ascending: false, + }) + } + /> +
+
+
+
+ + {t('Area')}(km2) + +
+ + handleSortChange({ value: 'DESIG', ascending: true }) + } + /> + + handleSortChange({ value: 'DESIG', ascending: false }) + } + /> +
+
+
+
+ {t('Area Protected')} +
+ + handleSortChange({ + value: 'DESIG_TYPE', + ascending: true, + }) + } + /> + + handleSortChange({ + value: 'DESIG_TYPE', + ascending: false, + }) + } + /> +
+
+
+
+ {t('Vertebrate Richness')} +
+ + handleSortChange({ value: 'STATUS', ascending: true }) + } + /> + + handleSortChange({ value: 'STATUS', ascending: false }) + } + /> +
+
+
{row.region_name}{row.SPI.toFixed(1)} + {row.Area.toFixed(1) ?? 0} + km2 + + {row.AreaProtected?.toFixed(1) ?? 0} + km2 + {row.VertebrateRichness}
+
+
+ ); +} + +export default TrendTableComponent; diff --git a/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/trend-table-styles.module.scss b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/trend-table-styles.module.scss new file mode 100644 index 000000000..56c95a7a9 --- /dev/null +++ b/src/containers/sidebars/dashboard-trends-sidebar/spi/temporal-trends/trend-table/trend-table-styles.module.scss @@ -0,0 +1,31 @@ +@import 'styles/ui.module'; +@import 'styles/settings'; +@import 'styles/typography-extends'; +@import 'styles/common-animations.module'; + +.container{ + display: flex; + flex-direction: column; + padding: 10px 5px 10px 26px; + flex-grow: 1; + align-items: center; + gap: 8px; + + .tableWrapper{ + background-color: rgba(255, 255, 255, 0.3); + height: 300px; + width: 100%; + overflow-y: auto; + + table{ + font-size: $font-size-xxs; + + th{ + span{ + color: $white; + text-align: center; + } + } + } + } +} diff --git a/src/containers/views/dashboard-view/dashboard-view-component.jsx b/src/containers/views/dashboard-view/dashboard-view-component.jsx new file mode 100644 index 000000000..bf578d3cb --- /dev/null +++ b/src/containers/views/dashboard-view/dashboard-view-component.jsx @@ -0,0 +1,290 @@ +import React, { useEffect, useState } from 'react'; + +import { DASHBOARD } from 'router'; + +import loadable from '@loadable/component'; + +import * as promiseUtils from '@arcgis/core/core/promiseUtils.js'; +import { Loading } from 'he-components'; + +import CountryLabelsLayer from 'containers/layers/country-labels-layer'; +import RegionsLabelsLayer from 'containers/layers/regions-labels-layer'; +import SideMenu from 'containers/menus/sidemenu'; +import DashboardSidebarContainer from 'containers/sidebars/dashboard-sidebar'; + +import MapView from 'components/map-view'; + +import { + LAYER_OPTIONS, + NAVIGATION, + REGION_OPTIONS, +} from 'constants/dashboard-constants.js'; + +import TopMenuContainer from '../../../components/top-menu'; +import { LightModeProvider } from '../../../context/light-mode'; + +const { VITE_APP_ARGISJS_API_VERSION: API_VERSION } = import.meta.env; +const LabelsLayer = loadable(() => import('containers/layers/labels-layer')); + +let highlight; + +function DashboardViewComponent(props) { + const { + activeLayers, + onMapLoad, + sceneMode, + viewSettings, + countryISO, + countryName, + isFullscreenActive, + openedModal, + geometry, + selectedIndex, + setSelectedIndex, + setSelectedRegion, + selectedRegion, + selectedRegionOption, + setTaxaList, + browsePage, + scientificName, + regionLayers, + setRegionLayers, + setSelectedProvince, + } = props; + + const [map, setMap] = useState(null); + const [view, setView] = useState(null); + const [mapViewSettings, setMapViewSettings] = useState(viewSettings); + const [clickedRegion, setClickedRegion] = useState(); + const [layerView, setLayerView] = useState(); + const [onClickHandler, setOnClickHandler] = useState(null); + const [onPointerMoveHandler, setOnPointerMoveHandler] = useState(null); + let hoverHighlight; + + const getLayerView = async () => { + return view.whenLayerView( + regionLayers[LAYER_OPTIONS.PROVINCES] || + regionLayers[LAYER_OPTIONS.PROTECTED_AREAS] || + regionLayers[LAYER_OPTIONS.FORESTS] || + regionLayers[`${countryISO}-outline`] + ); + }; + + const hitTest = promiseUtils.debounce(async (event) => { + const { results } = await view.hitTest(event); + if (results.length) { + const foundLayer = results.find( + (x) => + x.graphic.attributes.NAME_1 || + x.graphic.attributes.WDPA_PID || + x.graphic.attributes.territoire + ); + if (foundLayer) { + const { graphic } = foundLayer; + const { attributes } = graphic; + if ( + Object.prototype.hasOwnProperty.call(attributes, 'NAME_1') || + Object.prototype.hasOwnProperty.call(attributes, 'WDPA_PID') || + Object.prototype.hasOwnProperty.call(attributes, 'territoire') + ) { + return { graphic, attributes }; + } + return null; + } + return null; + } + return null; + }); + + const handleRegionClicked = async (event) => { + event.stopPropagation(); + setSelectedProvince(null); + let hits; + try { + highlight?.remove(); + hoverHighlight?.remove(); + + if (selectedIndex !== NAVIGATION.BIO_IND) { + hits = await hitTest(event); + if (hits) { + switch (selectedIndex) { + case NAVIGATION.REGION: + { + setTaxaList([]); + + const { WDPA_PID, GID_1, mgc } = hits.attributes; + setSelectedIndex(NAVIGATION.EXPLORE_SPECIES); + if (selectedRegionOption === REGION_OPTIONS.PROTECTED_AREAS) { + setSelectedRegion({ WDPA_PID }); + } + + if (selectedRegionOption === REGION_OPTIONS.PROVINCES) { + setSelectedRegion({ GID_1 }); + } + + if (selectedRegionOption === REGION_OPTIONS.FORESTS) { + setSelectedRegion({ mgc }); + } + } + break; + case NAVIGATION.TRENDS: + { + const al = Object.keys(regionLayers); + browsePage({ + type: DASHBOARD, + payload: { iso: countryISO.toLowerCase() }, + query: { + scientificName, + selectedIndex, + regionLayers: al, + selectedRegion: + hits.attributes.NAME_1 ?? hits.attributes.region_name, + }, + }); + setClickedRegion(hits.attributes); + } + break; + default: + break; + } + + highlight = layerView.highlight(hits.graphic); + } + } + } catch (error) { + throw Error(error); + } + }; + + const handlePointerMove = async (event) => { + let hits; + + try { + if ( + selectedIndex !== NAVIGATION.BIO_IND && + selectedIndex !== NAVIGATION.DATA_LAYER + ) { + hits = await hitTest(event); + hoverHighlight?.remove(); + view.closePopup(); + + if (hits) { + let regionName; + if (selectedRegionOption === REGION_OPTIONS.PROTECTED_AREAS) { + if (hits.attributes.ISO3 === countryISO) { + regionName = hits.attributes.NAME; + } + } else if (selectedRegionOption === REGION_OPTIONS.PROVINCES) { + if (hits.attributes.GID_0 === countryISO) { + regionName = + hits.attributes.NAME_1 ?? hits.attributes.region_name; + } + } else if (selectedRegionOption === REGION_OPTIONS.FORESTS) { + regionName = hits.attributes.territoire; + } + + if (regionName) { + hoverHighlight = layerView.highlight(hits.graphic); + view.openPopup({ + // Set the popup's title to the coordinates of the location + title: `${regionName}`, + location: view.toMap({ x: event.x, y: event.y }), + }); + } + } + } else { + view.closePopup(); + hoverHighlight?.remove(); + } + } catch (error) { + throw Error(error); + } + }; + + const handleRegionSelected = (foundRegion) => { + highlight?.remove(); + highlight = layerView?.highlight(foundRegion.graphic); + }; + + useEffect(() => { + if (!view) return; + view.on('click', (event) => { + event.stopPropagation(); + }); + }, [view]); + + useEffect(async () => { + if (view && Object.keys(regionLayers).length) { + const layer = await getLayerView(); + setLayerView(layer); + } + }, [regionLayers, view]); + + useEffect(() => { + if (!layerView) return; + + if (onClickHandler) { + onClickHandler.remove(); + setOnClickHandler(null); + } + + if (onPointerMoveHandler) { + onPointerMoveHandler.remove(); + setOnPointerMoveHandler(null); + } + + setOnClickHandler(view.on('click', (event) => handleRegionClicked(event))); + setOnPointerMoveHandler(view.on('pointer-move', handlePointerMove)); + }, [layerView]); + + return ( + + + + + + + + + + + + + + ); +} + +export default DashboardViewComponent; diff --git a/src/containers/views/dashboard-view/dashboard-view-config.js b/src/containers/views/dashboard-view/dashboard-view-config.js new file mode 100644 index 000000000..3bb7da1f8 --- /dev/null +++ b/src/containers/views/dashboard-view/dashboard-view-config.js @@ -0,0 +1,62 @@ +import { + GRAPHIC_LAYER, + CITIES_LABELS_LAYER, + REGIONS_LABELS_LAYER, + COUNTRIES_LABELS_FEATURE_LAYER, + LANDSCAPE_FEATURES_LABELS_LAYER, + FIREFLY_BASEMAP_LAYER, + SATELLITE_BASEMAP_LAYER, +} from 'constants/layers-slugs'; +import { DASHBOARD_URLS } from 'constants/layers-urls'; + +export default { + view: { + activeLayers: [ + { title: GRAPHIC_LAYER }, + { title: CITIES_LABELS_LAYER }, + { title: REGIONS_LABELS_LAYER, opacity: 0 }, + { title: COUNTRIES_LABELS_FEATURE_LAYER }, + { title: LANDSCAPE_FEATURES_LABELS_LAYER }, + { title: DASHBOARD_URLS.ADMIN_AREAS_FEATURE_LAYER }, + ], + highlightOptions: { + color: [22, 186, 180, 1], + haloOpacity: 0.9, + fillOpacity: 0.6, + }, + padding: { + left: -200, + }, + environment: { + atmosphereEnabled: false, + background: { + type: 'color', + color: [0, 10, 16], + }, + alphaCompositingEnabled: true, + }, + constraints: { + altitude: { + max: 35512548, + min: 10000, + }, + minZoom: 3, + rotationEnabled: false, + snapToZoom: false, + minScale: 147914381, + }, + ui: { + components: [], + }, + basemap: { + layersArray: [FIREFLY_BASEMAP_LAYER, SATELLITE_BASEMAP_LAYER], + }, + }, + ui: { + isSidebarOpen: false, + isFullscreenActive: false, + activeCategory: '', + sceneMode: 'data', + selectedAnalysisLayer: DASHBOARD_URLS.ADMIN_AREAS_FEATURE_LAYER, + }, +}; diff --git a/src/containers/views/dashboard-view/dashboard-view.js b/src/containers/views/dashboard-view/dashboard-view.js new file mode 100644 index 000000000..ca4530260 --- /dev/null +++ b/src/containers/views/dashboard-view/dashboard-view.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import Component from './dashboard-view-component'; + +function DashboardView(props) { + return ; +} + +export default DashboardView; diff --git a/src/context/light-mode/index.js b/src/context/light-mode/index.js new file mode 100644 index 000000000..9992aaba5 --- /dev/null +++ b/src/context/light-mode/index.js @@ -0,0 +1,20 @@ +import React, { createContext, useState } from 'react'; + +const LightModeContext = createContext(); + +function LightModeProvider(props) { + const [lightMode, setLightMode] = useState(false); + const toggleLightMode = () => { + setLightMode(!lightMode); + }; + + return ( +
+ + {props.children} + +
+ ); +} + +export { LightModeContext, LightModeProvider }; diff --git a/src/pages/dashboard/dashboard-component.jsx b/src/pages/dashboard/dashboard-component.jsx new file mode 100644 index 000000000..5e7efd2ba --- /dev/null +++ b/src/pages/dashboard/dashboard-component.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import DashboardLogin from '../../components/dashboard-login'; +import DashboardView from '../../containers/views/dashboard-view/dashboard-view'; + +function DashboardComponent(props) { + const { activeLayers, handleMapLoad, loggedIn, setLoggedIn } = props; + + return ( + <> + {!loggedIn && } + {loggedIn && ( + handleMapLoad(map, activeLayers)} + {...props} + /> + )} + + ); +} + +export default DashboardComponent; diff --git a/src/pages/dashboard/dashboard-selectors.js b/src/pages/dashboard/dashboard-selectors.js new file mode 100644 index 000000000..5e6a8f6ba --- /dev/null +++ b/src/pages/dashboard/dashboard-selectors.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ +import { createSelector, createStructuredSelector } from 'reselect'; + +import { selectGlobeUrlState } from 'selectors/location-selectors'; + +import dashboardViewConfig from '../../containers/views/dashboard-view/dashboard-view-config'; + +const selectCountryIso = ({ location }) => location.payload.iso.toUpperCase(); +const selectCountriesData = ({ countryData }) => + countryData && (countryData.data || null); +const selectQueryParams = ({ location }) => location.query; + +const getViewSettings = createSelector(selectGlobeUrlState, (globeUrlState) => { + return { + ...dashboardViewConfig.view, + ...globeUrlState, + }; +}); + +export const getActiveLayers = createSelector( + getViewSettings, + (viewSettings) => viewSettings.activeLayers +); + +export const getCountryISO = createSelector( + selectCountryIso, + (countryISO) => countryISO +); + +const getCountryData = createSelector( + [selectCountriesData, selectCountryIso], + (countriesData, countryISO) => { + if (!countriesData || !countryISO) { + return null; + } + return countriesData[countryISO]; + } +); + +const getCountryName = createSelector([getCountryData], (countryData) => { + if (!countryData) { + return null; + } + return countryData.NAME_0; +}); + +const getQueryParams = createSelector( + selectQueryParams, + (queryParams) => queryParams +); + +export default createStructuredSelector({ + viewSettings: getViewSettings, + activeLayers: getActiveLayers, + countryISO: getCountryISO, + countryName: getCountryName, + queryParams: getQueryParams, +}); diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js new file mode 100644 index 000000000..969a8dc03 --- /dev/null +++ b/src/pages/dashboard/index.js @@ -0,0 +1,659 @@ +/* eslint-disable camelcase */ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import countryDataActions from 'redux_modules/country-data'; + +import { DASHBOARD } from 'router'; + +import { useLocale, useT } from '@transifex/react'; + +import * as urlActions from 'actions/url-actions'; + +import { activateLayersOnLoad } from 'utils/layer-manager-utils'; +import { setBasemap } from 'utils/layer-manager-utils.js'; + +import EsriFeatureService from 'services/esri-feature-service'; + +import { NAVIGATION } from 'constants/dashboard-constants'; +import { + COUNTRIES_DATA_SERVICE_URL, + DASHBOARD_URLS, +} from 'constants/layers-urls'; +import { layersConfig } from 'constants/mol-layers-configs'; + +import DashboardComponent from './dashboard-component.jsx'; +import mapStateToProps from './dashboard-selectors.js'; + +const actions = { ...countryDataActions, ...urlActions }; + +function DashboardContainer(props) { + const locale = useLocale(); + const t = useT(); + const { + viewSettings, + countryISO, + queryParams, + setCountryDataLoading, + setCountryDataReady, + setCountryDataError, + browsePage, + } = props; + + const [geometry, setGeometry] = useState(null); + const [speciesInfo, setSpeciesInfo] = useState(null); + const [data, setData] = useState(null); + const [dataLayerData, setDataLayerData] = useState(null); + const [taxaList, setTaxaList] = useState([]); + const [dataByCountry, setDataByCountry] = useState(null); + const [spiDataByCountry, setSpiDataByCountry] = useState(null); + const [selectedTaxa, setSelectedTaxa] = useState(''); + const [filteredTaxaList, setFilteredTaxaList] = useState([]); + const [scientificName, setScientificName] = useState(null); + const [selectedIndex, setSelectedIndex] = useState(NAVIGATION.HOME); + const [loggedIn, setLoggedIn] = useState(true); + const [selectedRegion, setSelectedRegion] = useState(); + const [regionLayers, setRegionLayers] = useState({}); + const [selectedRegionOption, setSelectedRegionOption] = useState(''); + const [selectedProvince, setSelectedProvince] = useState(); + const [tabOption, setTabOption] = useState(2); + const [provinceName, setProvinceName] = useState(); + const [speciesListLoading, setSpeciesListLoading] = useState(true); + const [prioritySpeciesList, setPrioritySpeciesList] = useState([]); + const [user, setUser] = useState(); + const getQueryParams = () => { + if (queryParams) { + const { + species, + tab, + trend, + region, + province, + regionLayers, + selectedRegionOption, + } = queryParams; + + if (species) { + setScientificName(species); + } + + if (tab) { + setSelectedIndex(tab); + } + + if (trend) { + setTabOption(trend); + } + + if (region) { + setSelectedRegion(region); + } + + if (province) { + setProvinceName(province); + } + + if (regionLayers) { + setRegionLayers(regionLayers); + } + + if (selectedRegionOption) { + setSelectedRegionOption(selectedRegionOption); + } + } + }; + + const getSpeciesData = async () => { + const url = `https://next-api-dot-api-2-x-dot-map-of-life.appspot.com/2.x/species/info?lang=en&scientificname=${scientificName}`; + const response = await fetch(url); + const d = await response.json(); + setSpeciesInfo(d[0]); + }; + + const getDataLayersData = async () => { + const dataLayerParams = { + scientificname: scientificName, + group: 'movement', + lang: locale, + }; + const dparams = new URLSearchParams(dataLayerParams); + const dataLayersURL = `https://dev-api.mol.org/2.x/species/datasets?${dparams}`; + const countryCode = { COG: 'CG', GAB: 'GA', COD: 'CD', LBR: 'LR' }; + const speciesObservationCount = `https://storage.googleapis.com/cdn.mol.org/eow_demo/occ/${ + countryCode[countryISO] + }_counts_${scientificName.replace(' ', '_')}.geojson`; + + const apiCalls = [dataLayersURL, speciesObservationCount]; + + const apiResponses = await Promise.all( + apiCalls.map(async (url) => { + const response = await fetch(url); + try { + const d = await response.json(); + return d; + } catch (error) { + return []; + } + }) + ); + + const [dataLayersData, speciesObservationData] = apiResponses; + + const ebirdCount = speciesObservationData.find( + (sod) => sod.which === 'ebird' + ); + const gbifCount = speciesObservationData.find( + (sod) => sod.which === 'gbif' + ); + + dataLayersData.map((dld) => { + if (dld.dataset_title.toUpperCase().match(/EBIRD/)) { + if (ebirdCount) { + dld.no_rows = ebirdCount.n; + } else { + dld.no_rows = 0; + } + } + + if (dld.dataset_title.toUpperCase().match(/GBIF/)) { + if (gbifCount) { + dld.no_rows = gbifCount.n; + } else { + dld.no_rows = 0; + } + } + }); + + setDataLayerData(dataLayersData); + }; + + const makeSpeciesListParams = (args, summary = false) => { + const params = {}; + params.lang = locale || 'en'; + if (args.lat) { + params.lat = args.lat.toString(); + } + if (args.lng) { + params.lng = args.lng.toString(); + } + if (args.radius) { + params.radius = args.radius.toString(); + } + if (args.wkt) { + params.wkt = args.wkt; + } + + if (args.geojson) { + params.geojson = args.geojson; + } + if (args.region_id) { + params.region_id = args.region_id; + } + if (args.WDPA_PID) { + params.region_attribute = 'WDPA_PID'; + params.region_dataset_id = 'wdpa'; + params.region_attribute_value = args.WDPA_PID; + } + if (args.GID_1) { + params.region_attribute = 'GID_1'; + params.region_attribute_value = args.GID_1; + params.region_dataset_id = 'gadm_states'; + } + // if(args.region_dataset_id){ + // params.region_dataset_id = args.region_dataset_id; + // } + + if (summary) { + params.summary = 'true'; + } + return params; + }; + + const getTaxaSpecies = async (taxa, slices) => { + const json = JSON.parse(slices); + let url; + + switch (taxa) { + case 'amphibians': + url = DASHBOARD_URLS.AMPHIBIAN_LOOKUP; + break; + case 'birds': + url = DASHBOARD_URLS.BIRDS_LOOKUP; + break; + case 'mammals': + url = DASHBOARD_URLS.MAMMALS_LOOKUP; + break; + case 'reptiles': + url = DASHBOARD_URLS.REPTILES_LOOKUP; + break; + default: + break; + } + + const response = await EsriFeatureService.getFeatures({ + url, + whereClause: `SliceNumber IN (${json + .map((s) => s.SliceNumber) + .join(',')})`, + returnGeometry: false, + }); + + return { + taxa, + title: t(taxa), + count: json.length, + species: response.map((r) => r.attributes), + }; + }; + + const getSpeciesDetails = (speciesData, taxa) => { + const species = speciesData.species.map( + ({ scientific_name, common_name, attributes }) => { + const { source, species_url, threat_status } = JSON.parse( + attributes.replace(/NaN/g, 'null') + )[0]; + + return { + common_name, + scientific_name, + threat_status, + source, + species_url, + taxa, + }; + } + ); + + return { + count: speciesData.species.length, + species, + taxa, + title: taxa, + }; + }; + + const getOccurenceSpecies = async (speciesData) => { + let url = DASHBOARD_URLS.SPECIES_OCCURENCE_URL; + let whereClause = `ISO3 = '${countryISO}'`; + + if (selectedRegion) { + const { GID_1, WDPA_PID } = selectedRegion; + if (GID_1) { + whereClause = `GID_1 = '${GID_1}'`; + } + + if (WDPA_PID) { + url = DASHBOARD_URLS.WDPA; + whereClause = `wdpaid = '${WDPA_PID}'`; + } + } + + if (!selectedRegion?.mgc) { + const occurenceFeatures = await EsriFeatureService.getFeatures({ + url, + whereClause, + returnGeometry: false, + }); + + const list = [...speciesData]; + + occurenceFeatures.forEach((feature) => { + const { taxa, species, attributes } = feature.attributes; + + const { source, species_url, threat_status } = JSON.parse( + attributes.replace(/NaN/g, 'null') + )[0]; + + const speciesToAdd = { + common_name: species, + scientific_name: species, + threat_status, + source, + species_url, + taxa, + }; + + const foundTaxa = list.find((t) => t.taxa === taxa); + + const foundSpecies = foundTaxa?.species.find( + (speciesToFind) => + speciesToFind.scientific_name === speciesToAdd.scientific_name + ); + + if (!foundSpecies) { + foundTaxa?.species.push(speciesToAdd); + } + }); + + list.forEach((t) => { + t.count = t.species.length; + }); + + setTaxaList(list); + } else { + setTaxaList(speciesData); + } + setSpeciesListLoading(false); + }; + + const getSpeciesList = async () => { + setSpeciesListLoading(true); + let url = DASHBOARD_URLS.PRECALC_AOI; + let whereClause = `GID_0 = '${countryISO}'`; + + if (selectedRegion) { + const { GID_1, WDPA_PID, mgc } = selectedRegion; + if (GID_1) { + whereClause = `GID_1 = '${GID_1}'`; + } + + if (WDPA_PID) { + whereClause = `WDPA_PID = '${WDPA_PID}'`; + url = DASHBOARD_URLS.WDPA_PRECALC; + } + + if (mgc) { + whereClause = `mgc_id = '${mgc}'`; + url = DASHBOARD_URLS.FOREST; + } + } + + const features = await EsriFeatureService.getFeatures({ + url, + whereClause, + returnGeometry: false, + }); + + if (features && features[0]) { + if (selectedRegion?.mgc) { + const speciesData = { + species: features.map((s) => { + const { scientificname, taxa, attributes } = s.attributes; + + const json = JSON.parse(attributes.replace(/NaN/g, 'null')); + return { + common_name: scientificname, + scientific_name: scientificname, + threat_status: json[0].threat_status, + source: json[0].source ?? '', + taxa, + }; + }), + }; + + const amphibians = speciesData.species.filter( + (item) => item.taxa === 'amphibians' + ); + const birds = speciesData.species.filter( + (item) => item.taxa === 'birds' + ); + const reptiles = speciesData.species.filter( + (item) => item.taxa === 'reptiles' + ); + const mammals = speciesData.species.filter( + (item) => item.taxa === 'mammals' + ); + + const ampSpecies = { + count: amphibians.length, + species: amphibians, + taxa: 'amphibians', + title: t('amphibians'), + }; + const birdSpecies = { + count: birds.length, + species: birds, + taxa: 'birds', + title: t('birds'), + }; + const repSpecies = { + count: reptiles.length, + species: reptiles, + taxa: 'reptiles', + title: t('reptiles'), + }; + const mamSpecies = { + count: mammals.length, + species: mammals, + taxa: 'mammals', + title: t('mammals'), + }; + const data = [ampSpecies, birdSpecies, repSpecies, mamSpecies]; + + getOccurenceSpecies(data); + } else { + const { attributes } = features[0]; + + const { amphibians, birds, reptiles, mammals } = attributes; + + const [amphibianData, birdsData, reptilesData, mammalsData] = + await Promise.all([ + getTaxaSpecies('amphibians', amphibians), + getTaxaSpecies('birds', birds), + getTaxaSpecies('reptiles', reptiles), + getTaxaSpecies('mammals', mammals), + ]); + + const ampSpecies = getSpeciesDetails(amphibianData, 'amphibians'); + const birdSpecies = getSpeciesDetails(birdsData, 'birds'); + const repSpecies = getSpeciesDetails(reptilesData, 'reptiles'); + const mamSpecies = getSpeciesDetails(mammalsData, 'mammals'); + + const speciesData = [ampSpecies, birdSpecies, repSpecies, mamSpecies]; + + getOccurenceSpecies(speciesData); + } + } + }; + + const getSpiDataByCountry = (d) => { + const spiCountryData = d.reduce((acc, obj) => { + const key = obj.country_name; + if (!acc[key]) { + acc[key] = { shs: [] }; + } + acc[key].shs.push(obj); + return acc; + }, {}); + + setSpiDataByCountry(spiCountryData); + }; + + const getDataByCountry = (d) => { + let countryData; + + // TODO: figure out what to do when no shs is returned + if (d.shs) { + countryData = d.shs.reduce((acc, obj) => { + const key = obj.country; + if (!acc[key]) { + acc[key] = { shs: [], frag: [] }; + } + acc[key].shs.push(obj); + return acc; + }, {}); + } + + if (d.frag) { + countryData = d.frag.reduce((acc, obj) => { + const key = obj.country; + if (!acc[key]) { + acc[key] = { shs: [], frag: [] }; + } + + acc[key].frag.push(obj); + return acc; + }, countryData || {}); + } + + setDataByCountry(countryData); + }; + + const getPrioritySpeciesList = async () => { + const url = DASHBOARD_URLS.PRIORITY_SPECIES; + const whereClause = `country_code = '${countryISO}'`; + + const features = await EsriFeatureService.getFeatures({ + url, + whereClause, + returnGeometry: false, + }); + + const species = features.map(({ attributes }) => attributes); + + setPrioritySpeciesList(species); + }; + + const handleMapLoad = (map, activeLayers) => { + setBasemap({ + map, + layersArray: viewSettings.basemap.layersArray, + }); + activateLayersOnLoad(map, activeLayers, layersConfig); + }; + + const getData = async () => { + const habitatTrendUrl = `https://next-api-dot-api-2-x-dot-map-of-life.appspot.com/2.x/species/indicators/habitat-trends/bycountry?scientificname=${scientificName}`; + const spiScoreURL = `https://next-api-dot-api-2-x-dot-map-of-life.appspot.com/2.x/indicators/sps/species_bycountry?scientificname=${scientificName}`; + + const apiCalls = [habitatTrendUrl, spiScoreURL]; + + const apiResponses = await Promise.all( + apiCalls.map(async (url) => { + const response = await fetch(url); + const d = await response.json(); + return d; + }) + ); + + const [habitatTrendData, spiScoreData] = apiResponses; + getDataByCountry(habitatTrendData); + getSpiDataByCountry(spiScoreData); + + setData({ habitatTrendData, spiScoreData }); + }; + + // Get Country information, allows to get country name + useEffect(() => { + getQueryParams(); + + // Function to handle back navigation + const handleBackButton = () => { + // Implement custom behavior here + setSelectedIndex(window.history.state?.selectedIndex ?? 1); + }; + + // Add event listener for popstate event + window.addEventListener('popstate', handleBackButton); + + setCountryDataLoading(); + EsriFeatureService.getFeatures({ + url: COUNTRIES_DATA_SERVICE_URL, + whereClause: `GID_0 = '${countryISO}'`, + returnGeometry: true, + }) + .then((features) => { + const { geometry } = features[0]; + + setCountryDataReady(features); + if (geometry) { + setGeometry(geometry); + } + }) + .catch((error) => { + setCountryDataError(error); + }); + + getSpeciesList(); + getPrioritySpeciesList(); + + // Cleanup event listener on component unmount + return () => { + window.removeEventListener('popstate', handleBackButton); + }; + }, []); + + useEffect(() => { + if (!selectedRegion) return; + getSpeciesList(); + }, [selectedRegion]); + + useEffect(() => { + if (!scientificName) return; + getSpeciesData(); + }, [scientificName]); + + useEffect(() => { + if (!speciesInfo) return; + getDataLayersData(); + }, [speciesInfo]); + + useEffect(() => { + if (!dataLayerData) return; + getData(); + }, [dataLayerData]); + + useEffect(() => { + browsePage({ + type: DASHBOARD, + payload: { iso: countryISO.toLowerCase() }, + query: { + species: scientificName ?? undefined, + tab: selectedIndex, + trend: tabOption ?? undefined, + region: selectedRegion ?? undefined, + // province: provinceName ?? undefined, + lang: user?.culture?.split('-')[0] ?? undefined, + }, + }); + }, [ + scientificName, + selectedIndex, + tabOption, + selectedRegion, + provinceName, + user, + ]); + + return ( + + ); +} + +export default connect(mapStateToProps, actions)(DashboardContainer); diff --git a/src/router.ts b/src/router.ts index deac323f4..8bdf03a1a 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,6 +1,6 @@ +import { connectRoutes, NOT_FOUND, redirect } from 'redux-first-router'; import { decodeUrlForState, encodeStateForUrl } from 'utils/state-to-url'; -import { connectRoutes, NOT_FOUND, redirect } from 'redux-first-router'; import type { RoutesMap } from 'redux-first-router'; export const LANDING = 'location/'; @@ -11,6 +11,7 @@ export const NATIONAL_REPORT_CARD_LANDING = 'location/NATIONAL_REPORT_CARD_LANDING'; export const AREA_OF_INTEREST = 'location/AREA_OF_INTEREST'; export const MAP_IFRAME = 'location/MAP_IFRAME'; +export const DASHBOARD = 'location/DASHBOARD'; export const routes: RoutesMap<{ path: string; page?: string }> = { [LANDING]: { @@ -37,6 +38,10 @@ export const routes: RoutesMap<{ path: string; page?: string }> = { path: '/aoi/:id?', page: 'aoi', }, + [DASHBOARD]: { + path: '/dashboard/:iso', + page: 'dashboard', + }, [NOT_FOUND]: { path: '/404', thunk: (dispatch) => dispatch(redirect({ type: LANDING })), diff --git a/src/services/esri-feature-service.ts b/src/services/esri-feature-service.ts index d33391d4f..5b2c1d14e 100644 --- a/src/services/esri-feature-service.ts +++ b/src/services/esri-feature-service.ts @@ -1,15 +1,22 @@ +import { LAYER_OPTIONS, LAYER_TITLE_TYPES } from 'constants/dashboard-constants.js'; +import { LAYERS_URLS } from 'constants/layers-urls'; +import { LOCAL_SPATIAL_REFERENCE } from 'constants/scenes-constants'; +import { AddFeature, GetFeatures, GetLayer } from 'types/services-types'; +import { + EXPERT_RANGE_MAP_URL, PROTECTED_AREA_FEATURE_URL, PROTECTED_AREA_GIN_FEATURE_URL, + PROTECTED_AREA_GUY_FEATURE_URL, PROTECTED_AREA_LIB_FEATURE_URL, PROTECTED_AREA_SLE_FEATURE_URL, + TREND_MAP_URL +} from 'utils/dashboard-utils'; + +import CSVLayer from '@arcgis/core/layers/CSVLayer'; import FeatureLayer from '@arcgis/core/layers/FeatureLayer'; -import * as query from '@arcgis/core/rest/query'; +import GeoJSONLayer from '@arcgis/core/layers/GeoJSONLayer'; +import TileLayer from '@arcgis/core/layers/TileLayer'; +import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer'; +import WebTileLayer from '@arcgis/core/layers/WebTileLayer'; import { - addFeatures, - queryFeatures, - applyEdits, - IQueryFeaturesResponse, + addFeatures, applyEdits, IQueryFeaturesResponse, queryFeatures } from '@esri/arcgis-rest-feature-layer'; -import { AddFeature, GetFeatures, GetLayer } from 'types/services-types'; - -import { LAYERS_URLS } from 'constants/layers-urls'; -import { LOCAL_SPATIAL_REFERENCE } from 'constants/scenes-constants'; function getFeatures({ url, @@ -20,6 +27,7 @@ function getFeatures({ wkid: LOCAL_SPATIAL_REFERENCE, }, geometry = null, + orderByFields = [], }: GetFeatures) { return new Promise((resolve) => { const layer = new FeatureLayer({ @@ -29,10 +37,11 @@ function getFeatures({ const featureQuery = layer.createQuery(); featureQuery.outFields = outFields; featureQuery.where = whereClause; - if (geometry) { - featureQuery.geometry = geometry; - } - featureQuery.returnGeometry = returnGeometry; + featureQuery.orderByFields = orderByFields; + // if (geometry) { + // featureQuery.geometry = geometry; + // } + // featureQuery.returnGeometry = returnGeometry; featureQuery.outSpatialReference = outSpatialReference; layer.queryFeatures(featureQuery).then((results) => { // TODO: TS-TODO: Type results. @@ -44,6 +53,87 @@ function getFeatures({ }); } +function getVectorTileLayer(url, id, countryISO) { + return new VectorTileLayer({ + url, + id, + }); +} + +function getFeatureLayer(portalItemId, countryISO, id) { + return new FeatureLayer({ + portalItem: { + id: portalItemId, + }, + outFields: ['*'], + definitionExpression: countryISO ? `GID_0 = '${countryISO}'` : '', + id: id ?? LAYER_OPTIONS.PROVINCES, + }); +} + +function getFeatureOccurenceLayer(portalItemId, scientificName, id) { + return new FeatureLayer({ + portalItem: { + id: portalItemId, + }, + outFields: ['*'], + definitionExpression: `scientific= '${scientificName}'`, + id, + }); +} + +function getCSVLayer() { + return new CSVLayer({ + // needs to be public accesible URL for csv file + url: 'https://raw.githubusercontent.com/MapofLife/half-earth-v3/refs/heads/develop/src/assets/data/drc_speciesRecords.csv', // URL to your CSV file + latitudeField: 'lat', // Name of the latitude column + longitudeField: 'long', // Name of the longitude column + id: 'POINT_OBSERVATIONS', + }); +} + +function getGeoJsonLayer(scientificname, id, countryISO = 'CD') { + return new GeoJSONLayer({ + url: `https://storage.googleapis.com/cdn.mol.org/eow_demo/occ/${countryISO}_${scientificname}.geojson`, + id, + }); +} + +async function getXYZLayer(scientificname, id, type) { + let url; + + if (type === LAYER_TITLE_TYPES.EXPERT_RANGE_MAPS) { + url = `${EXPERT_RANGE_MAP_URL}?scientificname=${scientificname}`; + } else if (type === LAYER_TITLE_TYPES.TREND) { + url = `${TREND_MAP_URL}?scientificname=${scientificname}`; + } + + const response = await fetch(url); + const data = await response.json(); + + return new WebTileLayer({ + urlTemplate: data.url, + id, + }); +} + +function getMVTSource(scientificname) { + return { + type: 'vector', + tiles: [ + 'https://production-dot-tiler-dot-map-of-life.appspot.com/0.x/tiles/regions/regions/{proj}/{z}/{x}/{y}.pbf?region_id=1673cab0-c717-4367-9db0-5c63bf26944d', + ], + }; +} + +function getTileLayer(url, id) { + return new TileLayer({ + url, + id, + visible: true, + }); +} + function getLayer({ slug, outFields = ['*'] }: GetLayer) { return new FeatureLayer({ url: LAYERS_URLS[slug], @@ -85,8 +175,48 @@ function addFeature({ url, features }: AddFeature) { }); } +function addProtectedAreaLayer(id, countryISO = 'COD') { + let featurePortalId = PROTECTED_AREA_FEATURE_URL; + + switch (countryISO) { + case 'LBR': + featurePortalId = PROTECTED_AREA_LIB_FEATURE_URL; + break; + case 'GIN': + featurePortalId = PROTECTED_AREA_GIN_FEATURE_URL; + break; + case 'SLE': + featurePortalId = PROTECTED_AREA_SLE_FEATURE_URL; + break; + case 'GUY': + featurePortalId = PROTECTED_AREA_GUY_FEATURE_URL; + break; + default: + break; + } + + const featureLayer = new FeatureLayer({ + portalItem: { + id: featurePortalId, + }, + outFields: ['*'], + id: id ?? LAYER_OPTIONS.PROTECTED_AREAS, + }); + + return featureLayer; +} + export default { getFeatures, getLayer, addFeature, + getFeatureLayer, + getGeoJsonLayer, + getVectorTileLayer, + getXYZLayer, + getTileLayer, + getMVTSource, + addProtectedAreaLayer, + getCSVLayer, + getFeatureOccurenceLayer, }; diff --git a/src/styles/settings.scss b/src/styles/settings.scss index b27df746b..1313fd993 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss @@ -22,9 +22,18 @@ $medium-purple: #8b62e9; $tango: #e87926; $french-rose: #eb588f; $neon-carrot: #ff9c32; +$bubbles: #FF7841; $white-opacity: rgba(255, 255, 255, 0.1); $white-opacity-20: rgba($white, 0.2); +$connectivity: #8578EB; +$habitat: #3AA8EE; +$area: #7DDECC; +$area-protected: #3ABC64; +$habitat-country: #2F2CAE; +$habitat-country-compare: #228D19; + + // Project colors $space-background: #09141a; $navy-he: $elephant; @@ -52,6 +61,10 @@ $birds-color: $saffron-mango; $fishes-color: #38e5db; $reptiles-color: $conifer; +// mol colors +$mol-blue: #243474; +$mol-green: #317D56; + // layers $carbon-layer: #ff9500; @@ -110,6 +123,12 @@ $spi-chart-gradient: linear-gradient( ); $challenges-view-background: #040e14; $warning: #dc2626; +$pointObservations: #54B04A; +$habitatSuitableRange: #E7C750; +$localInventories: #7D4182; +$expertRangeMap: #5A9AB5; +$regionalChecklist: #AE305C; +$privatePointObservations: #E93C39; // country ranking $endemic-species: #f8d300; @@ -159,6 +178,7 @@ $font-size-sm: 14px; $font-size-base: 1rem; $font-size-legend: 17px; $font-size-tick: 18px; +$font-size-ml: 20px; $font-size-rg: 24px; $font-size-xl: 32px; $font-size-xxl: 40px; @@ -173,6 +193,9 @@ $font-family-1: 'Open Sans', sans-serif; $font-family-1-light: 'Open Sans', sans-serif; $font-family-serif: 'ivypresto-display', serif; $font-family-sans-serif: 'Open Sans', sans-serif; +$font-family-roboto: 'Roboto', sans-serif; +$font-family-roboto-condensed: 'Roboto Condensed', sans-serif; +$font-family-roboto-flex: 'Roboto Flex', sans-serif; // Layout $site-gutter: 20px; @@ -283,6 +306,15 @@ $desktop: 'screen and (min-width: #{$breakpoint-desktop})'; --athens-gray: #{$athens-gray}; --oslo-gray: #{$oslo-gray}; + // TRENDS + --birds: #{$birds-color}; + --mammals:#{$mammals-color}; + --reptiles:#{$reptiles-color}; + --amphibians: #{$amphibians-color}; + --temporal-spi: #{$regionalChecklist}; + --bubble: #{$bubbles}; + --bubble-selected: #{$brand-color-main}; + // Landcover legend --water: #{$water}; --trees: #{$trees}; @@ -296,4 +328,16 @@ $desktop: 'screen and (min-width: #{$breakpoint-desktop})'; // FONT FAMILIES --font-family-serif: #{$font-family-serif}; -} \ No newline at end of file + + // MOL colors + --mol-blue: #{$mol-blue}; + --mol-green: #{$mol-green}; + --dark-opacity: #{$dark-opacity}; + + --connectivity: #{$connectivity}; + --habitat: #{$habitat}; + --area: #{$area}; + --area-protected: #{$area-protected}; + --habitat-country: #{$habitat-country}; + --habitat-country-compare: #{$habitat-country-compare}; +} diff --git a/src/styles/themes/checkboxes-theme.module.scss b/src/styles/themes/checkboxes-theme.module.scss index 1b2ce6a54..f7613bf2e 100644 --- a/src/styles/themes/checkboxes-theme.module.scss +++ b/src/styles/themes/checkboxes-theme.module.scss @@ -75,3 +75,42 @@ } } + + + +.pointObservations{ + input:checked+label::after { + background-color: $pointObservations; + } +} + +.privatePointObservations{ + input:checked+label::after { + background-color: $privatePointObservations; + } +} + + +.habitatSuitableRange{ + input:checked+label::after { + background-color: $habitatSuitableRange; + } +} + +.localInventories{ + input:checked+label::after { + background-color: $localInventories; + } +} + +.expertRangeMap{ + input:checked+label::after { + background-color: $expertRangeMap; + } +} + +.regionalChecklist{ + input:checked+label::after { + background-color: $regionalChecklist + } +} diff --git a/src/styles/ui.module.scss b/src/styles/ui.module.scss index 70f63c491..1bc3ebd0a 100644 --- a/src/styles/ui.module.scss +++ b/src/styles/ui.module.scss @@ -55,6 +55,16 @@ $scrollbar-width: 4px; } } +@mixin lightBackdropBlur($colour: rgba(255, 255, 255, 0.7), $fallback: $space-background, $fallbackOpacity: 0.7) { + background-color: rgba($fallback, $fallbackOpacity); + + @supports ((-webkit-backdrop-filter: blur($blur-level)) or (backdrop-filter: blur($blur-level))) { + background-color: $colour; + -webkit-backdrop-filter: blur($blur-level); + backdrop-filter: blur($blur-level); + } +} + %spacer { height: 1px; background-image: linear-gradient(to right, rgba($alto, $spacer-edge-opacity), rgba($white, $spacer-middle-opacity), rgba($alto, $spacer-edge-opacity)) @@ -102,4 +112,4 @@ $scrollbar-width: 4px; canvas { filter: blur($bottom-menu-blur); } -} \ No newline at end of file +} diff --git a/src/types/services-types.d.ts b/src/types/services-types.d.ts index d2ffb2ad9..b131967e7 100644 --- a/src/types/services-types.d.ts +++ b/src/types/services-types.d.ts @@ -127,6 +127,7 @@ export interface GetFeatures { returnGeometry: boolean; url: string; whereClause?: string; + orderByFields?: string[]; } export interface GetLayer { diff --git a/src/utils/dashboard-utils.js b/src/utils/dashboard-utils.js new file mode 100644 index 000000000..95f6c9def --- /dev/null +++ b/src/utils/dashboard-utils.js @@ -0,0 +1,86 @@ +import FeatureLayer from '@arcgis/core/layers/FeatureLayer'; +import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; +import GroupLayer from '@arcgis/core/layers/GroupLayer'; +import TileLayer from '@arcgis/core/layers/TileLayer'; + +import { DASHBOARD_LAYER_SLUGS } from 'constants/dashboard-constants'; +import { DASHBOARD_URLS } from 'constants/layers-urls'; + +export const PROVINCE_FEATURE_GLOBAL_SPI_LAYER_ID = + 'e3dca98a5bf74c9898c30f72baf6b1ba'; +export const PROVINCE_FEATURE_GLOBAL_OUTLINE_ID = + 'c6bc2248f053422da9d8d30ce591ca16'; + +// DRC LAYERS +export const PROTECTED_AREA_FEATURE_URL = '6b13aac7863a44bb915d1847dfc5dfd9'; +export const SHI_LAYER_ID = '41981d576d6042aea14595de0fb924f2'; +export const DRC_REGION_FEATURE_ID = '95cac457c0244a2286d914148c24af98'; +export const EXPERT_RANGE_MAP_URL = + 'https://next-api-dot-map-of-life.appspot.com/2.x/species/drc_rangemap'; +export const TREND_MAP_URL = + 'https://next-api-dot-map-of-life.appspot.com/2.x/species/drc_trend'; + +// LIBERIA LAYERS +export const PROTECTED_AREA_LIB_FEATURE_URL = + 'db67d0fa645047a18d83c1c5a67e9d99'; + +// GUINEA LAYERS +export const PROTECTED_AREA_GIN_FEATURE_URL = + 'fd86210b977c4ec3935f297559dcd80b'; + +// SIERRA LEONE +export const PROTECTED_AREA_SLE_FEATURE_URL = + 'af483c5930f9447080c9e49f8698882d'; + +// GUYANA +export const PROTECTED_AREA_GUY_FEATURE_URL = + 'd610d9ad96bc4071a31b3aacdfbf844d'; + +export const GBIF_OCCURENCE_URL = 'fa37779380764f939a4747e92b3d3fb2'; + +export const DASHBOARD_TABLE_URL = + 'https://services9.arcgis.com/IkktFdUAcY3WrH25/arcgis/rest/services/ESRI_table1/FeatureServer'; + +// temporary for DRC demo +export const SPECIES_LAYER_IDS = { + Myotis_bocagii: 'c41c9e06c2284b44be9fc41d144e63ba', + Hyperolius_castaneus: 'a8d710cd5a5f4124b90ff189cdcdfeba', + Chiromantis_rufescens: 'eb1019801a8b44bebd98e0452ef20132', +}; + +export const createDefaultDashboardLayers = () => { + const countries = new FeatureLayer({ + portalItem: { + id: DASHBOARD_URLS.INITIAL_COUNTRY_LAYER, + }, + id: DASHBOARD_LAYER_SLUGS.INITIAL_COUNTRY_LAYER, + }); + + const graphics = new GraphicsLayer({ + blendMode: 'destination-in', + title: 'layer', + }); + + const tileLayer = new TileLayer({ + portalItem: { + // bottom layer in the group layer + id: '10df2279f9684e4a9f6a7f08febac2a9', // world imagery + }, + }); + + const group = new GroupLayer({ + id: DASHBOARD_LAYER_SLUGS.INITIAL_GROUP_LAYER, + layers: [ + tileLayer, + // world imagery layer will show where it overlaps with the graphicslayer + graphics, + ], + opacity: 0, // initially this layer will be transparent + }); + + return { + countries, + graphics, + group, + }; +}; diff --git a/vercel.json b/vercel.json index 0a3c9786c..cfc171f2b 100644 --- a/vercel.json +++ b/vercel.json @@ -1,3 +1,9 @@ { - "outputDirectory": "dist" -} \ No newline at end of file + "outputDirectory": "dist", + "rewrites": [ + { + "source": "/(.*)", + "destination": "/" + } + ] +} diff --git a/vite.config.ts b/vite.config.ts index bdbb506f7..5773808b0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,9 +1,13 @@ import path from 'path'; - -import react from '@vitejs/plugin-react'; import { defineConfig, transformWithEsbuild } from 'vite'; import svgr from 'vite-plugin-svgr'; import viteTsconfigPaths from 'vite-tsconfig-paths'; + +import basicSsl from '@vitejs/plugin-basic-ssl'; +import react from '@vitejs/plugin-react'; + +// vite.config.js + // Custom Vite plugin for directory-named resolution function directoryNamedResolver() { return { @@ -74,6 +78,14 @@ export default defineConfig({ directoryNamedResolver(), moduleScssResolver(), svgr(), + basicSsl({ + /** name of certification */ + name: 'test', + /** custom trust domains */ + domains: ['*.custom.com'], + /** custom certification directory */ + certDir: '/Users/.../.devServer/cert', + }), ], optimizeDeps: { force: true, @@ -97,7 +109,7 @@ export default defineConfig({ rollupOptions: { output: { manualChunks: undefined, - }, + }, }, }, resolve: { @@ -118,6 +130,7 @@ export default defineConfig({ sideEffects: path.resolve(__dirname, 'src/side-effects'), actions: path.resolve(__dirname, 'src/store/actions'), utils: path.resolve(__dirname, 'src/utils'), + context: path.resolve(__dirname, 'src/context'), constants: path.resolve(__dirname, 'src/constants'), redux_modules: path.resolve(__dirname, 'src/store/redux-modules'), services: path.resolve(__dirname, 'src/services'), diff --git a/yarn.lock b/yarn.lock index f27abe4aa..433f8ae5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -199,6 +199,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.25.0": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" + integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" @@ -309,6 +316,23 @@ source-map "^0.5.7" stylis "4.2.0" +"@emotion/babel-plugin@^11.13.5": + version "11.13.5" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0" + integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.3.3" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + "@emotion/cache@^11.11.0", "@emotion/cache@^11.4.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" @@ -320,11 +344,38 @@ "@emotion/weak-memoize" "^0.3.1" stylis "4.2.0" +"@emotion/cache@^11.13.1": + version "11.13.1" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7" + integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" + +"@emotion/cache@^11.14.0": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.14.0.tgz#ee44b26986eeb93c8be82bb92f1f7a9b21b2ed76" + integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" + "@emotion/hash@^0.9.1": version "0.9.1" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== + "@emotion/is-prop-valid@^0.8.2": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" @@ -332,6 +383,13 @@ dependencies: "@emotion/memoize" "0.7.4" +"@emotion/is-prop-valid@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz#bd84ba972195e8a2d42462387581560ef780e4e2" + integrity sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/memoize@0.7.4": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" @@ -342,6 +400,25 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/react@^11.14.0": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.14.0.tgz#cfaae35ebc67dd9ef4ea2e9acc6cd29e157dd05d" + integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/cache" "^11.14.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + hoist-non-react-statics "^3.3.1" + "@emotion/react@^11.8.1": version "11.11.4" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d" @@ -367,11 +444,44 @@ "@emotion/utils" "^1.2.1" csstype "^3.0.2" +"@emotion/serialize@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.3.tgz#d291531005f17d704d0463a032fe679f376509e8" + integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== + dependencies: + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.10.0" + "@emotion/utils" "^1.4.2" + csstype "^3.0.2" + "@emotion/sheet@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== + +"@emotion/styled@^11.14.0": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.14.0.tgz#f47ca7219b1a295186d7661583376fcea95f0ff3" + integrity sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/is-prop-valid" "^1.3.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== + "@emotion/unitless@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" @@ -382,16 +492,36 @@ resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== +"@emotion/use-insertion-effect-with-fallbacks@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== + "@emotion/utils@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== +"@emotion/utils@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd" + integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ== + +"@emotion/utils@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.2.tgz#6df6c45881fcb1c412d6688a311a98b7f59c1b52" + integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== + "@emotion/weak-memoize@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + "@esbuild/aix-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" @@ -733,6 +863,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@kurkle/color@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" + integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== + "@lit-labs/ssr-dom-shim@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz#353ce4a76c83fadec272ea5674ede767650762fd" @@ -843,6 +978,86 @@ hey-listen "^1.0.8" tslib "^2.3.1" +"@mui/core-downloads-tracker@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-6.0.2.tgz#edaec4015e440b55d535a805bd9dcec7e421d6ce" + integrity sha512-Cg68oOlAfbJgMgvbCwcX3Y3HdygCl6X1nREYTdEWcEKUQhNarrC45Cc35mP+zA7p3ZXE/7FLiaTCCgwuSoef/Q== + +"@mui/icons-material@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-6.0.2.tgz#fc0fba40421eb6d44f1266b53f9d5e51b67df42b" + integrity sha512-WaTPSvKcx8X7NdWAHzJWDZv+YXvK0MUY8+JI/r4/q2GgIa5RW+n4+08CGX6jB7sWhU1R3zy28NfsDUwwQjOThw== + dependencies: + "@babel/runtime" "^7.25.0" + +"@mui/material@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-6.0.2.tgz#58a3a58d126b1b4ceb842aac401e20542ab0c17e" + integrity sha512-KrnkJFSyhsAh8V30DNUbWyRyxMi4ZHjFg1ikQGx+mUAIffFTYIEx9Q+Kxd3vCT0FUFGOmbsuh6F6yRhpybsjkg== + dependencies: + "@babel/runtime" "^7.25.0" + "@mui/core-downloads-tracker" "^6.0.2" + "@mui/system" "^6.0.2" + "@mui/types" "^7.2.16" + "@mui/utils" "^6.0.2" + "@popperjs/core" "^2.11.8" + "@types/react-transition-group" "^4.4.11" + clsx "^2.1.1" + csstype "^3.1.3" + prop-types "^15.8.1" + react-is "^18.3.1" + react-transition-group "^4.4.5" + +"@mui/private-theming@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-6.0.2.tgz#498cf18040cdb196d1280f1d829d996d2a07e004" + integrity sha512-emddFcRhA0hPGVIwIbW5g0V8vtCgw2g/H/A7jTdGe7dpCWEPpp6jPIXRRKcEUWgmg91R6rBNfV+LFHxBxmZXOQ== + dependencies: + "@babel/runtime" "^7.25.0" + "@mui/utils" "^6.0.2" + prop-types "^15.8.1" + +"@mui/styled-engine@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-6.0.2.tgz#7eac59f5e9e1a7efba24d245d62de91752a67cc3" + integrity sha512-qd3Vlhted0SYVGotnCfVNcxff7vW2WN0fclbAexff60NeNS1qs/H/CImHEHUBiUGeNWMPRochbN6VF1arQ7/jA== + dependencies: + "@babel/runtime" "^7.25.0" + "@emotion/cache" "^11.13.1" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/system@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-6.0.2.tgz#9dce511047f63a5d819914cc978b719f439ebbc3" + integrity sha512-AZv1/C4PuHgWFTA8YraIzl3FTVLdRz0RIMRwEADWZBdIhnuTHS/4+r8qE9+3CcpTHg1WsEu8btaO3AhQahSM9A== + dependencies: + "@babel/runtime" "^7.25.0" + "@mui/private-theming" "^6.0.2" + "@mui/styled-engine" "^6.0.2" + "@mui/types" "^7.2.16" + "@mui/utils" "^6.0.2" + clsx "^2.1.1" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/types@^7.2.16": + version "7.2.16" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.16.tgz#66710c691b51cd4fca95322100cd74ec230cfe30" + integrity sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag== + +"@mui/utils@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-6.0.2.tgz#be3c7e79af074bce57559b7ea4c693d3a4c3c2ca" + integrity sha512-TeFrYsxcmeoDSlkoPhX+LjIuuqC5Pyj+xz2kRceKCkUpwMNTEeVOfowXDPe+mboZwmpJ5ZxP4eiAgQMdeEasjg== + dependencies: + "@babel/runtime" "^7.25.0" + "@mui/types" "^7.2.16" + "@types/prop-types" "^15.7.12" + clsx "^2.1.1" + prop-types "^15.8.1" + react-is "^18.3.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1038,7 +1253,7 @@ dependencies: "@webcomponents/shadycss" "^1.9.1" -"@popperjs/core@^2.9.0": +"@popperjs/core@^2.11.8", "@popperjs/core@^2.9.0": version "2.11.8" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== @@ -1757,7 +1972,7 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== -"@types/prop-types@*", "@types/prop-types@^15.0.0": +"@types/prop-types@*", "@types/prop-types@^15.0.0", "@types/prop-types@^15.7.12": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== @@ -1786,6 +2001,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.11": + version "4.4.11" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5" + integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^18.0.26": version "18.3.3" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" @@ -2082,6 +2304,11 @@ dependencies: "@vaadin/vaadin-development-mode-detector" "^2.0.0" +"@vitejs/plugin-basic-ssl@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz#8b840305a6b48e8764803435ec0c716fa27d3802" + integrity sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A== + "@vitejs/plugin-react@^4.3.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz#d0be6594051ded8957df555ff07a991fb618b48e" @@ -3316,6 +3543,13 @@ charenc@0.0.2, "charenc@>= 0.0.1": resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== +chart.js@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.3.tgz#3b2e11e7010fefa99b07d0349236f5098e5226ad" + integrity sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw== + dependencies: + "@kurkle/color" "^0.3.0" + check-more-types@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" @@ -3427,7 +3661,7 @@ cli-ux@^6.0.9: supports-hyperlinks "^2.1.0" tslib "^2.0.0" -clsx@^2.0.0: +clsx@^2.0.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -3679,7 +3913,7 @@ cssfilter@0.0.10: resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw== -csstype@^3.0.2: +csstype@^3.0.2, csstype@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== @@ -7434,6 +7668,11 @@ rc-util@^5.16.1, rc-util@^5.19.2, rc-util@^5.26.0, rc-util@^5.43.0: "@babel/runtime" "^7.18.3" react-is "^18.2.0" +react-chartjs-2@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d" + integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA== + react-copy-to-clipboard@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz#09aae5ec4c62750ccb2e6421a58725eabc41255c" @@ -7470,7 +7709,7 @@ react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^18.0.0, react-is@^18.2.0: +react-is@^18.0.0, react-is@^18.2.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==