From a0d4f88fcdf6fd1ceef29afdb718a6882e025b75 Mon Sep 17 00:00:00 2001 From: Bastyen Date: Thu, 4 Jan 2024 14:54:34 +0100 Subject: [PATCH] add touristic events list --- src/components.d.ts | 103 ++++++++--- src/components/grw-app/grw-app.scss | 2 +- src/components/grw-app/grw-app.tsx | 78 +++++++-- src/components/grw-app/readme.md | 9 +- src/components/grw-filter/grw-filter.tsx | 17 +- src/components/grw-filters/grw-filters.scss | 3 +- src/components/grw-filters/grw-filters.tsx | 104 ++++++++++- src/components/grw-map/grw-map.scss | 12 +- src/components/grw-map/grw-map.tsx | 163 +++++++++++++++++- src/components/grw-search/grw-search.tsx | 4 +- .../grw-touristic-event-card.scss | 48 +++++- .../grw-touristic-event-card.tsx | 155 +++++++++++------ src/components/grw-touristic-event/readme.md | 18 +- .../grw-touristic-events-list.scss | 43 +++++ .../grw-touristic-events-list.tsx | 110 ++++++++++++ .../grw-touristic-events-list/readme.md | 39 +++++ .../grw-trek-card/grw-trek-card.tsx | 12 +- src/i18n/i18n.ts | 15 ++ src/store/grw-touristic-contents-provider.tsx | 1 - src/store/grw-touristic-event-provider.tsx | 2 +- src/store/grw-touristic-events-provider.tsx | 86 +++++++++ src/store/store.ts | 6 + src/types/types.ts | 6 +- src/utils/utils.ts | 80 ++++++++- 24 files changed, 995 insertions(+), 121 deletions(-) create mode 100644 src/components/grw-touristic-events-list/grw-touristic-events-list.scss create mode 100644 src/components/grw-touristic-events-list/grw-touristic-events-list.tsx create mode 100644 src/components/grw-touristic-events-list/readme.md create mode 100644 src/store/grw-touristic-events-provider.tsx diff --git a/src/components.d.ts b/src/components.d.ts index 678b182..2eb496e 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -131,27 +131,47 @@ export namespace Components { "portals": string; "structures": string; "themes": string; - "touristicContentId": string; } interface GrwTouristicEventCard { + "isInsideHorizontalList": boolean; + "isLargeView": boolean; "touristicEvent": TouristicEvent; } - interface GrwTouristicEventCardProvider { + interface GrwTouristicEventDetail { + "colorBackground": string; + "colorOnPrimaryContainer": string; + "colorOnSecondaryContainer": string; + "colorOnSurface": string; + "colorPrimaryApp": string; + "colorPrimaryContainer": string; + "colorSecondaryContainer": string; + "colorSurfaceContainerLow": string; + "isLargeView": boolean; + } + interface GrwTouristicEventProvider { "api": string; "languages": string; "portals": string; "touristicEventId": string; } - interface GrwTouristicEventDetail { - "colorBackground": string; - "colorOnPrimaryContainer": string; + interface GrwTouristicEventsList { "colorOnSecondaryContainer": string; "colorOnSurface": string; "colorPrimaryApp": string; - "colorPrimaryContainer": string; "colorSecondaryContainer": string; "colorSurfaceContainerLow": string; "isLargeView": boolean; + "resetStoreOnDisconnected": boolean; + } + interface GrwTouristicEventsProvider { + "api": string; + "cities": string; + "districts": string; + "inBbox": string; + "languages": string; + "portals": string; + "structures": string; + "themes": string; } interface GrwTrekCard { "colorOnSecondaryContainer": string; @@ -325,18 +345,30 @@ declare global { prototype: HTMLGrwTouristicEventCardElement; new (): HTMLGrwTouristicEventCardElement; }; - interface HTMLGrwTouristicEventCardProviderElement extends Components.GrwTouristicEventCardProvider, HTMLStencilElement { - } - var HTMLGrwTouristicEventCardProviderElement: { - prototype: HTMLGrwTouristicEventCardProviderElement; - new (): HTMLGrwTouristicEventCardProviderElement; - }; interface HTMLGrwTouristicEventDetailElement extends Components.GrwTouristicEventDetail, HTMLStencilElement { } var HTMLGrwTouristicEventDetailElement: { prototype: HTMLGrwTouristicEventDetailElement; new (): HTMLGrwTouristicEventDetailElement; }; + interface HTMLGrwTouristicEventProviderElement extends Components.GrwTouristicEventProvider, HTMLStencilElement { + } + var HTMLGrwTouristicEventProviderElement: { + prototype: HTMLGrwTouristicEventProviderElement; + new (): HTMLGrwTouristicEventProviderElement; + }; + interface HTMLGrwTouristicEventsListElement extends Components.GrwTouristicEventsList, HTMLStencilElement { + } + var HTMLGrwTouristicEventsListElement: { + prototype: HTMLGrwTouristicEventsListElement; + new (): HTMLGrwTouristicEventsListElement; + }; + interface HTMLGrwTouristicEventsProviderElement extends Components.GrwTouristicEventsProvider, HTMLStencilElement { + } + var HTMLGrwTouristicEventsProviderElement: { + prototype: HTMLGrwTouristicEventsProviderElement; + new (): HTMLGrwTouristicEventsProviderElement; + }; interface HTMLGrwTrekCardElement extends Components.GrwTrekCard, HTMLStencilElement { } var HTMLGrwTrekCardElement: { @@ -383,8 +415,10 @@ declare global { "grw-touristic-contents-list": HTMLGrwTouristicContentsListElement; "grw-touristic-contents-provider": HTMLGrwTouristicContentsProviderElement; "grw-touristic-event-card": HTMLGrwTouristicEventCardElement; - "grw-touristic-event-card-provider": HTMLGrwTouristicEventCardProviderElement; "grw-touristic-event-detail": HTMLGrwTouristicEventDetailElement; + "grw-touristic-event-provider": HTMLGrwTouristicEventProviderElement; + "grw-touristic-events-list": HTMLGrwTouristicEventsListElement; + "grw-touristic-events-provider": HTMLGrwTouristicEventsProviderElement; "grw-trek-card": HTMLGrwTrekCardElement; "grw-trek-detail": HTMLGrwTrekDetailElement; "grw-trek-provider": HTMLGrwTrekProviderElement; @@ -464,6 +498,7 @@ declare namespace LocalJSX { "isLargeView"?: boolean; "nameLayer"?: string; "onTouristicContentCardPress"?: (event: GrwMapCustomEvent) => void; + "onTouristicEventCardPress"?: (event: GrwMapCustomEvent) => void; "onTrekCardPress"?: (event: GrwMapCustomEvent) => void; "resetStoreOnDisconnected"?: boolean; "urlLayer"?: string; @@ -523,28 +558,50 @@ declare namespace LocalJSX { "portals"?: string; "structures"?: string; "themes"?: string; - "touristicContentId"?: string; } interface GrwTouristicEventCard { + "isInsideHorizontalList"?: boolean; + "isLargeView"?: boolean; + "onCardTouristicEventMouseLeave"?: (event: GrwTouristicEventCardCustomEvent) => void; + "onCardTouristicEventMouseOver"?: (event: GrwTouristicEventCardCustomEvent) => void; "onTouristicEventCardPress"?: (event: GrwTouristicEventCardCustomEvent) => void; "touristicEvent"?: TouristicEvent; } - interface GrwTouristicEventCardProvider { + interface GrwTouristicEventDetail { + "colorBackground"?: string; + "colorOnPrimaryContainer"?: string; + "colorOnSecondaryContainer"?: string; + "colorOnSurface"?: string; + "colorPrimaryApp"?: string; + "colorPrimaryContainer"?: string; + "colorSecondaryContainer"?: string; + "colorSurfaceContainerLow"?: string; + "isLargeView"?: boolean; + } + interface GrwTouristicEventProvider { "api"?: string; "languages"?: string; "portals"?: string; "touristicEventId"?: string; } - interface GrwTouristicEventDetail { - "colorBackground"?: string; - "colorOnPrimaryContainer"?: string; + interface GrwTouristicEventsList { "colorOnSecondaryContainer"?: string; "colorOnSurface"?: string; "colorPrimaryApp"?: string; - "colorPrimaryContainer"?: string; "colorSecondaryContainer"?: string; "colorSurfaceContainerLow"?: string; "isLargeView"?: boolean; + "resetStoreOnDisconnected"?: boolean; + } + interface GrwTouristicEventsProvider { + "api"?: string; + "cities"?: string; + "districts"?: string; + "inBbox"?: string; + "languages"?: string; + "portals"?: string; + "structures"?: string; + "themes"?: string; } interface GrwTrekCard { "colorOnSecondaryContainer"?: string; @@ -626,8 +683,10 @@ declare namespace LocalJSX { "grw-touristic-contents-list": GrwTouristicContentsList; "grw-touristic-contents-provider": GrwTouristicContentsProvider; "grw-touristic-event-card": GrwTouristicEventCard; - "grw-touristic-event-card-provider": GrwTouristicEventCardProvider; "grw-touristic-event-detail": GrwTouristicEventDetail; + "grw-touristic-event-provider": GrwTouristicEventProvider; + "grw-touristic-events-list": GrwTouristicEventsList; + "grw-touristic-events-provider": GrwTouristicEventsProvider; "grw-trek-card": GrwTrekCard; "grw-trek-detail": GrwTrekDetail; "grw-trek-provider": GrwTrekProvider; @@ -654,8 +713,10 @@ declare module "@stencil/core" { "grw-touristic-contents-list": LocalJSX.GrwTouristicContentsList & JSXBase.HTMLAttributes; "grw-touristic-contents-provider": LocalJSX.GrwTouristicContentsProvider & JSXBase.HTMLAttributes; "grw-touristic-event-card": LocalJSX.GrwTouristicEventCard & JSXBase.HTMLAttributes; - "grw-touristic-event-card-provider": LocalJSX.GrwTouristicEventCardProvider & JSXBase.HTMLAttributes; "grw-touristic-event-detail": LocalJSX.GrwTouristicEventDetail & JSXBase.HTMLAttributes; + "grw-touristic-event-provider": LocalJSX.GrwTouristicEventProvider & JSXBase.HTMLAttributes; + "grw-touristic-events-list": LocalJSX.GrwTouristicEventsList & JSXBase.HTMLAttributes; + "grw-touristic-events-provider": LocalJSX.GrwTouristicEventsProvider & JSXBase.HTMLAttributes; "grw-trek-card": LocalJSX.GrwTrekCard & JSXBase.HTMLAttributes; "grw-trek-detail": LocalJSX.GrwTrekDetail & JSXBase.HTMLAttributes; "grw-trek-provider": LocalJSX.GrwTrekProvider & JSXBase.HTMLAttributes; diff --git a/src/components/grw-app/grw-app.scss b/src/components/grw-app/grw-app.scss index 0daaa91..d683266 100644 --- a/src/components/grw-app/grw-app.scss +++ b/src/components/grw-app/grw-app.scss @@ -21,6 +21,7 @@ grw-app { .app-container { position: relative; + background-color: var(--color-background); width: 100%; height: 100%; overflow: hidden; @@ -29,7 +30,6 @@ grw-app { .header-container, .large-view-header-container { background-color: var(--color-background); - box-shadow: var(--elevation-2); height: var(--header-height); .arrow-back-container { display: flex; diff --git a/src/components/grw-app/grw-app.tsx b/src/components/grw-app/grw-app.tsx index 7374b7a..7b5eb00 100644 --- a/src/components/grw-app/grw-app.tsx +++ b/src/components/grw-app/grw-app.tsx @@ -2,7 +2,14 @@ import { Component, Host, h, Listen, State, Prop, Element, Watch } from '@stenci import { translate } from 'i18n/i18n'; import state, { onChange, reset } from 'store/store'; import { mode } from 'types/types'; -import { handleTouristicContentsFiltersAndSearch, handleTreksFiltersAndSearch, touristicContentsFilters, treksFilters } from 'utils/utils'; +import { + handleTouristicContentsFiltersAndSearch, + handleTouristicEventsFiltersAndSearch, + handleTreksFiltersAndSearch, + touristicContentsFilters, + touristicEventsFilters, + treksFilters, +} from 'utils/utils'; @Component({ tag: 'grw-app', @@ -153,6 +160,8 @@ export class GrwApp { state.mode = 'treks'; } else if (this.touristicContents) { state.mode = 'touristicContents'; + } else if (this.touristicEvents) { + state.mode = 'touristicEvents'; } const url = new URL(window.location.toString()); const trekParam = url.searchParams.get('trek'); @@ -192,6 +201,13 @@ export class GrwApp { } onDetailsClose() { + if (this.treks) { + state.mode = 'treks'; + } else if (this.touristicContents) { + state.mode = 'touristicContents'; + } else if (this.touristicEvents) { + state.mode = 'touristicEvents'; + } this.currentTouristicContentId = null; this.showTouristicContent = false; this.showTouristicContentMap = false; @@ -315,6 +331,13 @@ export class GrwApp { state.selectedActivitiesFilters = 0; state.selectedLocationFilters = 0; state.currentTouristicContents = handleTouristicContentsFiltersAndSearch(); + } else if (mode === 'touristicEvents' && state.currentTouristicEvents) { + touristicEventsFilters.forEach(filter => { + state[filter.property].forEach(currentFilter => (currentFilter.selected = false)); + }); + state.selectedActivitiesFilters = 0; + state.selectedLocationFilters = 0; + state.currentTouristicEvents = handleTouristicEventsFiltersAndSearch(); } state.mode = mode; } @@ -343,11 +366,16 @@ export class GrwApp { '--header-height': Number(this.treks) + Number(this.touristicContents) + Number(this.touristicEvents) > 1 ? '136px' : '64px', }} > - {!state.currentTreks && !state.currentTrek && !state.currentTouristicContent && !state.currentTouristicEvent && !state.currentTouristicContents && ( -
- -
- )} + {!state.currentTreks && + !state.currentTrek && + !state.currentTouristicContent && + !state.currentTouristicEvent && + !state.currentTouristicContents && + !state.currentTouristicEvents && ( +
+ +
+ )} {this.treks && state.mode === 'treks' && !this.showTrek && !this.showTouristicContent && !this.showTouristicEvent && !state.currentTreks && ( )} - {(state.currentTreks || state.currentTrek || state.touristicContents || state.currentTouristicContent || state.currentTouristicEvent) && ( + {this.touristicEvents && state.mode === 'touristicEvents' && !state.currentTouristicEvents && !this.showTouristicEvent && ( + + )} + {(state.currentTreks || state.currentTrek || state.touristicContents || state.touristicEvents || state.currentTouristicContent || state.currentTouristicEvent) && (
)} + {this.touristicContents && ( + + )}
)} {!this.showTrek && !this.showTouristicContent && !this.showTouristicEvent ? ( @@ -448,7 +493,7 @@ export class GrwApp { > {state.mode === 'treks' && this.treks && ( )} + {state.mode === 'touristicEvents' && this.touristicEvents && ( + + )}
{(this.showTrek && !state.currentTrek) || (this.showTouristicContent && !state.currentTouristicContent) || @@ -485,7 +541,7 @@ export class GrwApp { visibility: (!this.showTrekMap && this.showTrek) || this.isLargeView ? 'visible' : 'hidden', zIndex: !this.showTrekMap ? '1' : '0', }} - reset-store-on-disconnected="false" + reset-store-on-disconnected={'false'} color-primary-app={this.colorPrimaryApp} color-on-surface={this.colorOnSurface} color-primary-container={this.colorPrimaryContainer} @@ -538,7 +594,7 @@ export class GrwApp { )} grw-treks-provider grw-app --> grw-trek-provider - grw-app --> grw-touristic-content-card-provider grw-app --> grw-touristic-content-provider + grw-app --> grw-touristic-contents-provider + grw-app --> grw-touristic-events-provider grw-app --> grw-search grw-app --> grw-treks-list grw-app --> grw-touristic-contents-list + grw-app --> grw-touristic-events-list grw-app --> grw-trek-detail grw-app --> grw-touristic-content-detail grw-app --> grw-touristic-event-detail @@ -83,6 +87,7 @@ graph TD; grw-app --> grw-filters grw-treks-list --> grw-trek-card grw-touristic-contents-list --> grw-touristic-content-card + grw-touristic-events-list --> grw-touristic-event-card grw-trek-detail --> grw-trek-card grw-trek-detail --> grw-sensitive-area-detail grw-trek-detail --> grw-information-desk diff --git a/src/components/grw-filter/grw-filter.tsx b/src/components/grw-filter/grw-filter.tsx index 0ef83b1..13b836e 100644 --- a/src/components/grw-filter/grw-filter.tsx +++ b/src/components/grw-filter/grw-filter.tsx @@ -1,6 +1,13 @@ import { Component, Host, h, Prop, Listen, forceUpdate, Element } from '@stencil/core'; import state from 'store/store'; -import { treksFilters, handleTreksFiltersAndSearch, touristicContentsFilters, handleTouristicContentsFiltersAndSearch } from 'utils/utils'; +import { + treksFilters, + handleTreksFiltersAndSearch, + touristicContentsFilters, + handleTouristicContentsFiltersAndSearch, + touristicEventsFilters, + handleTouristicEventsFiltersAndSearch, +} from 'utils/utils'; import 'choices.js/public/assets/scripts/choices.min.js'; @Component({ @@ -49,6 +56,14 @@ export class GrwFilter { }); state.currentTouristicContents = handleTouristicContentsFiltersAndSearch(); + } else if (state.mode === 'touristicEvents') { + touristicEventsFilters + .filter(filter => filter.segment === this.segment) + .forEach(filter => { + state[this.segment] += state[filter.property].filter(filter => filter.selected).length; + }); + + state.currentTouristicEvents = handleTouristicEventsFiltersAndSearch(); } } diff --git a/src/components/grw-filters/grw-filters.scss b/src/components/grw-filters/grw-filters.scss index 8176d78..48b8a4e 100644 --- a/src/components/grw-filters/grw-filters.scss +++ b/src/components/grw-filters/grw-filters.scss @@ -13,7 +13,8 @@ height: 100%; z-index: 1100; .filters-treks-container, - .filters-touristic-contents-container { + .filters-touristic-contents-container, + .filters-touristic-events-container { height: 100%; } .segmented-buttons-container { diff --git a/src/components/grw-filters/grw-filters.tsx b/src/components/grw-filters/grw-filters.tsx index 22ed5c4..f77f484 100644 --- a/src/components/grw-filters/grw-filters.tsx +++ b/src/components/grw-filters/grw-filters.tsx @@ -1,7 +1,14 @@ import { Component, Event, EventEmitter, Host, Prop, State, h } from '@stencil/core'; import { translate } from 'i18n/i18n'; import state from 'store/store'; -import { treksFilters, handleTreksFiltersAndSearch, touristicContentsFilters, handleTouristicContentsFiltersAndSearch } from 'utils/utils'; +import { + treksFilters, + handleTreksFiltersAndSearch, + touristicContentsFilters, + handleTouristicContentsFiltersAndSearch, + handleTouristicEventsFiltersAndSearch, + touristicEventsFilters, +} from 'utils/utils'; @Component({ tag: 'grw-filters', @@ -39,6 +46,8 @@ export class GrwFilters { return treksFilters.filter(filter => filter.segment === segment).some(filter => state[filter.property] && state[filter.property].length > 0); } else if (state.mode === 'touristicContents') { return touristicContentsFilters.filter(filter => filter.segment === segment).some(filter => state[filter.property] && state[filter.property].length > 0); + } else if (state.mode === 'touristicEvents') { + return touristicEventsFilters.filter(filter => filter.segment === segment).some(filter => state[filter.property] && state[filter.property].length > 0); } } @@ -56,6 +65,20 @@ export class GrwFilters { this.handleFilters(); } + handleEraseTouristicEventsFilters() { + touristicEventsFilters.forEach(filter => { + state[filter.property].forEach(currentFilter => (currentFilter.selected = false)); + }); + state.selectedActivitiesFilters = 0; + state.selectedLocationFilters = 0; + state.currentTouristicEvents = handleTouristicEventsFiltersAndSearch(); + this.resetFilter.emit(); + } + + handleOkTouristicEventsFilters() { + this.handleFilters(); + } + render() { return ( @@ -301,6 +324,85 @@ export class GrwFilters { )} + {state.mode === 'touristicEvents' && ( +
+
+ {state.touristicEventsWithinBounds && ( +
{`${state.touristicEventsWithinBounds.length} ${ + state.touristicEventsWithinBounds.length > 1 ? translate[state.language].home.touristicEvents : translate[state.language].home.touristicEvent + }`}
+ )} +
+ + +
+
+
+ {this.handleSegment('selectedActivitiesFilters') && ( + + )} + {this.handleSegment('selectedLocationFilters') && ( + + )} +
+
+ {this.selectedSegment === 'selectedActivitiesFilters' && ( +
+ {state['touristicEventTypes'].length > 0 && ( +
+ +
+ )} +
+ )} + {this.selectedSegment === 'selectedLocationFilters' && ( +
+ {state['districts'].length > 0 && ( +
+ +
+ )} + {state['cities'].length > 0 && ( +
+ +
+ )} +
+ )} +
+
+ )}
this.handleFilters()} class="back-filters-container">
diff --git a/src/components/grw-map/grw-map.scss b/src/components/grw-map/grw-map.scss index 5c1e2d7..97e3ec0 100644 --- a/src/components/grw-map/grw-map.scss +++ b/src/components/grw-map/grw-map.scss @@ -81,7 +81,8 @@ } .treks-marker-cluster-group-icon, -.touristic-content-marker-cluster-group-icon { +.touristic-content-marker-cluster-group-icon, +.touristic-event-marker-cluster-group-icon { background-color: var(--color-primary-app); border-radius: 50%; display: flex; @@ -188,7 +189,8 @@ } .trek-departure-popup, -.touristic-content-coordinates-popup { +.touristic-content-coordinates-popup, +.touristic-event-coordinates-popup { position: relative; display: flex; flex-direction: column; @@ -197,14 +199,16 @@ border-top-right-radius: 8px; } .trek-name, - .touristic-content-name { + .touristic-content-name, + .touristic-event-name { font-weight: 400; font-size: 16px; line-height: 24px; padding: 8px; } .trek-button, - .touristic-content-button { + .touristic-content-button, + .touristic-event-button { margin: 0px 8px 8px 8px; user-select: none; cursor: pointer; diff --git a/src/components/grw-map/grw-map.tsx b/src/components/grw-map/grw-map.tsx index 1402090..91b0636 100644 --- a/src/components/grw-map/grw-map.tsx +++ b/src/components/grw-map/grw-map.tsx @@ -21,6 +21,7 @@ export class GrwMap { @Event() trekCardPress: EventEmitter; @Event() touristicContentCardPress: EventEmitter; + @Event() touristicEventCardPress: EventEmitter; @State() mapIsReady = false; @Prop() nameLayer: string; @@ -47,9 +48,11 @@ export class GrwMap { bounds; treksLayer: L.GeoJSON; toutisticContentsLayer: L.GeoJSON; + toutisticEventsLayer: L.GeoJSON; treksMarkerClusterGroup: MarkerClusterGroup; touristicContentsMarkerClusterGroup: MarkerClusterGroup; + touristicEventsMarkerClusterGroup: MarkerClusterGroup; currentTrekLayer: L.GeoJSON; currentStepsLayer: L.GeoJSON; selectedCurrentTrekLayer: L.GeoJSON; @@ -76,6 +79,7 @@ export class GrwMap { handleTreksWithinBoundsBind: (event: any) => void = this.handleTreksWithinBounds.bind(this); handleTouristicContentsWithinBoundsBind: (event: any) => void = this.handleTouristicContentsWithinBounds.bind(this); + handleTouristicEventsWithinBoundsBind: (event: any) => void = this.handleTouristicEventsWithinBounds.bind(this); @Listen('centerOnMap', { target: 'window' }) onCenterOnMap(event: CustomEvent<{ latitude: number; longitude: number }>) { @@ -246,6 +250,8 @@ export class GrwMap { this.addTouristicEvent(); } else if (state.touristicContents) { this.addTouristicContents(); + } else if (state.touristicEvents) { + this.addTouristicEvents(); } onChange('currentTreks', () => { @@ -292,6 +298,7 @@ export class GrwMap { onChange('currentTouristicEvent', () => { if (state.currentTouristicEvent) { this.removeTrek(); + this.removeTouristicEvents(); this.addTouristicEvent(); } else if (state.currentTrek) { this.removeTouristicEvent(); @@ -312,17 +319,35 @@ export class GrwMap { } }); + onChange('currentTouristicEvents', () => { + if (state.currentTouristicEvent) { + this.removeTouristicEvents(); + this.addTouristicEvent(); + } else if (state.currentTouristicEvents) { + this.removeTouristicEvent(); + this.addTouristicEvents(true); + } + }); + onChange('mode', () => { if (state.mode === 'treks') { if (state.treks) { this.removeTouristicContents(); + this.removeTouristicEvents(); this.addTreks(true); } } else if (state.mode === 'touristicContents') { if (state.touristicContents) { this.removeTreks(); + this.removeTouristicEvents(); this.addTouristicContents(true); } + } else if (state.mode === 'touristicEvents') { + if (state.touristicEvents) { + this.removeTreks(); + this.removeTouristicContents(); + this.addTouristicEvents(true); + } } }); } @@ -470,6 +495,17 @@ export class GrwMap { } } + handleTouristicEventsWithinBounds() { + if ( + (state.currentTouristicEvents && !state.currentMapBounds) || + (state.currentTouristicEvents && state.currentMapBounds && state.currentMapBounds.toBBoxString() !== this.map.getBounds().toBBoxString()) + ) { + state.touristicEventsWithinBounds = state.currentTouristicEvents.filter(touristicEvent => + this.map.getBounds().contains(L.latLng(touristicEvent.geometry.coordinates[1], touristicEvent.geometry.coordinates[0])), + ); + } + } + addTrek() { this.trekPopupIsOpen = false; state.selectedTrekId = null; @@ -1236,6 +1272,7 @@ export class GrwMap { id: currentTouristicContent.id, name: currentTouristicContent.name, practice: state.touristicContentCategories.find(touristicContentCategory => touristicContentCategory.id === currentTouristicContent.category).pictogram, + imgSrc: currentTouristicContent.attachments && currentTouristicContent.attachments.length > 0 && currentTouristicContent.attachments[0].thumbnail, }, }); } @@ -1267,9 +1304,9 @@ export class GrwMap { const touristicContentCoordinatesPopup = L.DomUtil.create('div'); touristicContentCoordinatesPopup.className = 'touristic-content-coordinates-popup'; if (geoJsonPoint.properties.imgSrc) { - const touristicCotentImg = L.DomUtil.create('img'); - touristicCotentImg.src = geoJsonPoint.properties.imgSrc; - touristicContentCoordinatesPopup.appendChild(touristicCotentImg); + const touristicContentImg = L.DomUtil.create('img'); + touristicContentImg.src = geoJsonPoint.properties.imgSrc; + touristicContentCoordinatesPopup.appendChild(touristicContentImg); } const touristicContentName = L.DomUtil.create('div'); touristicContentName.innerHTML = geoJsonPoint.properties.name; @@ -1467,6 +1504,126 @@ export class GrwMap { this.selectedTouristicContentLayer = null; } + addTouristicEvents(resetBounds = false) { + state.touristicEventsWithinBounds = state.currentTouristicEvents; + + const touristicEventsCurrentCoordinates = []; + + const touristicEventsFeatureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [], + }; + + if (state.currentTouristicEvents) { + for (const currentTouristicEvent of state.currentTouristicEvents) { + touristicEventsCurrentCoordinates.push(currentTouristicEvent.geometry.coordinates); + + touristicEventsFeatureCollection.features.push({ + type: 'Feature', + geometry: { type: 'Point', coordinates: currentTouristicEvent.geometry.coordinates }, + properties: { + id: currentTouristicEvent.id, + name: currentTouristicEvent.name, + type: state.touristicEventTypes.find(touristicEventType => touristicEventType.id === currentTouristicEvent.type)?.pictogram, + imgSrc: currentTouristicEvent.attachments && currentTouristicEvent.attachments.length > 0 && currentTouristicEvent.attachments[0].thumbnail, + }, + }); + } + } + + if (!this.toutisticEventsLayer) { + if ((touristicEventsCurrentCoordinates.length > 0 && !state.currentMapBounds) || resetBounds) { + this.bounds = L.latLngBounds(touristicEventsCurrentCoordinates.map(coordinate => [coordinate[1], coordinate[0]])); + } else { + if (state.currentMapBounds) { + this.bounds = state.currentMapBounds; + } + } + this.toutisticEventsLayer = L.geoJSON(touristicEventsFeatureCollection, { + pointToLayer: (geoJsonPoint, latlng) => + L.marker(latlng, { + icon: L.divIcon({ + html: geoJsonPoint.properties.type + ? `
` + : `
`, + className: 'touristic-event-marker', + iconSize: 32, + iconAnchor: [18, 0], + } as any), + autoPanOnFocus: false, + } as any), + onEachFeature: (geoJsonPoint, layer) => { + layer.once('click', () => { + const touristicEventCoordinatesPopup = L.DomUtil.create('div'); + touristicEventCoordinatesPopup.className = 'touristic-event-coordinates-popup'; + if (geoJsonPoint.properties.imgSrc) { + const touristicEventImg = L.DomUtil.create('img'); + touristicEventImg.src = geoJsonPoint.properties.imgSrc; + touristicEventCoordinatesPopup.appendChild(touristicEventImg); + } + const touristicEventName = L.DomUtil.create('div'); + touristicEventName.innerHTML = geoJsonPoint.properties.name; + touristicEventName.className = 'touristic-event-name'; + touristicEventCoordinatesPopup.appendChild(touristicEventName); + + const touristicEventButton = L.DomUtil.create('button'); + touristicEventButton.innerHTML = 'Afficher le détail'; + touristicEventButton.className = 'touristic-event-button'; + touristicEventButton.onclick = () => this.touristicEventCardPress.emit(geoJsonPoint.properties.id); + touristicEventCoordinatesPopup.appendChild(touristicEventButton); + + layer.bindPopup(touristicEventCoordinatesPopup, { interactive: true, autoPan: false, closeButton: false } as any).openPopup(); + }); + layer.on('mouseover', e => { + this.addSelectedTouristicContent(geoJsonPoint.properties.id, e.latlng); + }); + }, + }); + + this.touristicEventsMarkerClusterGroup = L.markerClusterGroup({ + showCoverageOnHover: false, + removeOutsideVisibleBounds: false, + iconCreateFunction: cluster => { + return L.divIcon({ + html: '
' + cluster.getChildCount() + '
', + className: 'touristic-event-marker-cluster-group-icon', + iconSize: 48, + iconAnchor: [24, 24], + } as any); + }, + }); + + this.touristicEventsMarkerClusterGroup.addLayer(this.toutisticEventsLayer); + this.map.addLayer(this.touristicEventsMarkerClusterGroup); + } else { + if (touristicEventsCurrentCoordinates.length > 0) { + this.bounds = L.latLngBounds(touristicEventsCurrentCoordinates.map(coordinate => [coordinate[1], coordinate[0]])); + } else { + this.map.fire('moveend'); + } + this.toutisticEventsLayer.clearLayers(); + this.toutisticEventsLayer.addData(touristicEventsFeatureCollection); + this.touristicEventsMarkerClusterGroup.clearLayers(); + this.touristicEventsMarkerClusterGroup.addLayer(this.toutisticEventsLayer); + } + + this.bounds && this.map.fitBounds(this.bounds); + + !this.mapIsReady && (this.mapIsReady = !this.mapIsReady); + + this.map.on('moveend', this.handleTouristicEventsWithinBoundsBind); + } + + removeTouristicEvents() { + if (this.toutisticEventsLayer) { + state.currentMapBounds = this.map.getBounds(); + this.map.removeLayer(this.touristicEventsMarkerClusterGroup); + this.toutisticEventsLayer = null; + this.touristicEventsMarkerClusterGroup = null; + this.map.off('moveend', this.handleTouristicEventsWithinBoundsBind); + } + } + render() { const layersImageSrc = getAssetPath(`${Build.isDev ? '/' : ''}assets/layers.svg`); const contractImageSrc = getAssetPath(`${Build.isDev ? '/' : ''}assets/contract.svg`); diff --git a/src/components/grw-search/grw-search.tsx b/src/components/grw-search/grw-search.tsx index 54666a3..83aa270 100644 --- a/src/components/grw-search/grw-search.tsx +++ b/src/components/grw-search/grw-search.tsx @@ -1,6 +1,6 @@ import { Component, Host, h } from '@stencil/core'; import state from 'store/store'; -import { handleTouristicContentsFiltersAndSearch, handleTreksFiltersAndSearch } from 'utils/utils'; +import { handleTouristicContentsFiltersAndSearch, handleTouristicEventsFiltersAndSearch, handleTreksFiltersAndSearch } from 'utils/utils'; @Component({ tag: 'grw-search', @@ -14,6 +14,8 @@ export class GrwSearch { state.currentTreks = handleTreksFiltersAndSearch(); } else if (state.mode === 'touristicContents') { state.currentTouristicContents = handleTouristicContentsFiltersAndSearch(); + } else if (state.mode === 'touristicEvents') { + state.currentTouristicEvents = handleTouristicEventsFiltersAndSearch(); } } diff --git a/src/components/grw-touristic-event/grw-touristic-event-card.scss b/src/components/grw-touristic-event/grw-touristic-event-card.scss index 1c9a82e..da12b2f 100644 --- a/src/components/grw-touristic-event/grw-touristic-event-card.scss +++ b/src/components/grw-touristic-event/grw-touristic-event-card.scss @@ -13,11 +13,6 @@ :host { display: flex; flex-direction: column; - overflow: hidden; - border: 1px solid gray; - border-radius: 12px; - background-color: var(--color-surface-container-low); - color: var(--color-on-surface); a { color: var(--color-primary-app); } @@ -132,3 +127,46 @@ } } } + +.touristic-event-card-container, +.touristic-event-card-large-view-container { + cursor: pointer; + font-family: 'Roboto'; + border-radius: 12px; + box-shadow: var(--elevation-1); + background-color: var(--color-surface-container-low); + color: var(--color-on-surface); + .image { + height: 100%; + object-fit: cover; + border-top-left-radius: 12px; + border-top-right-radius: 12px; + } +} + +.touristic-event-container { + width: 100%; + max-width: 350px; + .image { + width: 100%; + max-height: 200px; + } +} + +.touristic-event-card-large-view-container { + width: 100%; + display: flex; + flex-direction: row; + .image { + width: 250px; + } +} + +.is-inside-vertical-list { + cursor: pointer; + border: none; +} + +.selected-touristic-event-card { + box-shadow: var(--elevation-2); +} diff --git a/src/components/grw-touristic-event/grw-touristic-event-card.tsx b/src/components/grw-touristic-event/grw-touristic-event-card.tsx index 5703cc6..acc2992 100644 --- a/src/components/grw-touristic-event/grw-touristic-event-card.tsx +++ b/src/components/grw-touristic-event/grw-touristic-event-card.tsx @@ -1,4 +1,4 @@ -import { Build, Component, Host, getAssetPath, h, State, Prop, Event, EventEmitter } from '@stencil/core'; +import { Build, Component, Host, getAssetPath, h, State, Prop, Event, EventEmitter, Listen } from '@stencil/core'; import state from 'store/store'; import Swiper, { Keyboard, Navigation, Pagination } from 'swiper'; import { TouristicEvent } from 'types/types'; @@ -16,27 +16,34 @@ export class GrwTouristicEvent { nextElTouristicEventRef?: HTMLDivElement; paginationElTouristicEventRef?: HTMLDivElement; @Prop() touristicEvent: TouristicEvent; + @Prop() isLargeView = false; + @Prop() isInsideHorizontalList = false; @State() displayFullscreen = false; + @Event() cardTouristicEventMouseOver: EventEmitter; + @Event() cardTouristicEventMouseLeave: EventEmitter; + componentDidLoad() { - this.swiperTouristicEvent = new Swiper(this.swiperTouristicEventRef, { - modules: [Navigation, Pagination, Keyboard], - navigation: { - prevEl: this.prevElTouristicEventRef, - nextEl: this.nextElTouristicEventRef, - }, - pagination: { el: this.paginationElTouristicEventRef }, - allowTouchMove: false, - keyboard: false, - }); - this.swiperTouristicEventRef.onfullscreenchange = () => { - this.displayFullscreen = !this.displayFullscreen; - if (this.displayFullscreen) { - this.swiperTouristicEvent.keyboard.enable(); - } else { - this.swiperTouristicEvent.keyboard.disable(); - } - }; + if (this.swiperTouristicEventRef) { + this.swiperTouristicEvent = new Swiper(this.swiperTouristicEventRef, { + modules: [Navigation, Pagination, Keyboard], + navigation: { + prevEl: this.prevElTouristicEventRef, + nextEl: this.nextElTouristicEventRef, + }, + pagination: { el: this.paginationElTouristicEventRef }, + allowTouchMove: false, + keyboard: false, + }); + this.swiperTouristicEventRef.onfullscreenchange = () => { + this.displayFullscreen = !this.displayFullscreen; + if (this.displayFullscreen) { + this.swiperTouristicEvent.keyboard.enable(); + } else { + this.swiperTouristicEvent.keyboard.disable(); + } + }; + } } handleFullscreen() { @@ -44,49 +51,89 @@ export class GrwTouristicEvent { this.swiperTouristicEventRef.requestFullscreen(); } } + + @Listen('mouseover') + handleMouseOver() { + this.cardTouristicEventMouseOver.emit(this.touristicEvent.id); + } + + @Listen('mouseleave') + handleMouseLeave() { + this.cardTouristicEventMouseLeave.emit(); + } + render() { const defaultImageSrc = getAssetPath(`${Build.isDev ? '/' : ''}assets/default-image.svg`); - const touristicEventTypes = state.touristicEventTypes.find(touristicEventType => touristicEventType.id === this.touristicEvent.type); + const touristicEventType = state.touristicEventTypes.find(touristicEventType => touristicEventType.id === this.touristicEvent.type); return ( - -
-
(this.swiperTouristicEventRef = el)}> -
- {this.touristicEvent.attachments.length > 0 ? ( - this.touristicEvent.attachments - .filter(attachment => attachment.type === 'image') - .map(attachment => ( + +
{ + if (!this.isInsideHorizontalList) { + this.touristicEventCardPress.emit(this.touristicEvent.id); + } + }} + > +
+ {this.isInsideHorizontalList ? ( +
(this.swiperTouristicEventRef = el)}> +
+ {this.touristicEvent.attachments.length > 0 ? ( + this.touristicEvent.attachments + .filter(attachment => attachment.type === 'image') + .map(attachment => ( +
+ this.handleFullscreen()} + /> +
+ )) + ) : (
- this.handleFullscreen()} - /> +
- )) - ) : ( -
- + )}
- )} -
-
(this.paginationElTouristicEventRef = el)}>
-
(this.prevElTouristicEventRef = el)}>
-
(this.nextElTouristicEventRef = el)}>
+
(this.paginationElTouristicEventRef = el)}>
+
(this.prevElTouristicEventRef = el)}>
+
(this.nextElTouristicEventRef = el)}>
+
+ ) : this.touristicEvent.attachments.filter(attachment => attachment.type === 'image').length > 0 ? ( + attachment.type === 'image')[0].thumbnail}`} loading="lazy" /> + ) : ( +
+ )}
-
-
-
- -
{touristicEventTypes.type}
+
+
+ +
{touristicEventType.type}
+
+
{this.touristicEvent.name}
-
{this.touristicEvent.name}
-
-
- + {this.isInsideHorizontalList && ( +
+ +
+ )}
); diff --git a/src/components/grw-touristic-event/readme.md b/src/components/grw-touristic-event/readme.md index 5e92d20..7aa13ef 100644 --- a/src/components/grw-touristic-event/readme.md +++ b/src/components/grw-touristic-event/readme.md @@ -5,27 +5,33 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ---------------- | --------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `touristicEvent` | -- | | `{ id: number; name: string; attachments: Attachments; description?: string; description_teaser?: string; practical_info?: string; type: number; geometry: Point; cities?: string[]; source?: number[]; pdf?: string; contact?: string; email?: string; website?: string; }` | `undefined` | +| Property | Attribute | Description | Type | Default | +| ------------------------ | --------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `isInsideHorizontalList` | `is-inside-horizontal-list` | | `boolean` | `false` | +| `isLargeView` | `is-large-view` | | `boolean` | `false` | +| `touristicEvent` | -- | | `{ id: number; name: string; attachments: Attachments; description?: string; description_teaser?: string; practical_info?: string; type: number; geometry: Point; cities?: string[]; source?: number[]; pdf?: string; contact?: string; email?: string; website?: string; }` | `undefined` | ## Events -| Event | Description | Type | -| ------------------------- | ----------- | --------------------- | -| `touristicEventCardPress` | | `CustomEvent` | +| Event | Description | Type | +| ------------------------------ | ----------- | --------------------- | +| `cardTouristicEventMouseLeave` | | `CustomEvent` | +| `cardTouristicEventMouseOver` | | `CustomEvent` | +| `touristicEventCardPress` | | `CustomEvent` | ## Dependencies ### Used by + - [grw-touristic-events-list](../grw-touristic-events-list) - [grw-trek-detail](../grw-trek-detail) ### Graph ```mermaid graph TD; + grw-touristic-events-list --> grw-touristic-event-card grw-trek-detail --> grw-touristic-event-card style grw-touristic-event-card fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/src/components/grw-touristic-events-list/grw-touristic-events-list.scss b/src/components/grw-touristic-events-list/grw-touristic-events-list.scss new file mode 100644 index 0000000..787096e --- /dev/null +++ b/src/components/grw-touristic-events-list/grw-touristic-events-list.scss @@ -0,0 +1,43 @@ +.material-symbols { + font-family: 'Material Symbols Outlined'; +} + +.material-symbols-outlined { + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 48; +} + +:host { + font-family: 'Roboto'; + width: 100%; + height: 100%; + overflow-y: auto; + width: 100%; +} + +grw-touristic-event-card { + width: 100% !important; + margin: 0px 8px 8px 8px; +} + +.list-bottom-space { + min-height: 104px; + height: 104px; + width: 100%; +} + +.current-touristic-events-within-bounds-length { + display: flex; + align-items: center; + justify-content: space-between; + margin: 16px; + color: var(--color-on-surface); + font-size: 22px; + line-height: 28px; + font-weight: 400; +} + +.touristic-events-list-container { + display: flex; + flex-wrap: wrap; + justify-content: space-around; +} diff --git a/src/components/grw-touristic-events-list/grw-touristic-events-list.tsx b/src/components/grw-touristic-events-list/grw-touristic-events-list.tsx new file mode 100644 index 0000000..760c303 --- /dev/null +++ b/src/components/grw-touristic-events-list/grw-touristic-events-list.tsx @@ -0,0 +1,110 @@ +import { Component, Host, h, Element, State, Prop } from '@stencil/core'; +import { translate } from 'i18n/i18n'; +import state, { onChange, reset } from 'store/store'; +import { TouristicEvents } from 'types/types'; + +@Component({ + tag: 'grw-touristic-events-list', + styleUrl: 'grw-touristic-events-list.scss', + shadow: true, +}) +export class GrwTouristicContentsList { + @Element() element: HTMLElement; + @State() touristicEventsToDisplay: TouristicEvents = []; + + @Prop() colorPrimaryApp = '#6b0030'; + @Prop() colorOnSurface = '#49454e'; + @Prop() colorSecondaryContainer = '#e8def8'; + @Prop() colorOnSecondaryContainer = '#1d192b'; + @Prop() colorSurfaceContainerLow = '#f7f2fa'; + + @Prop() isLargeView = false; + @Prop() resetStoreOnDisconnected = true; + step = 10; + shouldAddInfiniteScrollEvent = true; + + handleInfiniteScrollBind: (event: any) => void = this.handleInfiniteScroll.bind(this); + + connectedCallback() { + this.handleInfiniteScrollEvent(true); + if (state.currentTouristicEvents) { + this.touristicEventsToDisplay = [...state.currentTouristicEvents.slice(0, this.step)]; + } + onChange('currentTouristicEvents', () => { + this.handleInfiniteScrollEvent(true); + this.element.scroll({ top: 0 }); + if (state.currentTouristicContents) { + this.touristicEventsToDisplay = [...state.currentTouristicEvents.slice(0, this.step)]; + } + }); + onChange('touristicEventsWithinBounds', () => { + this.handleInfiniteScrollEvent(true); + this.element.scroll({ top: 0 }); + if (state.touristicEventsWithinBounds) { + this.touristicEventsToDisplay = [...state.touristicEventsWithinBounds.slice(0, this.step)]; + } + }); + } + + handleInfiniteScroll(event: any) { + if (event.composedPath()[0].scrollTop + event.composedPath()[0].scrollHeight / 2 >= event.composedPath()[0].scrollHeight) { + if (this.touristicEventsToDisplay.length < state.touristicEventsWithinBounds.length) { + this.touristicEventsToDisplay = state.touristicEventsWithinBounds.slice( + 0, + this.touristicEventsToDisplay.length + this.step >= state.touristicEventsWithinBounds.length + ? state.touristicEventsWithinBounds.length + : this.touristicEventsToDisplay.length + this.step, + ); + } else { + this.handleInfiniteScrollEvent(false); + } + } + } + + handleInfiniteScrollEvent(shouldAddInfiniteScrollEvent: boolean) { + if (shouldAddInfiniteScrollEvent) { + if (this.shouldAddInfiniteScrollEvent) { + this.element.addEventListener('scroll', this.handleInfiniteScrollBind); + this.shouldAddInfiniteScrollEvent = !shouldAddInfiniteScrollEvent; + } + } else { + this.element.removeEventListener('scroll', this.handleInfiniteScrollBind); + this.shouldAddInfiniteScrollEvent = !shouldAddInfiniteScrollEvent; + } + } + + disconnectedCallback() { + if (this.resetStoreOnDisconnected) { + reset(); + } + this.handleInfiniteScrollEvent(false); + } + + render() { + return ( + + {state.touristicEventsWithinBounds && ( +
{`${state.touristicEventsWithinBounds.length} ${ + state.touristicEventsWithinBounds.length > 1 ? translate[state.language].home.touristicEvents : translate[state.language].home.touristicEvent + }`}
+ )} +
+ {this.touristicEventsToDisplay.map(touristicEvent => ( + + ))} +
+ {!this.isLargeView &&
} +
+ ); + } +} diff --git a/src/components/grw-touristic-events-list/readme.md b/src/components/grw-touristic-events-list/readme.md new file mode 100644 index 0000000..2ec3017 --- /dev/null +++ b/src/components/grw-touristic-events-list/readme.md @@ -0,0 +1,39 @@ +# grw-treks-list + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| --------------------------- | ------------------------------ | ----------- | --------- | ----------- | +| `colorOnSecondaryContainer` | `color-on-secondary-container` | | `string` | `'#1d192b'` | +| `colorOnSurface` | `color-on-surface` | | `string` | `'#49454e'` | +| `colorPrimaryApp` | `color-primary-app` | | `string` | `'#6b0030'` | +| `colorSecondaryContainer` | `color-secondary-container` | | `string` | `'#e8def8'` | +| `colorSurfaceContainerLow` | `color-surface-container-low` | | `string` | `'#f7f2fa'` | +| `isLargeView` | `is-large-view` | | `boolean` | `false` | +| `resetStoreOnDisconnected` | `reset-store-on-disconnected` | | `boolean` | `true` | + + +## Dependencies + +### Used by + + - [grw-app](../grw-app) + +### Depends on + +- [grw-touristic-event-card](../grw-touristic-event) + +### Graph +```mermaid +graph TD; + grw-touristic-events-list --> grw-touristic-event-card + grw-app --> grw-touristic-events-list + style grw-touristic-events-list fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/grw-trek-card/grw-trek-card.tsx b/src/components/grw-trek-card/grw-trek-card.tsx index 9b54a4c..cc3fd60 100644 --- a/src/components/grw-trek-card/grw-trek-card.tsx +++ b/src/components/grw-trek-card/grw-trek-card.tsx @@ -41,11 +41,11 @@ export class GrwTrekCard { onChange('currentTrek', () => { this.currentTrek = this.trek ? this.trek : state.currentTrek; if (this.currentTrek) { - this.difficulty = state.difficulties.find(difficulty => difficulty.id === this.currentTrek.difficulty); - this.route = state.routes.find(route => route.id === this.currentTrek.route); - this.practice = state.practices.find(practice => practice.id === this.currentTrek.practice); - this.themes = state.themes.filter(theme => this.currentTrek.themes.includes(theme.id)); - this.departureCity = state.cities.find(city => city.id === this.currentTrek.departure_city); + this.difficulty = this.currentTrek.difficulty && state.difficulties ? state.difficulties.find(difficulty => difficulty.id === this.currentTrek.difficulty) : null; + this.route = this.currentTrek.route && state.routes ? state.routes.find(route => route.id === this.currentTrek.route) : null; + this.practice = this.currentTrek.practice && state.practices ? state.practices.find(practice => practice.id === this.currentTrek.practice) : null; + this.themes = this.currentTrek.themes && state.themes ? state.themes.filter(theme => this.currentTrek.themes.includes(theme.id)) : null; + this.departureCity = this.currentTrek.departure_city && state.cities ? state.cities.find(city => city.id === this.currentTrek.departure_city) : null; } }); } @@ -95,7 +95,7 @@ export class GrwTrekCard {
{this.departureCity &&
{this.departureCity.name}
}
{this.currentTrek?.name}
- {!this.isStep && ( + {!this.isStep && this.themes && (
{this.themes.map(theme => (
{theme.label}
diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index e0f88a3..fce2183 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -42,9 +42,12 @@ interface Translation { segment: { treks: string; touristicContents: string; + touristicEvents: string; }; touristicContents: string; touristicContent: string; + touristicEvents: string; + touristicEvent: string; }; options: { presentation: string; @@ -84,6 +87,8 @@ interface Translation { website: string; category: string; services: string; + type: string; + placeholderType: string; } interface AvailableTranslations { @@ -136,9 +141,12 @@ export const translate: AvailableTranslations = { segment: { treks: 'Itinéraires', touristicContents: 'Services', + touristicEvents: 'Événements', }, touristicContents: 'Services', touristicContent: 'Service', + touristicEvents: 'Événements', + touristicEvent: 'Événement', }, options: { presentation: 'Présentation', @@ -178,6 +186,8 @@ export const translate: AvailableTranslations = { website: 'Site web', category: 'Catégorie', services: 'Services', + type: 'Types', + placeholderType: 'Sélectionner un ou plusieurs types', }, en: { filter: 'Filter', @@ -223,9 +233,12 @@ export const translate: AvailableTranslations = { segment: { treks: 'Routes', touristicContents: 'Services', + touristicEvents: 'Events', }, touristicContents: 'Services', touristicContent: 'Service', + touristicEvents: 'Events', + touristicEvent: 'Event', }, options: { presentation: 'Presentation', @@ -265,5 +278,7 @@ export const translate: AvailableTranslations = { website: 'Website', category: 'Category', services: 'Services', + type: 'Types', + placeholderType: 'Select one or more types', }, }; diff --git a/src/store/grw-touristic-contents-provider.tsx b/src/store/grw-touristic-contents-provider.tsx index b12d18e..9496a12 100644 --- a/src/store/grw-touristic-contents-provider.tsx +++ b/src/store/grw-touristic-contents-provider.tsx @@ -8,7 +8,6 @@ import state from 'store/store'; export class GrwTouristicContentsProvider { @Prop() languages = 'fr'; @Prop() api: string; - @Prop() touristicContentId: string; @Prop() inBbox: string; @Prop() cities: string; @Prop() districts: string; diff --git a/src/store/grw-touristic-event-provider.tsx b/src/store/grw-touristic-event-provider.tsx index b060957..4f926bc 100644 --- a/src/store/grw-touristic-event-provider.tsx +++ b/src/store/grw-touristic-event-provider.tsx @@ -2,7 +2,7 @@ import { Build, Component, h, Host, Prop } from '@stencil/core'; import state from 'store/store'; @Component({ - tag: 'grw-touristic-event-card-provider', + tag: 'grw-touristic-event-provider', shadow: true, }) export class GrwTouristicEventProvider { diff --git a/src/store/grw-touristic-events-provider.tsx b/src/store/grw-touristic-events-provider.tsx new file mode 100644 index 0000000..aea4249 --- /dev/null +++ b/src/store/grw-touristic-events-provider.tsx @@ -0,0 +1,86 @@ +import { Build, Component, h, Host, Prop } from '@stencil/core'; +import state from 'store/store'; + +@Component({ + tag: 'grw-touristic-events-provider', + shadow: true, +}) +export class GrwTouristicEventsProvider { + @Prop() languages = 'fr'; + @Prop() api: string; + @Prop() inBbox: string; + @Prop() cities: string; + @Prop() districts: string; + @Prop() structures: string; + @Prop() themes: string; + @Prop() portals: string; + + controller = new AbortController(); + signal = this.controller.signal; + init: RequestInit = { cache: Build.isDev ? 'force-cache' : 'default', signal: this.signal }; + + connectedCallback() { + if (!state.api) { + state.api = this.api; + state.languages = this.languages.split(','); + state.language = state.languages[0]; + } + this.handleTouristicEvents(); + } + + handleTouristicEvents() { + let touristicEventsRequest = `${state.api}touristicevent/?language=${state.language}&published=true`; + this.inBbox && (touristicEventsRequest += `&in_bbox=${this.inBbox}`); + this.cities && (touristicEventsRequest += `&cities=${this.cities}`); + this.districts && (touristicEventsRequest += `&districts=${this.districts}`); + this.structures && (touristicEventsRequest += `&structures=${this.structures}`); + this.themes && (touristicEventsRequest += `&themes=${this.themes}`); + this.portals && (touristicEventsRequest += `&portals=${this.portals}`); + + touristicEventsRequest += `&fields=id,name,attachments,category,geometry,cities,districts,type&page_size=999`; + + const requests = []; + requests.push(!state.cities ? fetch(`${state.api}city/?language=${state.language}&fields=id,name&published=true&page_size=999`, this.init) : new Response('null')); + requests.push(!state.districts ? fetch(`${state.api}district/?language=${state.language}&fields=id,name&published=true&page_size=999`, this.init) : new Response('null')), + requests.push( + !state.touristicEventTypes + ? fetch( + `${state.api}touristicevent_type/?language=${state.language}${ + this.portals ? '&portals='.concat(this.portals) : '' + }&published=true&fields=id,type,pictogram&page_size=999`, + this.init, + ) + : new Response('null'), + ); + + Promise.all([...requests, fetch(touristicEventsRequest, this.init)]) + .then(responses => Promise.all(responses.map(response => response.json()))) + .then(([cities, districts, touristicEventTypes, touristicEvents]) => { + state.trekNetworkError = false; + + if (cities) { + state.cities = cities.results; + } + if (districts) { + state.districts = districts.results; + } + if (touristicEventTypes) { + state.touristicEventTypes = touristicEventTypes.results; + } + state.touristicEvents = touristicEvents.results; + state.currentTouristicEvents = touristicEvents.results; + }) + .catch(() => { + state.trekNetworkError = true; + }); + } + + disconnectedCallback() { + this.controller.abort(); + state.trekNetworkError = false; + } + + render() { + return ; + } +} diff --git a/src/store/store.ts b/src/store/store.ts index afadcc4..345c627 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -73,9 +73,12 @@ const { state, onChange, reset } = createStore<{ touristicContentCategories: TouristicContentCategories; currentTouristicContent: TouristicContent; touristicEvents: TouristicEvents; + currentTouristicEvents: TouristicEvents; + touristicEventsWithinBounds: TouristicEvents; touristicEventTypes: TouristicEventTypes; currentTouristicEvent: TouristicEvent; selectedTouristicContentId: number; + selectedTouristicEventId: number; }>({ mode: null, api: null, @@ -121,9 +124,12 @@ const { state, onChange, reset } = createStore<{ touristicContentCategories: null, currentTouristicContent: null, touristicEvents: null, + currentTouristicEvents: null, + touristicEventsWithinBounds: null, touristicEventTypes: null, currentTouristicEvent: null, selectedTouristicContentId: null, + selectedTouristicEventId: null, }); export { onChange, reset }; diff --git a/src/types/types.ts b/src/types/types.ts index 527860b..6a399df 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -290,6 +290,10 @@ export type TouristicContentsFilters = TouristicContentsFilter[]; export type TouristicContentsFilter = { property: string; touristicContentProperty: string; touristicContentPropertyIsArray: boolean; type: string; segment: string }; +export type TouristicEventsFilters = TouristicEventsFilter[]; + +export type TouristicEventsFilter = { property: string; touristicEventProperty: string; touristicEventPropertyIsArray: boolean; type: string; segment: string }; + export type Option = { visible: boolean; width: number; @@ -308,4 +312,4 @@ export type Options = { touristicEvents: Option; }; -export type mode = 'treks' | 'touristicContents'; +export type mode = 'treks' | 'touristicContents' | 'touristicEvents'; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 441101a..0ef6441 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,5 @@ import state from 'store/store'; -import { TouristicContents, TouristicContentsFilters, TrekFilters, Treks } from 'types/types'; +import { TouristicContents, TouristicContentsFilters, TouristicEvents, TouristicEventsFilters, TrekFilters, Treks } from 'types/types'; export function formatDuration(duration: number) { let formattedDuration; @@ -42,6 +42,12 @@ export const touristicContentsFilters: TouristicContentsFilters = [ { property: 'districts', touristicContentProperty: 'districts', touristicContentPropertyIsArray: true, type: 'include', segment: 'selectedLocationFilters' }, ]; +export const touristicEventsFilters: TouristicEventsFilters = [ + { property: 'touristicEventTypes', touristicEventProperty: 'type', touristicEventPropertyIsArray: false, type: 'include', segment: 'selectedActivitiesFilters' }, + { property: 'cities', touristicEventProperty: 'cities', touristicEventPropertyIsArray: true, type: 'include', segment: 'selectedLocationFilters' }, + { property: 'districts', touristicEventProperty: 'districts', touristicEventPropertyIsArray: true, type: 'include', segment: 'selectedLocationFilters' }, +]; + export function handleTreksFiltersAndSearch(): Treks { let isUsingFilter = false; let filtersTreks = []; @@ -172,3 +178,75 @@ export function handleTouristicContentsFiltersAndSearch(): TouristicContents { ? searchTouristicContents.filter(currentTouristicContents => currentTouristicContents.name.toLowerCase().includes(state.searchValue.toLowerCase())) : searchTouristicContents; } + +export function handleTouristicEventsFiltersAndSearch(): TouristicEvents { + let isUsingFilter = false; + let filtersTouristicEvents = []; + for (const filter of touristicEventsFilters) { + const currentFiltersId: number[] = state[filter.property].filter(currentFilter => currentFilter.selected).map(currentFilter => currentFilter.id); + + if (currentFiltersId.length > 0) { + if (filtersTouristicEvents.length > 0) { + if (filter.type === 'include') { + if (filter.touristicEventPropertyIsArray) { + filtersTouristicEvents = [ + ...filtersTouristicEvents.filter(touristicContent => + touristicContent[filter.touristicEventProperty].some(touristicContentProperty => currentFiltersId.includes(touristicContentProperty)), + ), + ]; + } else { + filtersTouristicEvents = [...filtersTouristicEvents.filter(touristicContent => currentFiltersId.includes(touristicContent[filter.touristicEventProperty]))]; + } + } else if (filter.type === 'interval') { + filtersTouristicEvents = [ + ...filtersTouristicEvents.filter(touristicContent => { + for (const currentFilterId of currentFiltersId) { + const currentFilter = state[filter.property].find(property => property.id === currentFilterId); + if (touristicContent[filter.touristicEventProperty] >= currentFilter.minValue && touristicContent[filter.touristicEventProperty] <= currentFilter.maxValue) { + return true; + } + } + return false; + }), + ]; + } + } else { + if (!isUsingFilter) { + isUsingFilter = true; + } + if (filter.type === 'include') { + if (filter.touristicEventPropertyIsArray) { + filtersTouristicEvents = [ + ...state.touristicContents.filter(touristicContent => + touristicContent[filter.touristicEventProperty].some(touristicContentProperty => currentFiltersId.includes(touristicContentProperty)), + ), + ]; + } else { + filtersTouristicEvents = [...state.touristicEvents.filter(touristicContent => currentFiltersId.includes(touristicContent[filter.touristicEventProperty]))]; + } + } else if (filter.type === 'interval') { + let minValue: number; + let maxValue: number; + for (const currentFilterId of currentFiltersId) { + const currentFilter = state[filter.property].find(property => property.id === currentFilterId); + if (isNaN(minValue) || currentFilter.minValue < minValue) { + minValue = currentFilter.minValue; + } + if (isNaN(maxValue) || currentFilter.maxValue > maxValue) { + maxValue = currentFilter.maxValue; + } + } + filtersTouristicEvents = [ + ...state.touristicEvents.filter( + touristicEvent => touristicEvent[filter.touristicEventProperty] >= minValue && touristicEvent[filter.touristicEventProperty] <= maxValue, + ), + ]; + } + } + } + } + const searchTouristicEvents = isUsingFilter ? filtersTouristicEvents : state.touristicEvents; + return Boolean(state.searchValue) + ? searchTouristicEvents.filter(currentTouristicEvent => currentTouristicEvent.name.toLowerCase().includes(state.searchValue.toLowerCase())) + : searchTouristicEvents; +}