mirror of
https://github.com/HSLdevcom/digitransit-ui
synced 2025-07-05 16:30:37 +02:00
261 lines
8.4 KiB
JavaScript
261 lines
8.4 KiB
JavaScript
import PropTypes from 'prop-types';
|
|
import React, { useState, useEffect } from 'react';
|
|
import { matchShape, routerShape } from 'found';
|
|
import { intlShape } from 'react-intl';
|
|
import { DateTime } from 'luxon';
|
|
import connectToStores from 'fluxible-addons-react/connectToStores';
|
|
import { parkShape, configShape, errorShape } from '../util/shapes';
|
|
import ParkOrStationHeader from './ParkOrStationHeader';
|
|
import Icon from './Icon';
|
|
import { PREFIX_BIKEPARK, PREFIX_CARPARK } from '../util/path';
|
|
import { DATE_FORMAT } from '../constants';
|
|
|
|
function ParkAndRideContent(
|
|
{ vehicleParking, error, currentLanguage },
|
|
{ config, intl, router, match },
|
|
) {
|
|
// throw error when relay query fails
|
|
if (error) {
|
|
throw error.message;
|
|
}
|
|
const bikePark = match.location.pathname.includes(PREFIX_BIKEPARK);
|
|
if (!vehicleParking) {
|
|
const path = bikePark ? PREFIX_BIKEPARK : PREFIX_CARPARK;
|
|
router.replace(`/${path}`);
|
|
return null;
|
|
}
|
|
const prePostFix = bikePark ? 'bike-park' : 'car-park';
|
|
const [authenticationMethods, setAuthenticationMethods] = useState([]);
|
|
const [pricingMethods, setPricingMethods] = useState([]);
|
|
const [services, setServices] = useState([]);
|
|
const [openingHours, setOpeningHours] = useState(null);
|
|
const [lang, setLang] = useState('fi');
|
|
|
|
let spacesAvailable;
|
|
let maxCapacity;
|
|
if (bikePark) {
|
|
spacesAvailable = vehicleParking.availability?.bicycleSpaces;
|
|
} else {
|
|
spacesAvailable = vehicleParking.availability?.carSpaces;
|
|
maxCapacity = vehicleParking.capacity?.carSpaces || 1;
|
|
}
|
|
|
|
const {
|
|
getAuthenticationMethods,
|
|
getPricingMethods,
|
|
getServices,
|
|
isFree,
|
|
isPaid,
|
|
getOpeningHours,
|
|
} = config.parkAndRide.pageContent.default;
|
|
|
|
useEffect(() => {
|
|
setAuthenticationMethods(getAuthenticationMethods(vehicleParking));
|
|
setPricingMethods(getPricingMethods(vehicleParking));
|
|
setServices(getServices(vehicleParking));
|
|
setOpeningHours(getOpeningHours(vehicleParking));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (lang !== currentLanguage) {
|
|
setLang(currentLanguage);
|
|
}
|
|
}, [currentLanguage]);
|
|
|
|
const getOpeningHoursAsText = () => {
|
|
const openingHoursDates = openingHours?.dates;
|
|
if (openingHoursDates) {
|
|
const filteredOpeningHours = openingHoursDates.filter(o => o.timeSpans);
|
|
const sameOpeningHoursEveryday = filteredOpeningHours.every(
|
|
openingHour =>
|
|
openingHour?.timeSpans.from ===
|
|
filteredOpeningHours[0]?.timeSpans.from &&
|
|
openingHour?.timeSpans.to === filteredOpeningHours[0]?.timeSpans.to,
|
|
);
|
|
if (
|
|
sameOpeningHoursEveryday &&
|
|
filteredOpeningHours.length === openingHoursDates.length &&
|
|
filteredOpeningHours.length
|
|
) {
|
|
const { to, from } = filteredOpeningHours[0].timeSpans;
|
|
if (to - from - 60 * 60 * 24 === 0) {
|
|
return [`24${intl.formatMessage({ id: 'hour-short' })}`];
|
|
}
|
|
const formattedFrom = DateTime.fromSeconds(from)
|
|
.toUTC()
|
|
.toFormat('HH:mm');
|
|
const formattedTo = DateTime.fromSeconds(to).toUTC().toFormat('HH:mm');
|
|
return [`${formattedFrom} - ${formattedTo}`];
|
|
}
|
|
let i = 0;
|
|
const hoursAsText = [];
|
|
const numberOfDays = filteredOpeningHours.length;
|
|
while (i < numberOfDays) {
|
|
const { date, timeSpans } = filteredOpeningHours[i];
|
|
let j = i + 1;
|
|
while (j < numberOfDays) {
|
|
if (
|
|
filteredOpeningHours[i].timeSpans.from !==
|
|
filteredOpeningHours[j].timeSpans.from ||
|
|
filteredOpeningHours[i].timeSpans.to !==
|
|
filteredOpeningHours[j].timeSpans.to
|
|
) {
|
|
break;
|
|
}
|
|
j += 1;
|
|
}
|
|
const from = DateTime.fromSeconds(timeSpans.from)
|
|
.toUTC()
|
|
.toFormat('HH:mm');
|
|
const to = DateTime.fromSeconds(timeSpans.to).toUTC().toFormat('HH:mm');
|
|
const day = DateTime.fromFormat(date, DATE_FORMAT)
|
|
.setLocale(currentLanguage)
|
|
.toFormat('ccc');
|
|
if (i === j - 1) {
|
|
hoursAsText.push(
|
|
`${day.charAt(0).toUpperCase() + day.slice(1)} ${from}-${to}`,
|
|
);
|
|
} else {
|
|
const until = openingHoursDates[j - 1].date.toLocaleString(
|
|
currentLanguage,
|
|
{
|
|
weekday: 'short',
|
|
},
|
|
);
|
|
hoursAsText.push(
|
|
`${day.charAt(0).toUpperCase() + day.slice(1)}-${
|
|
until.charAt(0).toUpperCase() + until.slice(1)
|
|
} ${from}-${to}`,
|
|
);
|
|
}
|
|
i = j;
|
|
}
|
|
return hoursAsText;
|
|
}
|
|
return [];
|
|
};
|
|
const parkIsPaid = isPaid(pricingMethods);
|
|
const parkIsFree = isFree(pricingMethods);
|
|
const { realtime } = vehicleParking;
|
|
const showOpeningHours =
|
|
Array.isArray(openingHours?.dates) && openingHours.dates.length > 0;
|
|
const showSpacesAvailable = !realtime && spacesAvailable;
|
|
|
|
return (
|
|
<div className="bike-station-page-container">
|
|
<ParkOrStationHeader
|
|
parkOrStation={vehicleParking}
|
|
parkType={bikePark ? 'bike' : 'car'}
|
|
/>
|
|
<div className="park-content-container">
|
|
<Icon img={`icon-icon_${prePostFix}`} height={2.4} width={2.4} />
|
|
<div className="park-details">
|
|
{showOpeningHours && (
|
|
<div className="park-opening-hours">
|
|
<span>{intl.formatMessage({ id: 'is-open' })}  </span>
|
|
<span>
|
|
{getOpeningHoursAsText().map((text, i) => (
|
|
// eslint-disable-next-line react/no-array-index-key
|
|
<p key={`opening-hour-${text}-${i}`}>{text}</p>
|
|
))}
|
|
</span>
|
|
</div>
|
|
)}
|
|
{realtime && (
|
|
<span>
|
|
{intl.formatMessage({ id: 'park-and-ride-availability' })}  
|
|
<p>
|
|
{spacesAvailable} / {maxCapacity}
|
|
</p>
|
|
</span>
|
|
)}
|
|
{showSpacesAvailable && (
|
|
<span>
|
|
{intl.formatMessage({ id: 'number-of-spaces' })}  
|
|
<p>{spacesAvailable}</p>
|
|
</span>
|
|
)}
|
|
{(parkIsFree || parkIsPaid) && (
|
|
<span>
|
|
{parkIsFree && intl.formatMessage({ id: 'free-of-charge' })}
|
|
{parkIsPaid && intl.formatMessage({ id: 'paid' })}
|
|
{authenticationMethods.length > 0 &&
|
|
`, ${intl.formatMessage({
|
|
id: 'access_with',
|
|
})} `}
|
|
{authenticationMethods.map(
|
|
(method, i) =>
|
|
`
|
|
${intl.formatMessage({ id: method })}
|
|
${i < authenticationMethods.length - 1 ? ' | ' : ''}
|
|
`,
|
|
)}
|
|
</span>
|
|
)}
|
|
{services.length > 0 && (
|
|
<span>
|
|
{services.map(
|
|
(service, i) =>
|
|
`
|
|
${intl.formatMessage({ id: service })}
|
|
${i < services.length - 1 ? ' | ' : ''}
|
|
`,
|
|
)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="citybike-use-disclaimer">
|
|
<h2 className="disclaimer-header">
|
|
{intl.formatMessage({ id: `${prePostFix}-disclaimer-header` })}
|
|
</h2>
|
|
<div className="disclaimer-content">
|
|
{intl.formatMessage({ id: `${prePostFix}-disclaimer` })}
|
|
</div>
|
|
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
|
{config.parkAndRide.url && (
|
|
<a
|
|
onClick={e => {
|
|
e.stopPropagation();
|
|
}}
|
|
className="external-link"
|
|
href={config.parkAndRide.url[lang]}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
{intl.formatMessage({ id: `${prePostFix}-disclaimer-link` })}{' '}
|
|
›
|
|
</a>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
ParkAndRideContent.propTypes = {
|
|
vehicleParking: parkShape,
|
|
error: errorShape,
|
|
currentLanguage: PropTypes.string.isRequired,
|
|
};
|
|
|
|
ParkAndRideContent.defaultProps = {
|
|
vehicleParking: undefined,
|
|
error: undefined,
|
|
};
|
|
|
|
ParkAndRideContent.contextTypes = {
|
|
config: configShape.isRequired,
|
|
intl: intlShape.isRequired,
|
|
router: routerShape.isRequired,
|
|
match: matchShape.isRequired,
|
|
};
|
|
|
|
const connectedComponent = connectToStores(
|
|
ParkAndRideContent,
|
|
['PreferencesStore'],
|
|
context => ({
|
|
currentLanguage: context.getStore('PreferencesStore').getLanguage(),
|
|
}),
|
|
);
|
|
|
|
export { connectedComponent as default, ParkAndRideContent as Component };
|