Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customization of result card #1018

Merged
merged 10 commits into from
Jan 25, 2024
13 changes: 13 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,19 @@ NB: For "report" and "reservationWidget" sections with `anchors` set to `true`,

- `zoomAvailableOffline` allows you to define the zoom modes allowed in offline mode. This allows you to control the amount of disk space required when caching. Default `[13,14,15]`

- `resultCard.json` to customize the elements to be displayed on featured cards that link to a details page (only trek cards for now).

- You can display/hide the `location` and `themes` by defining a `display` key.
- You can define an array of keywords in `informations` to display them (their order in the array matters). The keywords are as follows:
- `'difficulty'`,
- `'duration'`,
- `'distance'`,
- `'positiveElevation'`,
- `'negativeElevation'`,
- `'courseType'`,
- `'networks'`,
Default value is `"informations": ["difficulty", "duration", "distance", "positiveElevation"]`. See https://github.com/GeotrekCE/Geotrek-rando-v3/blob/main/frontend/config/resultCard.json.

- `redirects.json` to define URL rewriting for your instance. For example, you can use this customization to redirect old URL style (Geotrek-rando V2) to the new URL style (Geotrek-rando V3) or to redirect old URL to a new URL after changing the name of a hike in the backend.

- In `rules`, you can define all the rules needed to redirect clients
Expand Down
11 changes: 11 additions & 0 deletions frontend/config/resultCard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"trek": {
"location": {
"display": true
},
"themes": {
"display": true
},
"informations": ["difficulty", "duration", "distance", "positiveElevation"]
}
}
11 changes: 11 additions & 0 deletions frontend/customization/config/resultCard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"trek": {
"location": {
"display": true
},
"themes": {
"display": true
},
"informations": ["difficulty", "duration", "distance", "positiveElevation"]
}
}
14 changes: 14 additions & 0 deletions frontend/jestAfterEnv.setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,20 @@ setConfig({
touristicEvent: []
},
},
resultCard: {
trek: {
location: {
display: true,
},
themes: {
display: true,
},
labels: {
display: true,
},
informations: ['difficulty', 'duration', 'distance', 'positiveElevation'],
},
},
detailsSectionHtml: {
forecastWidget: { default: '<iframe\n id="widget_autocomplete_preview"\n className="w-full"\n height="150"\n src="https://meteofrance.com/widget/prevision/{{ cityCode }}0"\n></iframe>\n' }
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/pages/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export const SearchUI: React.FC<Props> = ({ language }) => {
searchResult.id,
searchResult.name,
)}
className="my-4 desktop:my-6 desktop:mx-1 desktop:max-h-50" // Height is limited in desktop to restrain vertical images ; not limiting with short text & informations
className="my-4 desktop:my-6 desktop:mx-1"
titleTag="h2"
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const mockTrekResult: TrekResult = {
{ label: 'duration', value: '7h' },

{ label: 'distance', value: '15,2km' },
{ label: 'elevation', value: '+1457m' },
{ label: 'positiveElevation', value: '+1457m' },
],
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { Height } from 'components/Icons/Height';
import { TrendingUp } from 'components/Icons/TrendingUp';
import { RemoteIconInformation } from 'components/Information';
import { LocalIconInformation } from 'components/Information/LocalIconInformation';
import { Network } from 'modules/networks/interface';
import {
InformationCard,
InformationCardArray,
InformationCardLabelValues,
InformationCardTuple,
} from 'modules/results/interface';
Expand All @@ -21,6 +23,10 @@ const isInformationCardLabeLValues = (
information: InformationCard,
): information is InformationCardLabelValues => information.label === 'types';

const isInformationCardArray = (
information: InformationCard,
): information is InformationCardArray => information.label === 'networks';

const getInformationItemProps = (information: InformationCard, intl: IntlShape) => {
const { label, pictogramUri, value } = information;
if (label === 'difficulty') {
Expand All @@ -41,12 +47,21 @@ const getInformationItemProps = (information: InformationCard, intl: IntlShape)
children: <>{value}</>,
};
}
if (label === 'elevation') {
if (label === 'positiveElevation') {
return {
icon: TrendingUp,
children: <>{value}</>,
};
}
if (label === 'negativeElevation') {
return {
icon: TrendingUp,
iconProps: {
className: '-scale-y-100',
},
children: <>{value}</>,
};
}
if (label === 'maxElevation') {
return {
icon: Altitude,
Expand All @@ -59,6 +74,32 @@ const getInformationItemProps = (information: InformationCard, intl: IntlShape)
children: <>{value}</>,
};
}
if (label === 'courseType') {
return {
icon: pictogramUri,
children: <>{value}</>,
};
}
if (label === 'networks' && isInformationCardArray(information)) {
return {
icon: null,
children: (
<>
{Array.isArray(value) && value.length > 0 && (
<ul className="flex gap-2">
{(value as Network[]).map(item => (
<li key={item.label}>
<RemoteIconInformation iconUri={item.pictogramUri}>
{item.label}
</RemoteIconInformation>
</li>
))}
</ul>
)}
</>
),
};
}
if (label === 'types' && isInformationCardLabeLValues(information)) {
return {
icon: null,
Expand Down Expand Up @@ -112,12 +153,12 @@ const getInformationItemProps = (information: InformationCard, intl: IntlShape)

export const InformationCardItem: React.FC<InformationCard> = item => {
const intl = useIntl();
const { icon, children } = getInformationItemProps(item, intl);
const { icon, ...rest } = getInformationItemProps(item, intl);
if (!icon) {
return <>{children}</>;
return <>{rest.children}</>;
}
if (typeof icon === 'string') {
return <RemoteIconInformation iconUri={icon}>{children}</RemoteIconInformation>;
return <RemoteIconInformation iconUri={icon} {...rest} />;
}
return <LocalIconInformation icon={icon}>{children}</LocalIconInformation>;
return <LocalIconInformation icon={icon} {...rest} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('Results Card', () => {
},
{ label: 'duration', value: '2h' },
{ label: 'distance', value: '5km' },
{ label: 'elevation', value: '+360m' },
{ label: 'positiveElevation', value: '+360m' },
]}
redirectionUrl={urlToTest}
/>
Expand Down
30 changes: 26 additions & 4 deletions frontend/src/modules/activitySuggestions/connector.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import getNextConfig from 'next/config';
import { getActivities } from 'modules/activities/connector';
import { getDifficulties } from 'modules/filters/difficulties';
import { getOutdoorPractices } from 'modules/outdoorPractice/connector';
Expand All @@ -6,7 +7,7 @@ import { fetchOutdoorSiteDetails } from 'modules/outdoorSite/api';
import { RawOutdoorSiteDetails } from 'modules/outdoorSite/interface';
import { adaptTrekResultList } from 'modules/results/adapter';
import { fetchTrekResult } from 'modules/results/api';
import { InformationCardTuple, RawTrekResult } from 'modules/results/interface';
import { InformationCardTuple } from 'modules/results/interface';
import { adaptTouristicContentResult } from 'modules/touristicContent/adapter';
import { fetchTouristicContentDetails } from 'modules/touristicContent/api';
import { RawTouristicContentDetails } from 'modules/touristicContent/interface';
Expand All @@ -17,8 +18,18 @@ import { RawTouristicEventDetails } from 'modules/touristicEvent/interface';
import { getTouristicEventTypes } from 'modules/touristicEventType/connector';
import { ONE_DAY } from 'services/constants/staleTime';
import { CommonDictionaries } from 'modules/dictionaries/interface';
import { getCourseType } from 'modules/filters/courseType/connector';
import { getNetworks } from 'modules/networks/connector';
import { Suggestion } from '../home/interface';
import { ActivitySuggestion } from './interface';
const {
publicRuntimeConfig: {
resultCard: {
trek: { informations = [] },
},
},
} = getNextConfig();

export const getActivitySuggestions = async (
suggestions: Suggestion[],
language: string,
Expand Down Expand Up @@ -48,17 +59,28 @@ export const getActivitySuggestions = async (

if (type === 'trek' && 'ids' in suggestion) {
const treks = await Promise.all(
suggestion.ids.map(
id => fetchTrekResult({ language }, id).catch(() => null) as Promise<RawTrekResult>,
),
suggestion.ids.map(async id => {
const trek = await fetchTrekResult({ language }, id).catch(() => null);
if (!trek) {
return {};
}
if (!informations.includes('courseType')) {
return trek;
}
const courseType = await getCourseType(trek.route, language);
return { ...trek, courseType };
}),
);

const networks = informations.includes('networks') ? await getNetworks(language) : {};
return {
...props,
results: adaptTrekResultList({
resultsList: treks.filter(Boolean),
difficulties,
themes,
activities,
networks,
cityDictionnary: cities,
}),
};
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/modules/details/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const getDetails = async (
displayRelatedSignages === true ? getSignage(language, id, 'TREK') : null,
displayRelatedServices === true ? getService(language, id, 'TREK') : null,
displayRelatedInfrastructures === true ? getInfrastructure(language, id, 'TREK') : null,
getTrekResultsById(rawDetails.properties.children, language, commonDictionaries),
getTrekResultsById(rawDetails.properties.children, language, networks, commonDictionaries),
getGlobalConfig().enableSensitiveAreas && displayRelatedSensitiveAreas === true
? getSensitiveAreas('trek', rawDetails.properties.id, language)
: [],
Expand Down
42 changes: 37 additions & 5 deletions frontend/src/modules/results/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import getNextConfig from 'next/config';
import { ActivityChoices } from 'modules/activities/interface';
import { CityDictionnary } from 'modules/city/interface';
import { DifficultyChoices } from 'modules/filters/difficulties/interface';
import { Choices } from 'modules/filters/interface';
import { getThumbnails } from 'modules/utils/adapter';
import { formatHours } from 'modules/utils/time';
import { RawTrekResult, TrekResult } from './interface';
import { NetworkDictionnary } from 'modules/networks/interface';
import { InformationCardArray, RawTrekResult, TrekResult } from './interface';
import { formatDistance } from './utils';

const {
publicRuntimeConfig: {
resultCard: {
trek: {
location,
themes: { display: displayThemes },
informations = [],
},
},
},
} = getNextConfig();

export const dataUnits = {
distance: 'm',
time: 'h',
Expand All @@ -33,19 +47,22 @@ export const adaptTrekResultList = ({
themes,
activities,
cityDictionnary,
networks,
}: {
resultsList: Partial<RawTrekResult>[];
difficulties: DifficultyChoices;
themes: Choices;
activities: ActivityChoices;
cityDictionnary: CityDictionnary;
networks: NetworkDictionnary;
}): TrekResult[] =>
resultsList.filter(isRawTrekResultComplete).map(rawResult => ({
type: 'TREK',
id: `${rawResult.id}`,
place: cityDictionnary[rawResult.departure_city]?.name ?? null,
place: (location.display === true && cityDictionnary[rawResult.departure_city]?.name) || null,
name: rawResult.name,
tags: rawResult.themes.map(themeId => themes[themeId]?.label || ''),
tags:
displayThemes === true ? rawResult.themes.map(themeId => themes[themeId]?.label || '') : [],
attachments: getThumbnails(rawResult.attachments),
category: activities[rawResult.practice] ?? null,
informations: [
Expand All @@ -64,9 +81,22 @@ export const adaptTrekResultList = ({
value: `${formatDistance(rawResult.length_2d)}`,
},
{
label: 'elevation',
label: 'positiveElevation',
value: `+${rawResult.ascent}${dataUnits.distance}`,
},
{
label: 'negativeElevation',
value: `${rawResult.descent}${dataUnits.distance}`,
},
{
label: 'courseType',
value: rawResult.courseType?.label ?? '',
pictogramUri: rawResult.courseType?.pictogramUri ?? '',
},
{
label: 'networks',
value: rawResult.networks.map(networkId => networks[networkId]),
} as InformationCardArray,
// we disable this button because the booking behaviour is not implemented yet
// {
// label: 'reservationSystem',
Expand All @@ -75,5 +105,7 @@ export const adaptTrekResultList = ({
// ? `${rawResult.reservation_system}`
// : null,
// },
].filter(item => item.value.length > 0),
]
.filter(item => informations.includes(item.label) && item.value.length > 0)
.sort((a, b) => informations.indexOf(a.label) - informations.indexOf(b.label)),
}));
2 changes: 1 addition & 1 deletion frontend/src/modules/results/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RawTrekResult } from './interface';

const fieldsParams = {
fields:
'id,departure,name,themes,duration,length_2d,ascent,difficulty,reservation_system,attachments,practice,departure_city',
'id,departure,name,themes,duration,length_2d,ascent,descent,difficulty,reservation_system,attachments,practice,departure_city,route,networks',
};

export const fetchTrekResults = (
Expand Down
Loading
Loading