From 4de23166eed9354419537a843bfaa23dec4aaf20 Mon Sep 17 00:00:00 2001 From: Camille Monchicourt Date: Tue, 26 Apr 2022 16:19:54 +0200 Subject: [PATCH 01/24] Fix min-height for mobile in HTML example --- frontend/customization/html/homeTop.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/customization/html/homeTop.html b/frontend/customization/html/homeTop.html index 4dd7c7480..571a3d384 100644 --- a/frontend/customization/html/homeTop.html +++ b/frontend/customization/html/homeTop.html @@ -1,6 +1,6 @@
+ style="min-height: 265px;background-image: linear-gradient( 180deg,transparent 0%,#27041970 100% ),url(/medias/trek-selection.jpg); background-size: cover;background-position: center; padding:40px; border-radius: 20px;justify-content: space-between; align-items: flex-start;display: flex;flex-direction: column;">
Sélectionné par le Parc national des Écrins From 6ebfdba1ad20943503c9a4fdb6db516d0158840d Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Thu, 28 Apr 2022 12:09:36 +0200 Subject: [PATCH 02/24] Fix array key index for displaying geometries --- .../Map/DetailsMap/TouristicContent.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Map/DetailsMap/TouristicContent.tsx b/frontend/src/components/Map/DetailsMap/TouristicContent.tsx index d16406341..3a3aac65d 100644 --- a/frontend/src/components/Map/DetailsMap/TouristicContent.tsx +++ b/frontend/src/components/Map/DetailsMap/TouristicContent.tsx @@ -1,5 +1,5 @@ import { Popup } from 'components/Map/components/Popup'; -import React from 'react'; +import React, { Fragment } from 'react'; import 'leaflet/dist/leaflet.css'; import { TouristicContentGeometry } from './DetailsMap'; import { HoverableMarker } from '../components/HoverableMarker'; @@ -23,12 +23,13 @@ export const TouristicContent: React.FC = ({ contents, type = 'TOURIS case 'Point': case 'MultiPoint': return ( - <> + {(geometry.type === 'Point' ? [geometry.coordinates] : geometry.coordinates).map( coordinates => pictogramUri ? ( = ({ contents, type = 'TOURIS ) : null, )} - + ); case 'LineString': case 'MultiLineString': return ( - <> + {(geometry.type === 'LineString' ? [geometry.coordinates] : geometry.coordinates @@ -54,13 +55,13 @@ export const TouristicContent: React.FC = ({ contents, type = 'TOURIS positions={group.map(point => [point.y, point.x])} /> ))} - + ); case 'Polygon': case 'MultiPolygon': return ( - <> + {(geometry.type === 'Polygon' ? [geometry.coordinates] : geometry.coordinates @@ -73,7 +74,7 @@ export const TouristicContent: React.FC = ({ contents, type = 'TOURIS )} /> ))} - + ); default: From faaa7eacab2aac82d162b8730c4fe70c73044270 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 3 May 2022 15:49:49 +0200 Subject: [PATCH 03/24] Create useIsomorphicLayoutEffect hook --- frontend/src/hooks/useIsomorphicLayoutEffect.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 frontend/src/hooks/useIsomorphicLayoutEffect.ts diff --git a/frontend/src/hooks/useIsomorphicLayoutEffect.ts b/frontend/src/hooks/useIsomorphicLayoutEffect.ts new file mode 100644 index 000000000..b285db784 --- /dev/null +++ b/frontend/src/hooks/useIsomorphicLayoutEffect.ts @@ -0,0 +1,5 @@ +import { useEffect, useLayoutEffect } from 'react'; + +const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; + +export default useIsomorphicLayoutEffect; From 39a868a6a7a9701ab0863d2b8cb922ed3ce28d9e Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 3 May 2022 16:53:14 +0200 Subject: [PATCH 04/24] Create useSectionsReferences hook --- frontend/src/hooks/useSectionsReferences.ts | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 frontend/src/hooks/useSectionsReferences.ts diff --git a/frontend/src/hooks/useSectionsReferences.ts b/frontend/src/hooks/useSectionsReferences.ts new file mode 100644 index 000000000..c9d2aa129 --- /dev/null +++ b/frontend/src/hooks/useSectionsReferences.ts @@ -0,0 +1,62 @@ +import { useCallback, useRef, useState } from 'react'; +import { DetailsSectionsPosition } from 'components/pages/details/useDetails'; +// @ts-ignore +import debounce from 'debounce'; +import { getDimensions } from 'components/pages/details/utils'; +import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'; + +const useSectionsReferences = () => { + const sectionsReferences = useRef>({}); + const [sectionsPositions, setSectionsPositions] = useState({}); + + const useSectionReferenceCallback = (sectionName: string) => + useCallback((node: HTMLDivElement | null): void => { + if (node !== null) { + sectionsReferences.current[sectionName] = node; + setSectionsPositions(currentSectionsPositions => ({ + ...currentSectionsPositions, + [sectionName]: getDimensions(node), + })); + } + }, []); + + const handleResize = useCallback( + debounce( + () => { + setSectionsPositions(currentSectionsPositions => { + if (sectionsReferences.current === null) { + return currentSectionsPositions; + } + return Object.entries(sectionsReferences.current).reduce( + (obj, [name, ref]) => ({ + ...obj, + [name]: getDimensions(ref), + }), + {}, + ); + }); + }, + 1000, + false, + ), + [], + ); + + useIsomorphicLayoutEffect(() => { + global.addEventListener('resize', handleResize); + global.addEventListener('scroll', handleResize); + return () => { + global.removeEventListener('resize', handleResize); + global.removeEventListener('scroll', handleResize); + }; + }, []); + + return { + sectionsReferences, + sectionsPositions, + setSectionsPositions, + useSectionReferenceCallback, + }; +}; + +export default useSectionsReferences; From 6d1008c6d340d969cd373fe168ae2c5dc8dcc0a4 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 3 May 2022 15:50:24 +0200 Subject: [PATCH 05/24] Use useSectionReferences in trek details --- .../components/pages/details/useDetails.tsx | 52 ++----------------- 1 file changed, 4 insertions(+), 48 deletions(-) diff --git a/frontend/src/components/pages/details/useDetails.tsx b/frontend/src/components/pages/details/useDetails.tsx index 6191cf348..5c40d195f 100644 --- a/frontend/src/components/pages/details/useDetails.tsx +++ b/frontend/src/components/pages/details/useDetails.tsx @@ -4,14 +4,12 @@ import { getDetails, getTrekFamily } from 'modules/details/connector'; import { isUrlString } from 'modules/utils/string'; import { ONE_DAY } from 'services/constants/staleTime'; import { isRessourceMissing } from 'services/routeUtils'; -import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useRouter } from 'next/router'; import { routes } from 'services/routes'; import { useMediaPredicate } from 'react-media-hook'; -// @ts-ignore -import debounce from 'debounce'; -import { getDimensions } from './utils'; +import useSectionsReferences from 'hooks/useSectionsReferences'; export type DetailsHeaderSection = Partial>; @@ -69,50 +67,8 @@ export const useDetails = ( }, ); - const sectionsReferences = useRef({}); - const [sectionsPositions, setSectionsPositions] = useState({}); - - const useSectionReferenceCallback = (sectionName: DetailsSections) => - useCallback((node: HTMLDivElement | null) => { - if (node !== null) { - sectionsReferences.current[sectionName] = node; - setSectionsPositions(currentSectionsPositions => ({ - ...currentSectionsPositions, - [sectionName]: getDimensions(node), - })); - } - }, []); - - const handleResize = useCallback( - debounce( - () => { - setSectionsPositions(currentSectionsPositions => { - if (sectionsReferences.current === null) { - return currentSectionsPositions; - } - return Object.entries(sectionsReferences.current).reduce( - (obj, [name, ref]) => ({ - ...obj, - [name]: getDimensions(ref), - }), - {}, - ); - }); - }, - 1000, - false, - ), - [], - ); - - useLayoutEffect(() => { - global.addEventListener('resize', handleResize); - global.addEventListener('scroll', handleResize); - return () => { - global.removeEventListener('resize', handleResize); - global.removeEventListener('scroll', handleResize); - }; - }, []); + const { sectionsReferences, sectionsPositions, useSectionReferenceCallback } = + useSectionsReferences(); const setPreviewRef = useSectionReferenceCallback('preview'); const setChildrenRef = useSectionReferenceCallback('children'); From 992519aa1688bebc444bd506f9288869de5f824c Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 3 May 2022 16:55:09 +0200 Subject: [PATCH 06/24] Use useSectionReferences in touristicEvents details --- .../pages/touristicEvent/TouristicEventUI.tsx | 6 +---- .../touristicEvent/useTouristicEvent.tsx | 22 ++++--------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/pages/touristicEvent/TouristicEventUI.tsx b/frontend/src/components/pages/touristicEvent/TouristicEventUI.tsx index f66afc176..b03224f14 100644 --- a/frontend/src/components/pages/touristicEvent/TouristicEventUI.tsx +++ b/frontend/src/components/pages/touristicEvent/TouristicEventUI.tsx @@ -1,18 +1,14 @@ import { Layout } from 'components/Layout/Layout'; import { Modal } from 'components/Modal'; -import { DetailsAdvice } from 'components/pages/details/components/DetailsAdvice'; import { DetailsCardSection } from 'components/pages/details/components/DetailsCardSection'; import { DetailsDescription } from 'components/pages/details/components/DetailsDescription'; import { DetailsHeader } from 'components/pages/details/components/DetailsHeader'; -import { DetailsInformationDesk } from 'components/pages/details/components/DetailsInformationDesk'; import { DetailsSection } from 'components/pages/details/components/DetailsSection'; import { DetailsSource } from 'components/pages/details/components/DetailsSource'; import { DetailsHeaderMobile, marginDetailsChild } from 'components/pages/details/Details'; import { useOnScreenSection } from 'components/pages/details/hooks/useHighlightedSection'; import { generateTouristicContentUrl } from 'components/pages/details/utils'; import { VisibleSectionProvider } from 'components/pages/details/VisibleSectionContext'; -import { OutdoorSiteChildrenSection } from 'components/pages/site/components/OutdoorSiteChildrenSection'; -import { useOutdoorCourse } from 'components/pages/site/useOutdoorCourse'; import { useTouristicEvent } from 'components/pages/touristicEvent/useTouristicEvent'; import React, { useMemo, useRef } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; @@ -390,7 +386,7 @@ export const TouristicEventUIWithoutContext: React.FC = ({ )} ), - [touristicEventContent, isLoading, mobileMapState, sectionsPositions], + [touristicEventContent, isLoading, mobileMapState], ); }; diff --git a/frontend/src/components/pages/touristicEvent/useTouristicEvent.tsx b/frontend/src/components/pages/touristicEvent/useTouristicEvent.tsx index 449096e66..6e2bff5f7 100644 --- a/frontend/src/components/pages/touristicEvent/useTouristicEvent.tsx +++ b/frontend/src/components/pages/touristicEvent/useTouristicEvent.tsx @@ -1,8 +1,7 @@ -import { DetailsSectionsPosition } from 'components/pages/details/useDetails'; -import { getDimensions } from 'components/pages/details/utils'; +import { useState } from 'react'; import { isUrlString } from 'modules/utils/string'; -import { useCallback, useRef, useState } from 'react'; import { useQuery } from 'react-query'; +import useSectionsReferences from 'hooks/useSectionsReferences'; import { getTouristicEventDetails } from '../../../modules/touristicEvent/connector'; import { TouristicEventDetails } from '../../../modules/touristicEvent/interface'; @@ -20,21 +19,8 @@ export const useTouristicEvent = ( }, ); - const sectionsReferences = useRef>({}); - const [sectionsPositions, setSectionsPositions] = useState({}); - - const useSectionReferenceCallback = (sectionName: string) => - useCallback((node: HTMLDivElement | null) => { - if (node !== null) { - setTimeout(() => { - sectionsReferences.current[sectionName] = node; - setSectionsPositions(currentSectionsPositions => ({ - ...currentSectionsPositions, - [sectionName]: getDimensions(node), - })); - }, 1000); - } - }, []); + const { sectionsReferences, sectionsPositions, useSectionReferenceCallback } = + useSectionsReferences(); const setPreviewRef = useSectionReferenceCallback('preview'); const setDescriptionRef = useSectionReferenceCallback('description'); From f65bb70c467d3552ba53b9ab12a1caded576123a Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 3 May 2022 17:35:23 +0200 Subject: [PATCH 07/24] Use useSectionReferences in outdoor details --- .../components/pages/site/OutdoorCourseUI.tsx | 2 +- .../components/pages/site/OutdoorSiteUI.tsx | 2 +- .../pages/site/useOutdoorCourse.tsx | 22 ++++--------------- .../components/pages/site/useOutdoorSite.tsx | 22 ++++--------------- 4 files changed, 10 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/pages/site/OutdoorCourseUI.tsx b/frontend/src/components/pages/site/OutdoorCourseUI.tsx index f7a5d5658..62a66bb97 100644 --- a/frontend/src/components/pages/site/OutdoorCourseUI.tsx +++ b/frontend/src/components/pages/site/OutdoorCourseUI.tsx @@ -386,7 +386,7 @@ export const OutdoorCourseUIWithoutContext: React.FC = ({ outdoorCourseUr )} ), - [outdoorCourseContent, isLoading, mobileMapState, sectionsPositions], + [outdoorCourseContent, isLoading, mobileMapState], ); }; diff --git a/frontend/src/components/pages/site/OutdoorSiteUI.tsx b/frontend/src/components/pages/site/OutdoorSiteUI.tsx index be7af5e52..1e9ca489d 100644 --- a/frontend/src/components/pages/site/OutdoorSiteUI.tsx +++ b/frontend/src/components/pages/site/OutdoorSiteUI.tsx @@ -463,7 +463,7 @@ const OutdoorSiteUIWithoutContext: React.FC = ({ outdoorSiteUrl, language )} ), - [outdoorSiteContent, isLoading, mobileMapState, sectionsPositions], + [outdoorSiteContent, isLoading, mobileMapState], ); }; diff --git a/frontend/src/components/pages/site/useOutdoorCourse.tsx b/frontend/src/components/pages/site/useOutdoorCourse.tsx index d6afcddef..23e73bc17 100644 --- a/frontend/src/components/pages/site/useOutdoorCourse.tsx +++ b/frontend/src/components/pages/site/useOutdoorCourse.tsx @@ -1,7 +1,6 @@ -import { DetailsSectionsPosition } from 'components/pages/details/useDetails'; -import { getDimensions } from 'components/pages/details/utils'; +import useSectionsReferences from 'hooks/useSectionsReferences'; import { isUrlString } from 'modules/utils/string'; -import { useCallback, useRef, useState } from 'react'; +import { useState } from 'react'; import { useQuery } from 'react-query'; import { getOutdoorCourseDetails } from '../../../modules/outdoorCourse/connector'; import { OutdoorCourseDetails } from '../../../modules/outdoorCourse/interface'; @@ -20,21 +19,8 @@ export const useOutdoorCourse = ( }, ); - const sectionsReferences = useRef>({}); - const [sectionsPositions, setSectionsPositions] = useState({}); - - const useSectionReferenceCallback = (sectionName: string) => - useCallback((node: HTMLDivElement | null) => { - if (node !== null) { - setTimeout(() => { - sectionsReferences.current[sectionName] = node; - setSectionsPositions(currentSectionsPositions => ({ - ...currentSectionsPositions, - [sectionName]: getDimensions(node), - })); - }, 1000); - } - }, []); + const { sectionsReferences, sectionsPositions, useSectionReferenceCallback } = + useSectionsReferences(); const setPreviewRef = useSectionReferenceCallback('preview'); const setPoisRef = useSectionReferenceCallback('poi'); diff --git a/frontend/src/components/pages/site/useOutdoorSite.tsx b/frontend/src/components/pages/site/useOutdoorSite.tsx index f880134c9..db7693426 100644 --- a/frontend/src/components/pages/site/useOutdoorSite.tsx +++ b/frontend/src/components/pages/site/useOutdoorSite.tsx @@ -1,7 +1,6 @@ -import { DetailsSectionsPosition } from 'components/pages/details/useDetails'; -import { getDimensions } from 'components/pages/details/utils'; +import useSectionsReferences from 'hooks/useSectionsReferences'; import { isUrlString } from 'modules/utils/string'; -import { useCallback, useRef, useState } from 'react'; +import { useState } from 'react'; import { useQuery } from 'react-query'; import { ONE_DAY } from 'services/constants/staleTime'; import { getOutdoorSiteDetails } from '../../../modules/outdoorSite/connector'; @@ -19,21 +18,8 @@ export const useOutdoorSite = (outdoorSiteUrl: string | string[] | undefined, la }, ); - const sectionsReferences = useRef>({}); - const [sectionsPositions, setSectionsPositions] = useState({}); - - const useSectionReferenceCallback = (sectionName: string) => - useCallback((node: HTMLDivElement | null) => { - if (node !== null) { - setTimeout(() => { - sectionsReferences.current[sectionName] = node; - setSectionsPositions(currentSectionsPositions => ({ - ...currentSectionsPositions, - [sectionName]: getDimensions(node), - })); - }, 1000); - } - }, []); + const { sectionsReferences, sectionsPositions, useSectionReferenceCallback } = + useSectionsReferences(); const setPreviewRef = useSectionReferenceCallback('preview'); const setAccessRef = useSectionReferenceCallback('access'); From 3dfa5139029a974a17d6110caa6b77a40714760e Mon Sep 17 00:00:00 2001 From: Camille Monchicourt Date: Thu, 19 May 2022 16:48:52 +0200 Subject: [PATCH 08/24] Changelog 3.8.3 --- docs/changelog.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index b4f23e892..7aa2b8798 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,13 +1,21 @@ # Changelog +3.8.3 (2022-04-25) +------------------ + +**🐛 Fixes** + +* Fix flickering of Outdoor, services and events maps +* Refactoring and improvements of anchor system in detail pages + 3.8.2 (2022-04-25) ------------------ **🐛 Fixes** -- Fix HTML interpretation of new "Accessibility" fields (#536 / #638) -- Don't display label filter (Others) in trek filters if no label defined as filter (#418) -- Improve outdoor subobjects display on site maps with adding it in layer control (#542) +* Fix HTML interpretation of new "Accessibility" fields (#536 / #638) +* Don't display label filter (Others) in trek filters if no label defined as filter (#418) +* Improve outdoor subobjects display on site maps with adding it in layer control (#542) 3.8.1 (2022-04-25) ------------------ From 6f6c51c3a06446babbba875e264d83b6e182f9a4 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Thu, 12 May 2022 14:59:21 +0200 Subject: [PATCH 09/24] Provide Signage information related to an activity --- .../pages/details/__tests__/Details.test.tsx | 5 +++ frontend/src/modules/signage/adapter.ts | 33 ++++++++++++++++ frontend/src/modules/signage/api.ts | 16 ++++++++ frontend/src/modules/signage/connector.ts | 39 +++++++++++++++++++ frontend/src/modules/signage/interface.ts | 33 ++++++++++++++++ frontend/src/modules/signage/mocks/index.ts | 33 ++++++++++++++++ frontend/src/modules/signageType/adapter.ts | 10 +++++ frontend/src/modules/signageType/api.ts | 12 ++++++ frontend/src/modules/signageType/connector.ts | 9 +++++ frontend/src/modules/signageType/interface.ts | 9 +++++ .../src/modules/signageType/mocks/index.ts | 26 +++++++++++++ 11 files changed, 225 insertions(+) create mode 100644 frontend/src/modules/signage/adapter.ts create mode 100644 frontend/src/modules/signage/api.ts create mode 100644 frontend/src/modules/signage/connector.ts create mode 100644 frontend/src/modules/signage/interface.ts create mode 100644 frontend/src/modules/signage/mocks/index.ts create mode 100644 frontend/src/modules/signageType/adapter.ts create mode 100644 frontend/src/modules/signageType/api.ts create mode 100644 frontend/src/modules/signageType/connector.ts create mode 100644 frontend/src/modules/signageType/interface.ts create mode 100644 frontend/src/modules/signageType/mocks/index.ts diff --git a/frontend/src/components/pages/details/__tests__/Details.test.tsx b/frontend/src/components/pages/details/__tests__/Details.test.tsx index a89ec08fd..c8b96c3aa 100644 --- a/frontend/src/components/pages/details/__tests__/Details.test.tsx +++ b/frontend/src/components/pages/details/__tests__/Details.test.tsx @@ -15,6 +15,8 @@ import { mockInformationDeskRoute } from 'modules/informationDesk/mocks'; import { mockLabelRoute } from 'modules/label/mocks'; import { mockSensitiveAreaRoute } from 'modules/sensitiveArea/mocks'; import { mockSensitiveAreaPracticeRoute } from 'modules/sensitiveAreaPractice/mocks'; +import { mockSignageRoute } from 'modules/signage/mocks'; +import { mockSignageTypeRoute } from 'modules/signageType/mocks'; import { getGlobalConfig } from 'modules/utils/api.config'; import { mockNetworksResponse, @@ -86,6 +88,9 @@ describe('Details', () => { mockTouristicContentCategoryRoute(1); mockTouristicContentRoute(1, rawDetailsMock.properties.id); + mockSignageTypeRoute(1); + mockSignageRoute(1, rawDetailsMock.properties.id); + mockCityRoute(1); mockAccessibilitiesRoute(1); mockSourceRoute(1); diff --git a/frontend/src/modules/signage/adapter.ts b/frontend/src/modules/signage/adapter.ts new file mode 100644 index 000000000..628706132 --- /dev/null +++ b/frontend/src/modules/signage/adapter.ts @@ -0,0 +1,33 @@ +import { SignageTypeDictionary } from 'modules/signageType/interface'; +import { RawSignage, Signage, SignageDictionary } from './interface'; + +const adaptSignage = ( + rawSignage: RawSignage, + signageTypeDictionary: SignageTypeDictionary, +): Signage => ({ + id: rawSignage.id, + imageUrl: rawSignage.attachments?.find(({ type }) => type === 'image')?.thumbnail, + description: rawSignage.description, + geometry: rawSignage.geometry, + name: rawSignage.name, + type: signageTypeDictionary[rawSignage.type], +}); + +export const adaptSignages = ({ + rawSignages, + signageTypeDictionary, +}: { + rawSignages: RawSignage[]; + signageTypeDictionary: SignageTypeDictionary; +}): SignageDictionary | null => { + if (rawSignages.length === 0) { + return null; + } + return rawSignages.reduce( + (Signages, currentSignage) => ({ + ...Signages, + [currentSignage.id]: adaptSignage(currentSignage, signageTypeDictionary), + }), + {}, + ); +}; diff --git a/frontend/src/modules/signage/api.ts b/frontend/src/modules/signage/api.ts new file mode 100644 index 000000000..d06015951 --- /dev/null +++ b/frontend/src/modules/signage/api.ts @@ -0,0 +1,16 @@ +import { GeotrekAPI } from 'services/api/client'; +import { APIQuery, APIResponseForList } from 'services/api/interface'; +import { RawSignage } from './interface'; + +const fieldsParams = { + fields: 'attachments,description,id,geometry,name,type', +}; + +export const fetchSignage = (query: APIQuery): Promise> => { + try { + return GeotrekAPI.get('/signage', { params: { ...query, ...fieldsParams } }).then(r => r.data); + } catch (e) { + console.error('Error in signage/api/fetch', e); + throw e; + } +}; diff --git a/frontend/src/modules/signage/connector.ts b/frontend/src/modules/signage/connector.ts new file mode 100644 index 000000000..4a718af2f --- /dev/null +++ b/frontend/src/modules/signage/connector.ts @@ -0,0 +1,39 @@ +import { getSignageType } from 'modules/signageType/connector'; +import { adaptSignages } from './adapter'; +import { fetchSignage } from './api'; +import { SignageDictionary } from './interface'; + +type Type = + | 'TREK' + | 'TOURISTIC_CONTENT' + | 'OUTDOOR_SITE' + | 'OUTDOOR_COURSE' + | 'TOURISTIC_EVENT' + | undefined; + +const getNearProp = (type: Type): string | null => { + if (type === undefined) { + return null; + } + return `near_${type.replace('_', '').toLowerCase()}`; +}; +export const getSignage = async ( + language: string, + id: string, + type?: Type, +): Promise => { + const nearProp = getNearProp(type); + try { + const [rawSignage, signageTypeDictionary] = await Promise.all([ + fetchSignage({ language, ...(nearProp !== null && { [nearProp]: Number(id) }) }), + getSignageType(language), + ]); + return adaptSignages({ + rawSignages: rawSignage.results, + signageTypeDictionary, + }); + } catch (e) { + console.error('Error in signage/connector', e); + throw e; + } +}; diff --git a/frontend/src/modules/signage/interface.ts b/frontend/src/modules/signage/interface.ts new file mode 100644 index 000000000..019b13891 --- /dev/null +++ b/frontend/src/modules/signage/interface.ts @@ -0,0 +1,33 @@ +import { RawPointGeometry3D } from 'modules/interface'; +import { SignageType } from 'modules/signageType/interface'; + +export interface RawSignage { + attachments?: { + type: string; + thumbnail: string; + }[]; + id: number; + name: string; + code: string; + condition: number; + description: string; + geometry: RawPointGeometry3D; + implantation_year: number; + printed_elevation: number; + sealing: number; + structure: string; + type: number; +} + +export interface Signage { + id: number; + name: string; + description: string; + geometry: RawPointGeometry3D; + type: SignageType; + imageUrl: string | undefined; +} + +export interface SignageDictionary { + [id: string]: Signage; +} diff --git a/frontend/src/modules/signage/mocks/index.ts b/frontend/src/modules/signage/mocks/index.ts new file mode 100644 index 000000000..28d02cec0 --- /dev/null +++ b/frontend/src/modules/signage/mocks/index.ts @@ -0,0 +1,33 @@ +import { mockRoute } from 'services/testing/utils'; + +export const mockSignageResponse = () => ({ + count: 1, + next: null, + previous: null, + results: [ + { + attachments: [ + { + type: 'image', + thumbnail: 'url/to/image.jpg', + }, + ], + id: 1, + description: 'Signage description', + name: 'Signage name', + geometry: { type: 'Point', coordinates: [1, 2] }, + type: 1, + }, + ], +}); + +export const mockSignageRoute = (times: number, nearTrek: number): void => + mockRoute({ + route: '/signage', + mockData: mockSignageResponse(), + additionalQueries: { + fields: 'attachments,description,id,geometry,name,type', + near_trek: nearTrek, + }, + times, + }); diff --git a/frontend/src/modules/signageType/adapter.ts b/frontend/src/modules/signageType/adapter.ts new file mode 100644 index 000000000..5a1f160ce --- /dev/null +++ b/frontend/src/modules/signageType/adapter.ts @@ -0,0 +1,10 @@ +import { SignageType, SignageTypeDictionary } from './interface'; +export const adaptSignageType = (signageType: SignageType[]): SignageTypeDictionary => { + return signageType.reduce( + (list, item) => ({ + ...list, + [item.id]: item, + }), + {}, + ); +}; diff --git a/frontend/src/modules/signageType/api.ts b/frontend/src/modules/signageType/api.ts new file mode 100644 index 000000000..9ffbe4fb0 --- /dev/null +++ b/frontend/src/modules/signageType/api.ts @@ -0,0 +1,12 @@ +import { GeotrekAPI } from 'services/api/client'; +import { APIQuery, APIResponseForList } from 'services/api/interface'; +import { SignageType } from './interface'; + +const fieldsParams = { + fields: 'id,label,pictogram', +}; + +export const fetchSignageType = (query: APIQuery): Promise> => + GeotrekAPI.get('/signage_type', { + params: { ...query, ...fieldsParams }, + }).then(r => r.data); diff --git a/frontend/src/modules/signageType/connector.ts b/frontend/src/modules/signageType/connector.ts new file mode 100644 index 000000000..1d3165a7a --- /dev/null +++ b/frontend/src/modules/signageType/connector.ts @@ -0,0 +1,9 @@ +import { adaptSignageType } from './adapter'; +import { fetchSignageType } from './api'; +import { SignageTypeDictionary } from './interface'; + +export const getSignageType = async (language: string): Promise => { + const rawSignageType = await fetchSignageType({ language }); + + return adaptSignageType(rawSignageType.results); +}; diff --git a/frontend/src/modules/signageType/interface.ts b/frontend/src/modules/signageType/interface.ts new file mode 100644 index 000000000..9a5042a5d --- /dev/null +++ b/frontend/src/modules/signageType/interface.ts @@ -0,0 +1,9 @@ +export interface SignageType { + id: number; + label: string; + pictogram: string; +} + +export interface SignageTypeDictionary { + [id: number]: SignageType; +} diff --git a/frontend/src/modules/signageType/mocks/index.ts b/frontend/src/modules/signageType/mocks/index.ts new file mode 100644 index 000000000..1f2d2b66d --- /dev/null +++ b/frontend/src/modules/signageType/mocks/index.ts @@ -0,0 +1,26 @@ +import { mockRoute } from 'services/testing/utils'; + +export const mockSignageTypeResponse = () => ({ + count: 1, + next: null, + previous: null, + results: [ + { + type: { + id: 1, + label: 'Entrée du parc', + pictogram: null, + }, + }, + ], +}); + +export const mockSignageTypeRoute = (times: number): void => + mockRoute({ + route: '/signage_type', + mockData: mockSignageTypeResponse(), + additionalQueries: { + fields: 'id,label,pictogram', + }, + times, + }); From 0ccc8d42a88deff81fd10dcf15a5861be42ea09b Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Thu, 19 May 2022 11:15:37 +0200 Subject: [PATCH 10/24] Create Signage generic icon --- .../src/components/Icons/Signage/index.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 frontend/src/components/Icons/Signage/index.tsx diff --git a/frontend/src/components/Icons/Signage/index.tsx b/frontend/src/components/Icons/Signage/index.tsx new file mode 100644 index 000000000..a602e5f13 --- /dev/null +++ b/frontend/src/components/Icons/Signage/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { GenericIconProps } from '../types'; + +export const Signage: React.FC = ({ + color = 'currentColor', + opacity, + className, + size, +}) => { + return ( + + + + ); +}; From 3d546ebc58864475ff003b135e0492d81b6baac8 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Thu, 19 May 2022 11:16:20 +0200 Subject: [PATCH 11/24] Create PointsSignage component --- .../Map/DetailsMap/PointsSignage.tsx | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 frontend/src/components/Map/DetailsMap/PointsSignage.tsx diff --git a/frontend/src/components/Map/DetailsMap/PointsSignage.tsx b/frontend/src/components/Map/DetailsMap/PointsSignage.tsx new file mode 100644 index 000000000..9fd66bb8e --- /dev/null +++ b/frontend/src/components/Map/DetailsMap/PointsSignage.tsx @@ -0,0 +1,110 @@ +import React, { useMemo } from 'react'; +import { Tooltip } from 'react-leaflet'; +import { Signage } from 'components/Icons/Signage'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { SignageDictionary } from 'modules/signage/interface'; +import styled, { css } from 'styled-components'; +import { desktopOnly, getSpacing } from 'stylesheet'; +import { textEllipsisAfterNLines } from 'services/cssHelpers'; +import { RawCoordinate2D } from 'modules/interface'; +import { HoverableMarker } from '../components/HoverableMarker'; + +export type PointsSignageProps = { + signage: SignageDictionary; +}; + +type Locations = { + description: string; + name: string; + imageUrl: string | undefined; + pictogramUri: string; + position: RawCoordinate2D; + type: string; +}[]; + +export const PointsSignage: React.FC = ({ signage }) => { + const locations: Locations = useMemo(() => { + return Object.values(signage) + .filter(({ geometry }) => Boolean(geometry?.coordinates)) + .map(({ description, geometry, name, type, imageUrl }) => ({ + description, + imageUrl, + name, + pictogramUri: type.pictogram ?? renderToStaticMarkup(), + position: [geometry.coordinates[1], geometry.coordinates[0]], + type: type.label, + })); + }, []); + + if (locations.length === 0) { + return null; + } + + return ( + <> + {locations.map((location, index: number) => ( + + +
+ {location.imageUrl !== undefined && } +
+
{location.type}
+ + {location.name} + +

+

+
+
+
+ ))} + + ); +}; + +const desktopWidth = 288; +const desktopImgHeight = 122; +const mobileWidth = 215; +const mobileImgHeight = 133; + +const StyledTooltip = styled(Tooltip)` + padding: 0; + border: 0px !important; + border-radius: ${getSpacing(4)} !important; + overflow: hidden; + white-space: initial !important; + width: ${mobileWidth}px; + ${desktopOnly(css` + width: ${desktopWidth}px; + `)}; +`; + +const Name = styled.span` + ${textEllipsisAfterNLines(2)} + + ${desktopOnly(css` + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + display: block; + `)} +`; + +const CoverImage = styled.img` + height: ${mobileImgHeight}px; + ${desktopOnly(css` + height: ${desktopImgHeight}px; + `)} + object-fit: cover; +`; From 93722596fd30d17c52e40e140a8d2be8254cdcd4 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Thu, 19 May 2022 11:19:04 +0200 Subject: [PATCH 12/24] Manage Signage in the map control panel --- .../src/components/Map/DetailsMap/useDetailsMap.tsx | 4 ++++ .../components/ControlSection/ControlPanel/index.tsx | 11 +++++++++++ .../Map/components/ControlSection/ControlSection.tsx | 2 ++ frontend/src/translations/ca.json | 5 +++-- frontend/src/translations/de.json | 3 ++- frontend/src/translations/en.json | 3 ++- frontend/src/translations/es.json | 3 ++- frontend/src/translations/fr.json | 3 ++- frontend/src/translations/it.json | 3 ++- 9 files changed, 30 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Map/DetailsMap/useDetailsMap.tsx b/frontend/src/components/Map/DetailsMap/useDetailsMap.tsx index 32b6bbb0f..6ced41ffb 100644 --- a/frontend/src/components/Map/DetailsMap/useDetailsMap.tsx +++ b/frontend/src/components/Map/DetailsMap/useDetailsMap.tsx @@ -18,6 +18,7 @@ export const useDetailsMap = () => { const [coursesVisibility, setCoursesVisibility] = useState('HIDDEN'); const [experiencesVisibility, setExperiencesVisibility] = useState('HIDDEN'); + const [signageVisibility, setSignageVisibility] = useState('HIDDEN'); const toggleTrekChildrenVisibility = () => setTrekChildrenVisibility(toggleVisibility); @@ -31,6 +32,7 @@ export const useDetailsMap = () => { const toggleExperiencesVisibility = () => setExperiencesVisibility(toggleVisibility); const toggleCoursesVisibility = () => setCoursesVisibility(toggleVisibility); + const toggleSignageVisibility = () => setSignageVisibility(toggleVisibility); return { trekChildrenMobileVisibility, @@ -47,5 +49,7 @@ export const useDetailsMap = () => { toggleCoursesVisibility, experiencesVisibility, toggleExperiencesVisibility, + signageVisibility, + toggleSignageVisibility, }; }; diff --git a/frontend/src/components/Map/components/ControlSection/ControlPanel/index.tsx b/frontend/src/components/Map/components/ControlSection/ControlPanel/index.tsx index e8f7ff51a..cdddfa0fa 100644 --- a/frontend/src/components/Map/components/ControlSection/ControlPanel/index.tsx +++ b/frontend/src/components/Map/components/ControlSection/ControlPanel/index.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { Florist } from 'components/Icons/Florist'; +import { Signage } from 'components/Icons/Signage'; import { Line } from './Line'; import IconLocation from './IconLocation'; import IconInfo from './IconInfo'; @@ -42,6 +43,8 @@ export const ControlPanel: React.FC = ({ toggleCoursesVisibility, experiencesVisibility, toggleExperiencesVisibility, + signageVisibility, + toggleSignageVisibility, }) => { return ( @@ -101,6 +104,14 @@ export const ControlPanel: React.FC = ({ transKey="search.map.panel.experiences" /> )} + {signageVisibility !== null && ( + + )} ); }; diff --git a/frontend/src/components/Map/components/ControlSection/ControlSection.tsx b/frontend/src/components/Map/components/ControlSection/ControlSection.tsx index dec365ef0..b393d1abb 100644 --- a/frontend/src/components/Map/components/ControlSection/ControlSection.tsx +++ b/frontend/src/components/Map/components/ControlSection/ControlSection.tsx @@ -22,6 +22,8 @@ export interface ControlSectionProps { toggleCoursesVisibility: () => void; experiencesVisibility: Visibility; toggleExperiencesVisibility: () => void; + signageVisibility: Visibility; + toggleSignageVisibility: () => void; className?: string; position?: ControlPosition; } diff --git a/frontend/src/translations/ca.json b/frontend/src/translations/ca.json index d27178fe9..b499fe72d 100644 --- a/frontend/src/translations/ca.json +++ b/frontend/src/translations/ca.json @@ -65,7 +65,8 @@ "touristicContent": "Close by", "informationDesks": "Llocs d’informació", "courses": "Parcours", - "experiences": "Lieux de pratique" + "experiences": "Lieux de pratique", + "signage": "Senyalització" }, "resetView": "Recenter el mapa" } @@ -95,7 +96,7 @@ "accessibility_advice": "Beratung", "accessibility_slope": "Neigung", "accessibility_width": "Länge", - "accessibility_signage": "Beschilderung", + "accessibility_signage": "Senyalització", "emergency_number": "Numero di emergenza", "toSee": "També a veure", "transport": "Transport", diff --git a/frontend/src/translations/de.json b/frontend/src/translations/de.json index 6d1ea7157..1ab6d688a 100644 --- a/frontend/src/translations/de.json +++ b/frontend/src/translations/de.json @@ -65,7 +65,8 @@ "touristicContent": "Close by", "informationDesks": "Orte der Information", "courses": "Parcours", - "experiences": "Lieux de pratique" + "experiences": "Lieux de pratique", + "signage": "Beschilderung" }, "resetView": "Karte zentrieren" } diff --git a/frontend/src/translations/en.json b/frontend/src/translations/en.json index 29b8872b1..a957d9c0f 100644 --- a/frontend/src/translations/en.json +++ b/frontend/src/translations/en.json @@ -67,7 +67,8 @@ "touristicContent": "Close by", "informationDesks": "Information desks", "courses": "Parcours", - "experiences": "Lieux de pratique" + "experiences": "Lieux de pratique", + "signage": "Signage" }, "resetView": "Recenter map" } diff --git a/frontend/src/translations/es.json b/frontend/src/translations/es.json index 940ecaac1..782989517 100644 --- a/frontend/src/translations/es.json +++ b/frontend/src/translations/es.json @@ -65,7 +65,8 @@ "touristicContent": "Cercano", "informationDesks": "Lugares de información", "courses": "Parcours", - "experiences": "Lieux de pratique" + "experiences": "Lieux de pratique", + "signage": "Señalización" }, "resetView": "Centrarse la mapa" } diff --git a/frontend/src/translations/fr.json b/frontend/src/translations/fr.json index 6bbd0c00b..62bffd783 100644 --- a/frontend/src/translations/fr.json +++ b/frontend/src/translations/fr.json @@ -68,7 +68,8 @@ "touristicContent": "À proximité", "informationDesks": "Lieux de renseignement", "courses": "Parcours", - "experiences": "Lieux de pratique" + "experiences": "Lieux de pratique", + "signage": "Signalétiques" }, "resetView": "Recentrer la carte" } diff --git a/frontend/src/translations/it.json b/frontend/src/translations/it.json index f2d362183..b400125d5 100644 --- a/frontend/src/translations/it.json +++ b/frontend/src/translations/it.json @@ -67,7 +67,8 @@ "touristicContent": "Vicino", "informationDesks": "Luoghi di informazione", "courses": "Parcours", - "experiences": "Lieux de pratique" + "experiences": "Lieux de pratique", + "signage": "Segnaletica" }, "resetView": "Rimettere a fuoco la mappa" } From 9c24395437351884061854d3aeef6d873a97dd33 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Thu, 19 May 2022 11:21:05 +0200 Subject: [PATCH 13/24] Provide Signage props to details page --- frontend/src/components/Map/DetailsMap/DetailsMap.tsx | 8 ++++++++ frontend/src/components/Map/DetailsMap/MapChildren.tsx | 6 ++++++ frontend/src/components/pages/details/Details.tsx | 2 ++ frontend/src/modules/details/adapter.ts | 4 ++++ frontend/src/modules/details/connector.ts | 5 ++++- frontend/src/modules/details/interface.ts | 3 ++- 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Map/DetailsMap/DetailsMap.tsx b/frontend/src/components/Map/DetailsMap/DetailsMap.tsx index 98bff425e..69cd32428 100644 --- a/frontend/src/components/Map/DetailsMap/DetailsMap.tsx +++ b/frontend/src/components/Map/DetailsMap/DetailsMap.tsx @@ -26,6 +26,7 @@ import { useDetailsAndMapContext } from 'components/pages/details/DetailsAndMapC import { Check } from 'components/Icons/Check'; import { FormattedMessage } from 'react-intl'; import { InformationDesk } from 'modules/informationDesk/interface'; +import { SignageDictionary } from 'modules/signage/interface'; import { BackButton } from '../components/BackButton'; import { TrekMarkersAndCourse } from './TrekMarkersAndCourse'; @@ -79,6 +80,7 @@ export type PropsType = { title?: string; displayAltimetricProfile?: boolean; informationDesks?: InformationDesk[]; + signage?: SignageDictionary; }; export const DetailsMap: React.FC = props => { const { reportVisibility, setReportVisibility } = useDetailsAndMapContext(); @@ -105,6 +107,8 @@ export const DetailsMap: React.FC = props => { toggleCoursesVisibility, experiencesVisibility, toggleExperiencesVisibility, + signageVisibility, + toggleSignageVisibility, } = useDetailsMap(); const mapConfig = getMapConfig(); @@ -189,6 +193,7 @@ export const DetailsMap: React.FC = props => { } coursesVisibility={props.courses ? coursesVisibility : null} experiencesVisibility={props.experiences ? experiencesVisibility : null} + signageVisibility={props.signage ? signageVisibility : null} toggleTrekChildrenVisibility={toggleTrekChildrenVisibility} togglePoiVisibility={togglePoiVisibility} toggleReferencePointsVisibility={toggleReferencePointsVisibility} @@ -196,6 +201,7 @@ export const DetailsMap: React.FC = props => { toggleInformationDeskVisibility={toggleInformationDeskVisibility} toggleCoursesVisibility={toggleCoursesVisibility} toggleExperiencesVisibility={toggleExperiencesVisibility} + toggleSignageVisibility={toggleSignageVisibility} /> {props.trekGeometry && ( = props => { pointsReference={props.pointsReference} trekChildrenGeometry={props.trekChildrenGeometry} sensitiveAreasGeometry={props.sensitiveAreas} + signage={props.signage} trekChildrenMobileVisibility={trekChildrenMobileVisibility} poiMobileVisibility={poiMobileVisibility} referencePointsMobileVisibility={referencePointsMobileVisibility} @@ -228,6 +235,7 @@ export const DetailsMap: React.FC = props => { coursesVisibility={coursesVisibility} experiencesVisibility={experiencesVisibility} informationDesks={props.informationDesks} + signageVisibility={signageVisibility} /> {props.displayAltimetricProfile === true && props.trekGeoJSON && ( diff --git a/frontend/src/components/Map/DetailsMap/MapChildren.tsx b/frontend/src/components/Map/DetailsMap/MapChildren.tsx index bbf9265a9..909e566a7 100644 --- a/frontend/src/components/Map/DetailsMap/MapChildren.tsx +++ b/frontend/src/components/Map/DetailsMap/MapChildren.tsx @@ -4,6 +4,7 @@ import { InformationDesk } from 'modules/informationDesk/interface'; import { Coordinate2D } from 'modules/interface'; import { OutdoorSite } from 'modules/outdoorSite/interface'; import { SensitiveAreaGeometry } from 'modules/sensitiveArea/interface'; +import { SignageDictionary } from 'modules/signage/interface'; import React, { useContext } from 'react'; import { useMediaPredicate } from 'react-media-hook'; import { TouristicContentGeometry } from './DetailsMap'; @@ -12,6 +13,7 @@ import { MarkersWithIcon } from './MarkersWithIcon'; import { PointReport } from './PointReport'; import { PointsInformationDesk } from './PointsInformationDesk'; import { PointsReference } from './PointsReference'; +import { PointsSignage } from './PointsSignage'; import { SensitiveAreas } from './SensitiveAreas'; import { TouristicContent } from './TouristicContent'; import { TrekChildren } from './TrekChildren'; @@ -42,6 +44,8 @@ type Props = { parentId?: number; informationDeskMobileVisibility: Visibility; informationDesks?: InformationDesk[]; + signageVisibility: Visibility; + signage?: SignageDictionary; }; export const MapChildren: React.FC = props => { @@ -103,6 +107,8 @@ export const MapChildren: React.FC = props => { )} + {props.signageVisibility === 'DISPLAYED' && } + {(isMobile || visibleSection === 'report') && props.reportVisibility && } ); diff --git a/frontend/src/components/pages/details/Details.tsx b/frontend/src/components/pages/details/Details.tsx index fa155ab6e..362611d56 100644 --- a/frontend/src/components/pages/details/Details.tsx +++ b/frontend/src/components/pages/details/Details.tsx @@ -505,6 +505,7 @@ export const DetailsUIWithoutContext: React.FC = ({ detailsId, parentId, trekId={Number(id)} displayAltimetricProfile={displayAltimetricProfile} informationDesks={details.informationDesks} + signage={details.signage} />
)} @@ -561,6 +562,7 @@ export const DetailsUIWithoutContext: React.FC = ({ detailsId, parentId, trekId={Number(id)} displayAltimetricProfile={displayAltimetricProfile} informationDesks={details.informationDesks} + signage={details.signage} /> )} diff --git a/frontend/src/modules/details/adapter.ts b/frontend/src/modules/details/adapter.ts index 0f4961b0e..1d1d44fdd 100644 --- a/frontend/src/modules/details/adapter.ts +++ b/frontend/src/modules/details/adapter.ts @@ -13,6 +13,7 @@ import { dataUnits } from 'modules/results/adapter'; import { TrekResult } from 'modules/results/interface'; import { formatDistance } from 'modules/results/utils'; import { SensitiveArea } from 'modules/sensitiveArea/interface'; +import { SignageDictionary } from 'modules/signage/interface'; import { SourceDictionnary } from 'modules/source/interface'; import { TouristicContent } from 'modules/touristicContent/interface'; import { getAttachments } from 'modules/utils/adapter'; @@ -38,6 +39,7 @@ export const adaptResults = ({ children, childrenGeometry, sensitiveAreas, + signage, reservation, }: { accessbilityLevel: AccessibilityLevel | null; @@ -57,6 +59,7 @@ export const adaptResults = ({ children: TrekResult[]; childrenGeometry: TrekChildGeometry[]; sensitiveAreas: SensitiveArea[]; + signage: SignageDictionary | null; reservation: Reservation | null; }): Details => { try { @@ -155,6 +158,7 @@ export const adaptResults = ({ length2d: rawDetailsProperties.length_2d, reservation, reservation_id: rawDetailsProperties.reservation_id ?? null, + signage, }; } catch (e) { console.error('Error in details/adapter', e); diff --git a/frontend/src/modules/details/connector.ts b/frontend/src/modules/details/connector.ts index adeb75fdf..542efab82 100644 --- a/frontend/src/modules/details/connector.ts +++ b/frontend/src/modules/details/connector.ts @@ -11,6 +11,7 @@ import { getNetworks } from 'modules/networks/connector'; import { getPois } from 'modules/poi/connector'; import { getTrekResultsById } from 'modules/results/connector'; import { getSensitiveAreas } from 'modules/sensitiveArea/connector'; +import { getSignage } from 'modules/signage/connector'; import { getSources } from 'modules/source/connector'; import { getTouristicContentsNearTarget } from 'modules/touristicContent/connector'; import { getGlobalConfig } from 'modules/utils/api.config'; @@ -45,9 +46,10 @@ export const getDetails = async (id: string, language: string): Promise
getAccessibilities(language), getSources(language), ]); - const [informationDeskDictionnary, labelsDictionnary, children, sensitiveAreas] = + const [informationDeskDictionnary, signage, labelsDictionnary, children, sensitiveAreas] = await Promise.all([ getInformationDesks(language), + getSignage(language, id, 'TREK'), getLabels(language), getTrekResultsById(rawDetails.properties.children, language), getGlobalConfig().enableSensitiveAreas @@ -80,6 +82,7 @@ export const getDetails = async (id: string, language: string): Promise
children, childrenGeometry, sensitiveAreas, + signage, reservation: getGlobalConfig().reservationPartner && getGlobalConfig().reservationProject ? { diff --git a/frontend/src/modules/details/interface.ts b/frontend/src/modules/details/interface.ts index 3b71f590b..56407acc1 100644 --- a/frontend/src/modules/details/interface.ts +++ b/frontend/src/modules/details/interface.ts @@ -20,7 +20,7 @@ import { InformationDesk } from 'modules/informationDesk/interface'; import { Label } from 'modules/label/interface'; import { TrekResult } from 'modules/results/interface'; import { SensitiveArea } from 'modules/sensitiveArea/interface'; -import { NumericDictionaryIteratee } from 'lodash'; +import { SignageDictionary } from 'modules/signage/interface'; export interface RawDetails { type: string; @@ -178,6 +178,7 @@ export interface Details extends DetailsHtml { length2d: number; reservation: Reservation | null; reservation_id: string | null; + signage: SignageDictionary | null; } export interface WebLink { From 3ad9b234fa58267fccb188e004a28d92d1fa7f27 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Fri, 20 May 2022 09:58:59 +0200 Subject: [PATCH 14/24] Set Signage props as optional --- frontend/src/components/Map/DetailsMap/DetailsMap.tsx | 2 +- frontend/src/components/Map/DetailsMap/MapChildren.tsx | 2 +- frontend/src/components/Map/DetailsMap/PointsSignage.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Map/DetailsMap/DetailsMap.tsx b/frontend/src/components/Map/DetailsMap/DetailsMap.tsx index 69cd32428..cbc47c712 100644 --- a/frontend/src/components/Map/DetailsMap/DetailsMap.tsx +++ b/frontend/src/components/Map/DetailsMap/DetailsMap.tsx @@ -80,7 +80,7 @@ export type PropsType = { title?: string; displayAltimetricProfile?: boolean; informationDesks?: InformationDesk[]; - signage?: SignageDictionary; + signage?: SignageDictionary | null; }; export const DetailsMap: React.FC = props => { const { reportVisibility, setReportVisibility } = useDetailsAndMapContext(); diff --git a/frontend/src/components/Map/DetailsMap/MapChildren.tsx b/frontend/src/components/Map/DetailsMap/MapChildren.tsx index 909e566a7..56d150ec3 100644 --- a/frontend/src/components/Map/DetailsMap/MapChildren.tsx +++ b/frontend/src/components/Map/DetailsMap/MapChildren.tsx @@ -45,7 +45,7 @@ type Props = { informationDeskMobileVisibility: Visibility; informationDesks?: InformationDesk[]; signageVisibility: Visibility; - signage?: SignageDictionary; + signage?: SignageDictionary | null; }; export const MapChildren: React.FC = props => { diff --git a/frontend/src/components/Map/DetailsMap/PointsSignage.tsx b/frontend/src/components/Map/DetailsMap/PointsSignage.tsx index 9fd66bb8e..480b9a03e 100644 --- a/frontend/src/components/Map/DetailsMap/PointsSignage.tsx +++ b/frontend/src/components/Map/DetailsMap/PointsSignage.tsx @@ -10,7 +10,7 @@ import { RawCoordinate2D } from 'modules/interface'; import { HoverableMarker } from '../components/HoverableMarker'; export type PointsSignageProps = { - signage: SignageDictionary; + signage?: SignageDictionary | null; }; type Locations = { @@ -24,7 +24,7 @@ type Locations = { export const PointsSignage: React.FC = ({ signage }) => { const locations: Locations = useMemo(() => { - return Object.values(signage) + return Object.values(signage ?? {}) .filter(({ geometry }) => Boolean(geometry?.coordinates)) .map(({ description, geometry, name, type, imageUrl }) => ({ description, From 14486d19c9b3c4d959efa02f0dc82130b53fa05c Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Fri, 20 May 2022 10:00:17 +0200 Subject: [PATCH 15/24] Provide signage to outdoorSite --- frontend/src/components/pages/site/OutdoorSiteUI.tsx | 2 ++ frontend/src/modules/outdoorSite/adapter.ts | 4 ++++ frontend/src/modules/outdoorSite/connector.ts | 6 +++++- frontend/src/modules/outdoorSite/interface.ts | 2 ++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/pages/site/OutdoorSiteUI.tsx b/frontend/src/components/pages/site/OutdoorSiteUI.tsx index 1e9ca489d..af97e7bdb 100644 --- a/frontend/src/components/pages/site/OutdoorSiteUI.tsx +++ b/frontend/src/components/pages/site/OutdoorSiteUI.tsx @@ -413,6 +413,7 @@ const OutdoorSiteUIWithoutContext: React.FC = ({ outdoorSiteUrl, language sensitiveAreas={[]} trekId={Number(id)} informationDesks={outdoorSiteContent?.informationDesks} + signage={outdoorSiteContent.signage} />
)} @@ -456,6 +457,7 @@ const OutdoorSiteUIWithoutContext: React.FC = ({ outdoorSiteUrl, language }))} hideMap={hideMobileMap} trekId={Number(id)} + signage={outdoorSiteContent.signage} /> )} diff --git a/frontend/src/modules/outdoorSite/adapter.ts b/frontend/src/modules/outdoorSite/adapter.ts index 7d3a05d2f..7a4785208 100644 --- a/frontend/src/modules/outdoorSite/adapter.ts +++ b/frontend/src/modules/outdoorSite/adapter.ts @@ -1,3 +1,4 @@ +import { SignageDictionary } from 'modules/signage/interface'; import { getAttachments, getThumbnail, getThumbnails } from 'modules/utils/adapter'; import { adaptGeometry } from 'modules/utils/geometry'; import { CityDictionnary } from '../city/interface'; @@ -67,6 +68,7 @@ export const adaptOutdoorSiteDetails = ({ outdoorRating, outdoorRatingScale, outdoorSiteType, + signage, }: { rawOutdoorSiteDetails: RawOutdoorSiteDetails; pois: Poi[]; @@ -84,6 +86,7 @@ export const adaptOutdoorSiteDetails = ({ outdoorRating: OutdoorRatingChoices; outdoorRatingScale: OutdoorRatingScale[]; outdoorSiteType: OutdoorSiteTypeChoices; + signage: SignageDictionary | null; }): OutdoorSiteDetails => ({ ...adaptOutdoorSites({ rawOutdoorSites: [ @@ -131,6 +134,7 @@ export const adaptOutdoorSiteDetails = ({ }) ?? [], ratingsDescription: rawOutdoorSiteDetails.properties.ratings_description, typeSite: outdoorSiteType[Number(rawOutdoorSiteDetails?.properties?.type)], + signage, }); export const adaptOutdoorSitePopupResults = ({ diff --git a/frontend/src/modules/outdoorSite/connector.ts b/frontend/src/modules/outdoorSite/connector.ts index 63def983f..997349d8d 100644 --- a/frontend/src/modules/outdoorSite/connector.ts +++ b/frontend/src/modules/outdoorSite/connector.ts @@ -1,10 +1,11 @@ +import { getSignage } from 'modules/signage/connector'; import { getCities } from '../city/connector'; import { getThemes } from '../filters/theme/connector'; import { getInformationDesks } from '../informationDesk/connector'; import { getLabels } from '../label/connector'; import { getOutdoorCourses } from '../outdoorCourse/connector'; import { getOutdoorPractices } from '../outdoorPractice/connector'; -import { getOutdoorRating, getOutdoorRatingHashMap } from '../outdoorRating/connector'; +import { getOutdoorRating } from '../outdoorRating/connector'; import { getOutdoorRatingScale } from '../outdoorRatingScale/connector'; import { getOutdoorSiteType } from '../outdoorSiteType/connector'; import { getPois } from '../poi/connector'; @@ -74,6 +75,7 @@ export const getOutdoorSiteDetails = async ( outdoorRating, outdoorRatingScale, outdoorSiteType, + signage, ] = await Promise.all([ getTrekResults(language, { near_outdoorsite: Number(id) }), getOutdoorPractices(language), @@ -81,6 +83,7 @@ export const getOutdoorSiteDetails = async ( getOutdoorRating(language), getOutdoorRatingScale(language), getOutdoorSiteType(language), + getSignage(language, id, 'OUTDOOR_SITE'), ]); return adaptOutdoorSiteDetails({ @@ -100,6 +103,7 @@ export const getOutdoorSiteDetails = async ( outdoorRating, outdoorRatingScale, outdoorSiteType, + signage, }); } catch (e) { console.error('Error in outdoor course connector', e); diff --git a/frontend/src/modules/outdoorSite/interface.ts b/frontend/src/modules/outdoorSite/interface.ts index 80428e376..a40b5ecad 100644 --- a/frontend/src/modules/outdoorSite/interface.ts +++ b/frontend/src/modules/outdoorSite/interface.ts @@ -11,6 +11,7 @@ import { RawGeometryCollection, RawWebLink, } from 'modules/interface'; +import { SignageDictionary } from 'modules/signage/interface'; import { Activity } from '../activities/interface'; import { InformationDesk } from '../informationDesk/interface'; import { Label } from '../label/interface'; @@ -108,5 +109,6 @@ export interface OutdoorSiteDetails extends OutdoorSite { cities_raw: string[]; ratings: OutdoorRatingWithScale[]; ratingsDescription: string; + signage: SignageDictionary | null; typeSite?: OutdoorSiteType; } From 8ed87047668fc103e6c0af50f5272a4de260e676 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Fri, 20 May 2022 11:20:03 +0200 Subject: [PATCH 16/24] Provide signage to outdoorCourses --- frontend/src/components/pages/site/OutdoorCourseUI.tsx | 2 ++ frontend/src/modules/outdoorCourse/adapter.ts | 4 ++++ frontend/src/modules/outdoorCourse/connector.ts | 4 ++++ frontend/src/modules/outdoorCourse/interface.ts | 3 +++ 4 files changed, 13 insertions(+) diff --git a/frontend/src/components/pages/site/OutdoorCourseUI.tsx b/frontend/src/components/pages/site/OutdoorCourseUI.tsx index 62a66bb97..0279eddbe 100644 --- a/frontend/src/components/pages/site/OutdoorCourseUI.tsx +++ b/frontend/src/components/pages/site/OutdoorCourseUI.tsx @@ -335,6 +335,7 @@ export const OutdoorCourseUIWithoutContext: React.FC = ({ outdoorCourseUr sensitiveAreas={[]} trekId={Number(id)} title={outdoorCourseContent.name} + signage={outdoorCourseContent.signage} /> )} @@ -379,6 +380,7 @@ export const OutdoorCourseUIWithoutContext: React.FC = ({ outdoorCourseUr hideMap={hideMobileMap} trekId={Number(id)} title={outdoorCourseContent.name} + signage={outdoorCourseContent.signage} /> )} diff --git a/frontend/src/modules/outdoorCourse/adapter.ts b/frontend/src/modules/outdoorCourse/adapter.ts index d87d1f2c9..273a5159d 100644 --- a/frontend/src/modules/outdoorCourse/adapter.ts +++ b/frontend/src/modules/outdoorCourse/adapter.ts @@ -1,3 +1,4 @@ +import { SignageDictionary } from 'modules/signage/interface'; import { getAttachments, getThumbnails } from 'modules/utils/adapter'; import { adaptGeometry } from 'modules/utils/geometry'; import { CityDictionnary } from '../city/interface'; @@ -48,6 +49,7 @@ export const adaptOutdoorCourseDetails = ({ outdoorRating, outdoorRatingScale, outdoorCourseType, + signage, }: { rawOutdoorCourseDetails: RawOutdoorCourseDetails; pois: Poi[]; @@ -56,6 +58,7 @@ export const adaptOutdoorCourseDetails = ({ outdoorRating: OutdoorRatingChoices; outdoorRatingScale: OutdoorRatingScale[]; outdoorCourseType: OutdoorSiteTypeChoices; + signage: SignageDictionary | null; }): OutdoorCourseDetails => { return { // We use the original adapter @@ -94,5 +97,6 @@ export const adaptOutdoorCourseDetails = ({ ratingsDescription: rawOutdoorCourseDetails.properties.ratings_description, typeCourse: outdoorCourseType[Number(rawOutdoorCourseDetails?.properties?.type)], id: rawOutdoorCourseDetails.id, + signage, }; }; diff --git a/frontend/src/modules/outdoorCourse/connector.ts b/frontend/src/modules/outdoorCourse/connector.ts index bc3809143..6c15c521d 100644 --- a/frontend/src/modules/outdoorCourse/connector.ts +++ b/frontend/src/modules/outdoorCourse/connector.ts @@ -1,3 +1,4 @@ +import { getSignage } from 'modules/signage/connector'; import { getCities } from '../city/connector'; import { getOutdoorCourseType } from '../outdoorCourseType/connector'; import { getOutdoorRating } from '../outdoorRating/connector'; @@ -33,6 +34,7 @@ export const getOutdoorCourseDetails = async ( outdoorRating, outdoorRatingScale, outdoorCourseType, + signage, ] = await Promise.all([ fetchOutdoorCourseDetails({ language }, id), getPois(Number(id), language, 'courses'), @@ -41,6 +43,7 @@ export const getOutdoorCourseDetails = async ( getOutdoorRating(language), getOutdoorRatingScale(language), getOutdoorCourseType(language), + getSignage(language, id, 'OUTDOOR_COURSE'), ]); return adaptOutdoorCourseDetails({ @@ -51,6 +54,7 @@ export const getOutdoorCourseDetails = async ( outdoorRating, outdoorRatingScale, outdoorCourseType, + signage, }); } catch (e) { console.error('Error in outdoor course connector', e); diff --git a/frontend/src/modules/outdoorCourse/interface.ts b/frontend/src/modules/outdoorCourse/interface.ts index 42f831ead..8223e15ff 100644 --- a/frontend/src/modules/outdoorCourse/interface.ts +++ b/frontend/src/modules/outdoorCourse/interface.ts @@ -10,6 +10,7 @@ import { RawAttachment, RawGeometryCollection, } from 'modules/interface'; +import { SignageDictionary } from 'modules/signage/interface'; import { OutdoorCourseType } from '../outdoorCourseType/interface'; import { OutdoorRatingWithScale } from '../outdoorRating/interface'; import { OutdoorSite } from '../outdoorSite/interface'; @@ -86,4 +87,6 @@ export interface OutdoorCourseDetails extends OutdoorCourse { ratings: OutdoorRatingWithScale[]; ratingsDescription: string; typeCourse?: OutdoorCourseType; + + signage: SignageDictionary | null; } From b063b3701a5a6e5f08ab356988746e2f6d304d0a Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 24 May 2022 11:59:25 +0200 Subject: [PATCH 17/24] Create SubFilterField component --- .../components/FilterBar/SubFilterField.tsx | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 frontend/src/components/pages/search/components/FilterBar/SubFilterField.tsx diff --git a/frontend/src/components/pages/search/components/FilterBar/SubFilterField.tsx b/frontend/src/components/pages/search/components/FilterBar/SubFilterField.tsx new file mode 100644 index 000000000..162aecfa5 --- /dev/null +++ b/frontend/src/components/pages/search/components/FilterBar/SubFilterField.tsx @@ -0,0 +1,59 @@ +import { FilterState, Option } from 'modules/filters/interface'; +import React, { Fragment } from 'react'; +import ShowFilters from './ShowFilters'; + +interface Props { + filters?: { + [key: string]: FilterState[]; + }; + setFilterSelectedOptions: (filterId: string, options: Option[]) => void; +} + +const SubFilterField: React.FC = ({ filters, setFilterSelectedOptions }) => { + if (filters === undefined) { + return null; + } + + const entriesFilters = Object.entries(filters); + + if (entriesFilters.length === 0) { + return null; + } + + // Display filter items in a row + if (entriesFilters.length === 1) { + return ( + <> + {entriesFilters.map(([, content], index) => ( + + {content.map(filter => ( +
+ +
+ ))} +
+ ))} + + ); + } + + // else display each filter in a column + return ( + <> + {entriesFilters.map(([title, content], index) => ( +
+ {title !== 'undefined' &&
{title}
} + {content.map(filter => ( + + ))} +
+ ))} + + ); +}; + +export default SubFilterField; From 4e654a8c477d5f62e3fd823abb23023fdb40706b Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 24 May 2022 12:49:48 +0200 Subject: [PATCH 18/24] Clean useless code --- .../src/components/pages/search/components/FilterBar/Field.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/components/pages/search/components/FilterBar/Field.tsx b/frontend/src/components/pages/search/components/FilterBar/Field.tsx index fadb42ae8..882d7de4f 100644 --- a/frontend/src/components/pages/search/components/FilterBar/Field.tsx +++ b/frontend/src/components/pages/search/components/FilterBar/Field.tsx @@ -4,7 +4,6 @@ import { useIntl } from 'react-intl'; import styled from 'styled-components'; import { colorPalette } from 'stylesheet'; import { FilterState, Option } from '../../../../../modules/filters/interface'; -import getActivityColor from '../ResultCard/getActivityColor'; interface Props { filterState: FilterState; @@ -35,8 +34,6 @@ const Field: React.FC = ({ filterState, onSelect, hideLabel, id }) => { return null; }; - const color = getActivityColor(id); - return (
{!hideLabel && ( From ba3cb7febd9e688e3aad8da4e253f5418b81ec6a Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 24 May 2022 12:51:00 +0200 Subject: [PATCH 19/24] Adapt countFiltersSelected helper to receive multiple subfilters --- frontend/src/modules/filters/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/modules/filters/utils.ts b/frontend/src/modules/filters/utils.ts index 01c0dae7d..9bd5ee6b6 100644 --- a/frontend/src/modules/filters/utils.ts +++ b/frontend/src/modules/filters/utils.ts @@ -437,9 +437,9 @@ export const getNewLanguageFiltersState = ( export const countFiltersSelected = ( filtersState: FilterState[], filters: string[] | null = [], - subFilters: string[] | null = [], + subFilters: string[] | string[][] | null = [], ): number => { - const subFiltersToDisplay = filtersState.filter(({ id }) => subFilters?.includes(id)); + const subFiltersToDisplay = filtersState.filter(({ id }) => subFilters?.flat().includes(id)); const filtersToDisplay = filtersState.filter( ({ id }) => filters?.includes(id) ?? filters === null, ); From 8660a966ba188129014a00bb338ab0885c04db4e Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 24 May 2022 12:52:19 +0200 Subject: [PATCH 20/24] Filters could get treks and outdoor grouped --- .../MobileFilterMenu/MobileFilterMenu.tsx | 8 +- .../MobileFilterMenu/MobileFilterSubMenu.tsx | 77 ++++++++----- .../MobileFilterMenu/useFilterMenu.ts | 3 +- .../src/components/pages/search/Search.tsx | 5 +- .../components/FilterBar/FilterField.tsx | 104 +++++++++--------- frontend/src/modules/filters/interface.ts | 4 +- frontend/src/translations/ca.json | 1 + frontend/src/translations/de.json | 1 + frontend/src/translations/en.json | 1 + frontend/src/translations/es.json | 1 + frontend/src/translations/fr.json | 1 + frontend/src/translations/it.json | 1 + 12 files changed, 120 insertions(+), 87 deletions(-) diff --git a/frontend/src/components/MobileFilterMenu/MobileFilterMenu.tsx b/frontend/src/components/MobileFilterMenu/MobileFilterMenu.tsx index a758e294a..52a661383 100644 --- a/frontend/src/components/MobileFilterMenu/MobileFilterMenu.tsx +++ b/frontend/src/components/MobileFilterMenu/MobileFilterMenu.tsx @@ -7,6 +7,7 @@ import { Cross } from 'components/Icons/Cross'; import getActivityColor from 'components/pages/search/components/ResultCard/getActivityColor'; import { CATEGORY_ID, EVENT_ID, OUTDOOR_ID, PRACTICE_ID } from 'modules/filters/constant'; import useCounter from 'components/pages/search/hooks/useCounter'; +import { FormattedMessage } from 'react-intl'; import { FilterCategory, FilterState } from '../../modules/filters/interface'; import { countFiltersSelected } from '../../modules/filters/utils'; @@ -65,10 +66,15 @@ export const MobileFilterMenu: React.FC = ({ if (touristicEventsCount === 0 && item.id === EVENT_ID) return null; const numberSelected = countFiltersSelected(filtersState, item.filters, item.subFilters); + const name = Array.isArray(item.name) ? ( + + ) : ( + item.name + ); return ( = ({ resultsNumber, resetFilter, }) => { - const item = FILTERS_CATEGORIES.find(i => i.id === filterId); + const categories = FILTERS_CATEGORIES.find(i => i.id === filterId); - if (!item) return null; + if (!categories) return null; - const { name, filters, subFilters } = item; + const { filters, subFilters } = categories; - const subFiltersToDisplay = groupBy( - filtersState.filter(({ id }) => subFilters?.some(subFilter => new RegExp(subFilter).test(id))), - 'category', + const name = Array.isArray(categories.name) ? ( + + ) : ( + categories.name ); + + const nextSubFilters = + (subFilters && subFilters.some((subFilter: string | string[]) => !Array.isArray(subFilter)) + ? ([subFilters] as string[][]) + : (subFilters as string[][])) ?? []; + + const subFiltersToDisplay = nextSubFilters.map( + item => + groupBy( + filtersState.filter(({ id }) => + item.some((subFilter: string) => new RegExp(subFilter).test(id)), + ), + 'category', + ) ?? {}, + ); + const filtersToDisplay = filtersState.filter(({ id }) => filters?.includes(id)); /* * The library default behaviour is to have a fixed close icon which * made the icon overlap @@ -77,27 +95,34 @@ export const MobileFilterSubMenu: React.FC = ({ /> ))} - {Object.keys(subFiltersToDisplay).length > 0 && filtersToDisplay.length > 0 && ( - - )} - -
- {Object.keys(subFiltersToDisplay).map(key => { - return ( -
- {key !== 'undefined' &&
{key}
} - {subFiltersToDisplay[key].map(filterState => ( -
- + {subFiltersToDisplay.map((subFilter, index) => ( + <> + {Object.keys(subFilter).length > 0 && filtersToDisplay.length > 0 && } +
+ {Object.keys(subFilter).map(key => { + return ( +
+
+ {key !== 'undefined' + ? key + : filtersToDisplay[index].selectedOptions + .map(({ label }) => label) + .join('/')} +
+ {subFilter[key].map(filterState => ( +
+ +
+ ))}
- ))} -
- ); - })} -
+ ); + })} +
+ + ))}
diff --git a/frontend/src/components/MobileFilterMenu/useFilterMenu.ts b/frontend/src/components/MobileFilterMenu/useFilterMenu.ts index 195f2e9aa..34d5efcf6 100644 --- a/frontend/src/components/MobileFilterMenu/useFilterMenu.ts +++ b/frontend/src/components/MobileFilterMenu/useFilterMenu.ts @@ -1,9 +1,8 @@ import { FILTERS_CATEGORIES } from 'components/pages/search/components/FilterBar'; -import { FilterCategory, FilterState } from 'modules/filters/interface'; +import { FilterCategory } from 'modules/filters/interface'; import { useState } from 'react'; export const useFilterMenu = ( - filtersState: FilterState[], selectFilter: (filterId: string) => void, ): { menuState: 'DISPLAYED' | 'HIDDEN'; diff --git a/frontend/src/components/pages/search/Search.tsx b/frontend/src/components/pages/search/Search.tsx index 43e5d9c9e..d9fcf4508 100644 --- a/frontend/src/components/pages/search/Search.tsx +++ b/frontend/src/components/pages/search/Search.tsx @@ -53,10 +53,7 @@ export const SearchUI: React.FC = ({ language }) => { const { filtersState, setFilterSelectedOptions, resetFilters } = useFilter(); const { subMenuState, selectFilter, hideSubMenu, currentFilterId } = useFilterSubMenu(); - const { menuState, displayMenu, hideMenu, filtersList } = useFilterMenu( - filtersState, - selectFilter, - ); + const { menuState, displayMenu, hideMenu, filtersList } = useFilterMenu(selectFilter); const { bboxState, handleMoveMap } = useBbox(); diff --git a/frontend/src/components/pages/search/components/FilterBar/FilterField.tsx b/frontend/src/components/pages/search/components/FilterBar/FilterField.tsx index e8730b477..2e00655c5 100644 --- a/frontend/src/components/pages/search/components/FilterBar/FilterField.tsx +++ b/frontend/src/components/pages/search/components/FilterBar/FilterField.tsx @@ -1,19 +1,21 @@ import { ChevronDown } from 'components/Icons/ChevronDown'; import { Cross } from 'components/Icons/Cross'; import ShowFilters from 'components/pages/search/components/FilterBar/ShowFilters'; -import React from 'react'; +import React, { Fragment } from 'react'; import styled from 'styled-components'; import { colorPalette, sizes } from 'stylesheet'; import { groupBy } from 'lodash'; +import { FormattedMessage } from 'react-intl'; import { FilterState, Option } from '../../../../../modules/filters/interface'; import { countFiltersSelected } from '../../../../../modules/filters/utils'; import getActivityColor from '../ResultCard/getActivityColor'; +import SubFilterField from './SubFilterField'; interface Props { id: string; - name: React.ReactElement; + name: React.ReactElement | React.ReactElement[]; filters?: string[]; - subFilters?: string[]; + subFilters?: string[] | string[][]; filtersState: FilterState[]; expanded: boolean; onClick: () => void; @@ -32,14 +34,31 @@ const FilterField: React.FC = ({ filtersState, setFilterSelectedOptions, }) => { - const subFiltersToDisplay = groupBy( - filtersState.filter(({ id }) => subFilters?.some(subFilter => new RegExp(subFilter).test(id))), - 'category', - ); const filtersToDisplay = filtersState.filter(({ id }) => filters?.includes(id)); const numberSelected = countFiltersSelected(filtersState, filters, subFilters); + const tabLabel = Array.isArray(name) ? ( + + ) : ( + name + ); + + const nextSubFilters = + (subFilters && subFilters.some((subFilter: string | string[]) => !Array.isArray(subFilter)) + ? ([subFilters] as string[][]) + : (subFilters as string[][])) ?? []; + + const subFiltersToDisplay = nextSubFilters.map( + item => + groupBy( + filtersState.filter(({ id }) => + item.some((subFilter: string) => new RegExp(subFilter).test(id)), + ), + 'category', + ) ?? {}, + ); + return (
= ({ {numberSelected}
)} -
{name}
+ {tabLabel !== null &&
{tabLabel}
} = ({ className="shadow-inner" style={{ display: expanded ? 'block' : 'none', background: BACKGROUND_EXPANDED }} > -
-
{name}
-
- -
-
-
- {filtersToDisplay.map(filterState => ( - - ))} -
-
- {Object.keys(subFiltersToDisplay).length > 1 - ? Object.keys(subFiltersToDisplay).map(key => { - return ( -
- {key !== 'undefined' &&
{key}
} - {subFiltersToDisplay[key].map(filterState => ( -
- -
- ))} -
- ); - }) - : Object.values(subFiltersToDisplay)[0] - ? Object.values(subFiltersToDisplay)[0].map(filterState => ( -
- -
- )) - : null} -
+ {filtersToDisplay.map((filterState, index) => ( + +
+
{Array.isArray(name) ? name[index] : name}
+ {index === 0 && ( + + )} +
+
+ +
+
+ +
+
+ ))}
); diff --git a/frontend/src/modules/filters/interface.ts b/frontend/src/modules/filters/interface.ts index a6e85ee7f..64dcc8a2f 100644 --- a/frontend/src/modules/filters/interface.ts +++ b/frontend/src/modules/filters/interface.ts @@ -72,8 +72,8 @@ export interface FilterState { export interface FilterCategory { id: string; - name: React.ReactElement; + name: React.ReactElement | React.ReactElement[]; filters?: string[]; - subFilters?: string[]; + subFilters?: string[] | string[][]; onSelect?: () => void; } diff --git a/frontend/src/translations/ca.json b/frontend/src/translations/ca.json index d27178fe9..158098243 100644 --- a/frontend/src/translations/ca.json +++ b/frontend/src/translations/ca.json @@ -39,6 +39,7 @@ "difficulty": "Dificultat", "practices": "excursions", "outdoor": "Exterior", + "treksOutdoorGrouped": "Activities", "events": "Esdeveniments", "categories": "Serveis", "cities": "poble", diff --git a/frontend/src/translations/de.json b/frontend/src/translations/de.json index 6d1ea7157..5d2e1be53 100644 --- a/frontend/src/translations/de.json +++ b/frontend/src/translations/de.json @@ -39,6 +39,7 @@ "difficulty": "Schwierigkeit", "practices": "Wanderungen", "outdoor": "Draußen", + "treksOutdoorGrouped": "Activities", "events": "Veranstaltungen", "categories": "Dienstleistungen", "cities": "Gemeinde", diff --git a/frontend/src/translations/en.json b/frontend/src/translations/en.json index 29b8872b1..977499fc8 100644 --- a/frontend/src/translations/en.json +++ b/frontend/src/translations/en.json @@ -41,6 +41,7 @@ "difficulty": "Difficulty", "practices": "Practice", "outdoor": "Outdoor", + "treksOutdoorGrouped": "Activities", "events": "Events", "categories": "Service", "localization": "Localization", diff --git a/frontend/src/translations/es.json b/frontend/src/translations/es.json index 940ecaac1..09c3111c7 100644 --- a/frontend/src/translations/es.json +++ b/frontend/src/translations/es.json @@ -40,6 +40,7 @@ "practices": "excursiones", "outdoor": "Exterior", "events": "Eventos", + "treksOutdoorGrouped": "Activities", "categories": "Servicios", "cities": "pueblo", "localization": "Localización", diff --git a/frontend/src/translations/fr.json b/frontend/src/translations/fr.json index 6bbd0c00b..dab309173 100644 --- a/frontend/src/translations/fr.json +++ b/frontend/src/translations/fr.json @@ -42,6 +42,7 @@ "difficulty": "Difficulté", "practices": "Randonnées", "outdoor": "Outdoor", + "treksOutdoorGrouped": "Activités", "events": "Évènements", "categories": "Services", "cities": "Commune", diff --git a/frontend/src/translations/it.json b/frontend/src/translations/it.json index f2d362183..71b842beb 100644 --- a/frontend/src/translations/it.json +++ b/frontend/src/translations/it.json @@ -41,6 +41,7 @@ "difficulty": "Difficoltà", "practices": "Escursioni", "outdoor": "Outdoor", + "treksOutdoorGrouped": "Activities", "events": "Eventi", "categories": "Servizi", "cities": "Comune", From 7df40abadbcc3da1824e363087a1177510faa039 Mon Sep 17 00:00:00 2001 From: Florian Sommariva Date: Tue, 24 May 2022 14:44:11 +0200 Subject: [PATCH 21/24] Manage groupTreksAndOutdoorFilters setting --- docs/customization.md | 7 ++- frontend/config/global.json | 3 +- .../search/components/FilterBar/index.tsx | 63 +++++++++++++------ .../components/ResultCard/getActivityColor.ts | 20 +++--- frontend/src/modules/interface.ts | 1 + 5 files changed, 62 insertions(+), 32 deletions(-) diff --git a/docs/customization.md b/docs/customization.md index 87bf53bbb..bf42e548e 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -22,7 +22,8 @@ In json files, you can just override the primary keys you need. You have to over - `maxTouristicContentPerPage`: max number of touristic contents displayed on a single trek page - `portalIds`: eventual portal filters (list of ids). - `enableSensitiveAreas`: boolean, default to false. Set it to true if sensitive areas are defined in your Geotrek-admin - - `enableOutdoor`: : boolean, default to false. Set it to true to enable Outdoor sites and courses + - `enableOutdoor`: boolean, default to false. Set it to true to enable Outdoor sites and courses + - `groupTreksAndOutdoorFilters`: boolean, default to false. Groups treks and outdoor filters into a single tab. For this setting to work, `enableOutdoor` must be set to `true`. - `apiUrl` : Geotrek-admin API URL - `googleAnalyticsId`: eventual Google Analytics Id - `googleSiteVerificationToken`: eventual code to enable Google Search Console and Google developer tools @@ -135,6 +136,8 @@ It's also possible to change category colors : } ``` +NB: If global config `groupTreksAndOutdoorFilters` is set to `true`, the `outdoor` color is ignored in favor of the `trek` color. + You can also override CSS in `customization/theme/style.css` file. To help overriding CSS, some ID have been added on main DIV components (header, logo, footer, cover, cards, results, maps, details...). ## Translations @@ -157,7 +160,7 @@ You can include some HTML parts in different sections of the layout application, These templates can be translated by using the language code as a suffix (e.g. `homeTop-en.html` will be rendered only for the English interface). The application tries to find the localized template first, otherwise it tries the non-localized template, otherwise it displays nothing. NB: If you want to display a message common to all languages but not to a particular language (e.g. french), just create the template suffixed with its language code (e.g. `-fr.html`) and leave it empty, and voilà! -You can also include some scripts: +You can also include some scripts: - `customization/html/scriptsHeader.html`: in the `` of the document - `customization/html/scriptsFooter.html`: just before the `` end tag diff --git a/frontend/config/global.json b/frontend/config/global.json index 8c972943b..8c71e72ec 100644 --- a/frontend/config/global.json +++ b/frontend/config/global.json @@ -23,5 +23,6 @@ "enableMeteoWidget": true, "maxLengthTrekAllowedFor3DRando": 25000, "minAltitudeDifferenceToDisplayElevationProfile": 0, - "accessibilityCodeNumber": "114" + "accessibilityCodeNumber": "114", + "groupTreksAndOutdoorFilters": false } diff --git a/frontend/src/components/pages/search/components/FilterBar/index.tsx b/frontend/src/components/pages/search/components/FilterBar/index.tsx index 71e063483..89861af2b 100644 --- a/frontend/src/components/pages/search/components/FilterBar/index.tsx +++ b/frontend/src/components/pages/search/components/FilterBar/index.tsx @@ -2,6 +2,7 @@ import { Bin } from 'components/Icons/Bin'; import { Filter } from 'components/Icons/Filter'; import FilterField from 'components/pages/search/components/FilterBar/FilterField'; import useCounter from 'components/pages/search/hooks/useCounter'; +import { getGlobalConfig } from 'modules/utils/api.config'; import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; import styled from 'styled-components'; @@ -26,27 +27,49 @@ interface Props { language: string; } +const { groupTreksAndOutdoorFilters, enableOutdoor } = getGlobalConfig(); + +const treksAndOutdoorCategories = + groupTreksAndOutdoorFilters === true && enableOutdoor === true + ? [ + { + id: PRACTICE_ID, + name: [ + , + , + ], + filters: [PRACTICE_ID, OUTDOOR_ID], + subFilters: [ + ['difficulty', 'duration', 'length', 'routes', 'ascent', 'accessibilities', 'labels'], + ['type-outdoorRating-.+'], + ], + }, + ] + : [ + { + id: PRACTICE_ID, + name: , + filters: [PRACTICE_ID], + subFilters: [ + 'difficulty', + 'duration', + 'length', + 'routes', + 'ascent', + 'accessibilities', + 'labels', + ], + }, + { + id: OUTDOOR_ID, + name: , + filters: [OUTDOOR_ID], + subFilters: ['type-outdoorRating-.+'], + }, + ]; + export const FILTERS_CATEGORIES = [ - { - id: PRACTICE_ID, - name: , - filters: [PRACTICE_ID], - subFilters: [ - 'difficulty', - 'duration', - 'length', - 'routes', - 'ascent', - 'accessibilities', - 'labels', - ], - }, - { - id: OUTDOOR_ID, - name: , - filters: [OUTDOOR_ID], - subFilters: ['type-outdoorRating-.+'], - }, + ...treksAndOutdoorCategories, { id: CATEGORY_ID, name: , diff --git a/frontend/src/components/pages/search/components/ResultCard/getActivityColor.ts b/frontend/src/components/pages/search/components/ResultCard/getActivityColor.ts index cd829c8dc..39740fef3 100644 --- a/frontend/src/components/pages/search/components/ResultCard/getActivityColor.ts +++ b/frontend/src/components/pages/search/components/ResultCard/getActivityColor.ts @@ -1,29 +1,31 @@ import getConfig from 'next/config'; -import { colorPalette } from 'stylesheet'; -import getNextConfig from 'next/config'; const default_trek_color = '#001B84'; const default_outdoor_color = '#E69736'; const default_touristic_event_color = '#62AB41'; const default_touristic_content_color = '#3B89A2'; -const { - publicRuntimeConfig: { style, colors }, -} = getNextConfig(); - const getActivityColor = (type?: string | null): string => { const { - publicRuntimeConfig: { colors }, + publicRuntimeConfig: { + colors, + global: { groupTreksAndOutdoorFilters }, + }, } = getConfig(); const defaultColor = colors.primary1?.DEFAULT || '#AA397D'; + const outdoorColor = + groupTreksAndOutdoorFilters === true + ? colors?.categories?.trek || default_trek_color + : colors?.categories?.outdoor || default_outdoor_color; + const color = (type ? { TREK: colors?.categories?.trek || default_trek_color, - OUTDOOR_SITE: colors?.categories?.outdoor || default_outdoor_color, - OUTDOOR_COURSE: colors?.categories?.outdoor || default_outdoor_color, + OUTDOOR_SITE: outdoorColor, + OUTDOOR_COURSE: outdoorColor, TOURISTIC_CONTENT: colors?.categories?.service || default_touristic_content_color, TOURISTIC_EVENT: colors?.categories?.events || default_touristic_event_color, POI: colors?.categories?.outdoor || default_outdoor_color, diff --git a/frontend/src/modules/interface.ts b/frontend/src/modules/interface.ts index b0fab36f0..0ba23b3c2 100644 --- a/frontend/src/modules/interface.ts +++ b/frontend/src/modules/interface.ts @@ -92,6 +92,7 @@ export interface APICallsConfig { reservationProject: string; minAltitudeDifferenceToDisplayElevationProfile: number; accessibilityCodeNumber: string | null; + groupTreksAndOutdoorFilters: boolean; } /** @deprecated please use Coordinate2D or Coordinate3D instead */ From e61e9b7934d29072ad1606721379a51fc74e8dd3 Mon Sep 17 00:00:00 2001 From: Camille Monchicourt Date: Tue, 24 May 2022 23:57:36 +0200 Subject: [PATCH 22/24] 3.8.3 / Bump package.json --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 7bee84dba..d17f9a9e9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "geotrek-rando-frontend", - "version": "3.8.2", + "version": "3.8.3", "private": true, "scripts": { "debug": "NODE_OPTIONS='--inspect' next ./src", From fa2152e7531aa40ed4445d3efcd9fa8f96b3c7f3 Mon Sep 17 00:00:00 2001 From: Camille Monchicourt Date: Wed, 25 May 2022 00:06:00 +0200 Subject: [PATCH 23/24] Changelog 3.8.3 --- docs/changelog.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 7aa2b8798..eeef21f82 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,8 +1,13 @@ # Changelog -3.8.3 (2022-04-25) +3.8.3 (2022-05-24) ------------------ +**🚀 New features** + +* Add published signages on treks and outdoor maps (#408) +* Add ``groupTreksAndOutdoorFilters`` to be able to group Treks and Outdoor filters on search page (#656) + **🐛 Fixes** * Fix flickering of Outdoor, services and events maps From cdf4f666262e6ed6b4f61fe48717171cceec0b6d Mon Sep 17 00:00:00 2001 From: Camille Monchicourt Date: Wed, 25 May 2022 00:06:36 +0200 Subject: [PATCH 24/24] Changelog 3.8.3 / Typo --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index eeef21f82..11cf9f15c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,7 +6,7 @@ **🚀 New features** * Add published signages on treks and outdoor maps (#408) -* Add ``groupTreksAndOutdoorFilters`` to be able to group Treks and Outdoor filters on search page (#656) +* Add ``groupTreksAndOutdoorFilters`` setting to be able to group Treks and Outdoor filters on search page (#656) **🐛 Fixes**