mirror of
https://github.com/HSLdevcom/digitransit-ui
synced 2026-02-22 16:35:47 +01:00
Part of itinerary detail view had focus on click handler. Handler never got triggered. Only the map icons do real focus actions.
728 lines
24 KiB
JavaScript
728 lines
24 KiB
JavaScript
import cx from 'classnames';
|
|
import PropTypes from 'prop-types';
|
|
import React from 'react';
|
|
import { FormattedMessage, intlShape } from 'react-intl';
|
|
import Link from 'found/Link';
|
|
import connectToStores from 'fluxible-addons-react/connectToStores';
|
|
import LegAgencyInfo from './LegAgencyInfo';
|
|
import Icon from '../Icon';
|
|
import IntermediateLeg from './IntermediateLeg';
|
|
import ItineraryCircleLine from './ItineraryCircleLine';
|
|
import ItineraryMapAction from './ItineraryMapAction';
|
|
import PlatformNumber from '../PlatformNumber';
|
|
import ServiceAlertIcon from '../ServiceAlertIcon';
|
|
import StopCode from '../StopCode';
|
|
import {
|
|
alertSeverityCompare,
|
|
getActiveAlertSeverityLevel,
|
|
getActiveLegAlerts,
|
|
getActiveLegAlertSeverityLevel,
|
|
getMaximumAlertSeverityLevel,
|
|
hasEntitiesOfType,
|
|
legHasCancelation,
|
|
tripHasCancelationForStop,
|
|
} from '../../util/alertUtils';
|
|
import {
|
|
PREFIX_DISRUPTION,
|
|
routePagePath,
|
|
stopPagePath,
|
|
} from '../../util/path';
|
|
import { durationToString } from '../../util/timeUtils';
|
|
import { addAnalyticsEvent } from '../../util/analyticsUtils';
|
|
import {
|
|
getHeadsignFromRouteLongName,
|
|
getStopHeadsignFromStoptimes,
|
|
getZoneLabel,
|
|
showBikeBoardingNote,
|
|
showCarBoardingNote,
|
|
legTimeStr,
|
|
legTime,
|
|
isPlatformChanged,
|
|
} from '../../util/legUtils';
|
|
import { shouldShowFareInfo } from '../../util/fareUtils';
|
|
import { AlertEntityType, AlertSeverityLevelType } from '../../constants';
|
|
import { legShape, configShape } from '../../util/shapes';
|
|
import ZoneIcon from '../ZoneIcon';
|
|
import StopInfo from './StopInfo';
|
|
import InterlineInfo from './InterlineInfo';
|
|
import AlternativeLegsInfo from './AlternativeLegsInfo';
|
|
import LegInfo from './LegInfo';
|
|
import ExternalLink from '../ExternalLink';
|
|
import {
|
|
getBoardingInformationText,
|
|
getPlatformChangeLabel,
|
|
} from './BoardingInformation';
|
|
import { modeUsesTrack } from '../../util/modeUtils';
|
|
|
|
const stopCode = code => code && <StopCode code={code} />;
|
|
|
|
/**
|
|
* Some next legs might be for example 24h in the future which seems confusing.
|
|
* Only show alternatives that are less than 12h in the future.
|
|
*/
|
|
const filterNextLegs = leg => {
|
|
if (!leg.nextLegs) {
|
|
return [];
|
|
}
|
|
return leg.nextLegs.filter(
|
|
nextLeg => legTime(nextLeg.start) - legTime(leg.start) < 12 * 3600 * 1000, // 12 hours
|
|
);
|
|
};
|
|
|
|
class TransitLeg extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
showIntermediateStops:
|
|
props.interliningLegs.length >= 1
|
|
? props.interliningLegs.reduce(
|
|
(sum, leg) => sum + leg.intermediatePlaces.length,
|
|
0,
|
|
) < 2
|
|
: props.leg.intermediatePlaces.length < 2,
|
|
showAlternativeLegs: false,
|
|
};
|
|
}
|
|
|
|
isRouteConstantOperation = () =>
|
|
this.context.config.constantOperationRoutes &&
|
|
!!this.context.config.constantOperationRoutes[this.props.leg.route.gtfsId];
|
|
|
|
displayAlternativeLegs = () =>
|
|
!!this.context.config.showAlternativeLegs &&
|
|
filterNextLegs(this.props.leg).length > 0 &&
|
|
!this.isRouteConstantOperation();
|
|
|
|
toggleShowIntermediateStops = () => {
|
|
addAnalyticsEvent({
|
|
event: 'sendMatomoEvent',
|
|
category: 'Itinerary',
|
|
action: this.state.showIntermediateStops
|
|
? 'HideIntermediateStops'
|
|
: 'ShowIntermediateStops',
|
|
name: null,
|
|
});
|
|
|
|
this.setState(prevState => ({
|
|
showIntermediateStops: !prevState.showIntermediateStops,
|
|
}));
|
|
};
|
|
|
|
getZoneChange() {
|
|
const { leg } = this.props;
|
|
const startZone = leg.from.stop.zoneId;
|
|
const endZone = leg.to.stop.zoneId || leg.to.stop.zoneId;
|
|
const renderZoneIcons = () => {
|
|
return (
|
|
this.context.config.zones.itinerary &&
|
|
leg.from.stop.gtfsId &&
|
|
this.context.config.feedIds.includes(leg.from.stop.gtfsId.split(':')[0])
|
|
);
|
|
};
|
|
if (
|
|
startZone !== endZone &&
|
|
(!this.state.showIntermediateStops ||
|
|
leg.intermediatePlaces.length === 0) &&
|
|
renderZoneIcons()
|
|
) {
|
|
return (
|
|
<div className="time-column-zone-icons-container">
|
|
<ZoneIcon
|
|
zoneId={getZoneLabel(startZone, this.context.config)}
|
|
showUnknown
|
|
/>
|
|
<ZoneIcon
|
|
zoneId={getZoneLabel(endZone, this.context.config)}
|
|
className="zone-delimiter"
|
|
showUnknown
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (startZone === endZone && renderZoneIcons()) {
|
|
return (
|
|
<div className="time-column-zone-icons-container single">
|
|
<ZoneIcon
|
|
zoneId={getZoneLabel(startZone, this.context.config)}
|
|
showUnknown
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
renderIntermediate() {
|
|
const { leg, mode, interliningLegs } = this.props;
|
|
if (
|
|
(leg.intermediatePlaces.length > 0 || interliningLegs.length > 0) &&
|
|
this.state.showIntermediateStops === true
|
|
) {
|
|
const places = leg.intermediatePlaces.slice();
|
|
if (interliningLegs) {
|
|
let previousLeg = leg;
|
|
interliningLegs.forEach(iLeg => {
|
|
places.push(
|
|
{ ...previousLeg.to, arrival: previousLeg.end },
|
|
...iLeg.intermediatePlaces,
|
|
);
|
|
previousLeg = iLeg;
|
|
});
|
|
}
|
|
const stopList = places.map((place, i, array) => {
|
|
const isFirstPlace = i === 0;
|
|
const isLastPlace = i === array.length - 1;
|
|
const isCanceled = tripHasCancelationForStop(leg.trip, place.stop);
|
|
|
|
const previousZoneId =
|
|
(array[i - 1] && array[i - 1].stop.zoneId) ||
|
|
(isFirstPlace && leg.from.stop.zoneId);
|
|
const currentZoneId = place.stop.zoneId;
|
|
const nextZoneId =
|
|
(array[i + 1] && array[i + 1].stop.zoneId) ||
|
|
(isLastPlace &&
|
|
interliningLegs[interliningLegs.length - 1]?.to.stop.zoneId) ||
|
|
leg.to.stop.zoneId;
|
|
const previousZoneIdDiffers =
|
|
previousZoneId && previousZoneId !== currentZoneId;
|
|
const nextZoneIdDiffers = nextZoneId && nextZoneId !== currentZoneId;
|
|
const showCurrentZoneId = previousZoneIdDiffers || nextZoneIdDiffers;
|
|
return (
|
|
<IntermediateLeg
|
|
placesCount={places.length}
|
|
color={leg.route ? `#${leg.route.color}` : 'currentColor'}
|
|
key={place.stop.gtfsId}
|
|
gtfsId={place.stop.gtfsId}
|
|
mode={mode}
|
|
name={place.stop.name}
|
|
arrival={place.arrival}
|
|
realTime={leg.realTime}
|
|
stopCode={place.stop.code}
|
|
showZoneLimits={this.context.config.zones.itinerary}
|
|
showCurrentZoneDelimiter={previousZoneIdDiffers}
|
|
previousZoneId={
|
|
(isFirstPlace &&
|
|
previousZoneIdDiffers &&
|
|
getZoneLabel(previousZoneId, this.context.config)) ||
|
|
undefined
|
|
}
|
|
currentZoneId={
|
|
(showCurrentZoneId &&
|
|
getZoneLabel(currentZoneId, this.context.config)) ||
|
|
undefined
|
|
}
|
|
nextZoneId={
|
|
(isLastPlace &&
|
|
nextZoneIdDiffers &&
|
|
getZoneLabel(nextZoneId, this.context.config)) ||
|
|
undefined
|
|
}
|
|
isViaPoint={place.isViaPoint}
|
|
isLastPlace={isLastPlace}
|
|
isCanceled={isCanceled}
|
|
/>
|
|
);
|
|
});
|
|
return <div className="itinerary-leg-container">{stopList}</div>;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
renderMain = () => {
|
|
const {
|
|
children,
|
|
focusAction,
|
|
index,
|
|
leg,
|
|
mode,
|
|
lang,
|
|
omitDivider,
|
|
interliningLegs,
|
|
usingOwnCarWholeTrip,
|
|
} = this.props;
|
|
const { config, intl } = this.context;
|
|
const startMs = legTime(leg.start);
|
|
const time = legTimeStr(leg.start);
|
|
const modeClassName = mode.toLowerCase();
|
|
const LegRouteName = leg.from.name.concat(' - ').concat(leg.to.name);
|
|
|
|
const textVersionBeforeLink = (
|
|
<FormattedMessage
|
|
id="itinerary-details.transit-leg-part-1"
|
|
values={{
|
|
time,
|
|
realtime: leg.realTime ? intl.formatMessage({ id: 'realtime' }) : '',
|
|
}}
|
|
/>
|
|
);
|
|
const platformChanged = isPlatformChanged(leg);
|
|
const textVersionAfterLink = (
|
|
<>
|
|
<FormattedMessage
|
|
id="itinerary-details.transit-leg-part-2"
|
|
values={{
|
|
startStop: leg.from.name,
|
|
startZoneInfo: intl.formatMessage(
|
|
{ id: 'zone-info' },
|
|
{ zone: leg.from.stop.zoneId },
|
|
),
|
|
endZoneInfo: intl.formatMessage(
|
|
{ id: 'zone-info' },
|
|
{ zone: leg.to.stop.zoneId },
|
|
),
|
|
endStop: leg.to.name,
|
|
duration: durationToString(leg.duration * 1000),
|
|
trackInfo: getBoardingInformationText(leg, intl, false),
|
|
}}
|
|
/>
|
|
{platformChanged && getPlatformChangeLabel(modeUsesTrack(mode), intl)}
|
|
</>
|
|
);
|
|
|
|
const alerts = getActiveLegAlerts(leg, startMs / 1000);
|
|
const alert =
|
|
alerts && alerts.length > 0
|
|
? alerts.sort(alertSeverityCompare)[0]
|
|
: undefined;
|
|
const alertSeverityLevel = getMaximumAlertSeverityLevel(alerts);
|
|
let alertSeverityDescription = null;
|
|
if (alertSeverityLevel) {
|
|
let id;
|
|
switch (alertSeverityLevel) {
|
|
case AlertSeverityLevelType.Info:
|
|
id = 'itinerary-details.route-has-info-alert';
|
|
break;
|
|
case AlertSeverityLevelType.Warning:
|
|
id = 'itinerary-details.route-has-warning-alert';
|
|
break;
|
|
case AlertSeverityLevelType.Severe:
|
|
id = 'itinerary-details.route-has-severe-alert';
|
|
break;
|
|
case AlertSeverityLevelType.Unknown:
|
|
default:
|
|
id = 'itinerary-details.route-has-unknown-alert';
|
|
break;
|
|
}
|
|
alertSeverityDescription = <FormattedMessage id={id} />;
|
|
}
|
|
const zoneIcons = this.getZoneChange();
|
|
// Checks if route only has letters without identifying numbers and
|
|
// length doesn't fit in the tab view
|
|
const hasNoShortName =
|
|
leg.route.shortName &&
|
|
/^([^0-9]*)$/.test(leg.route.shortName) &&
|
|
leg.route.shortName.length > 3;
|
|
|
|
const headsign =
|
|
getStopHeadsignFromStoptimes(leg.from.stop, leg.trip.stoptimesForDate) ||
|
|
leg.trip.tripHeadsign ||
|
|
getHeadsignFromRouteLongName(leg.route);
|
|
|
|
let intermediateStopCount = leg.intermediatePlaces.length;
|
|
if (interliningLegs) {
|
|
intermediateStopCount = interliningLegs.reduce(
|
|
(prev, curr) => prev + curr.intermediatePlaces.length + 1,
|
|
leg.intermediatePlaces.length,
|
|
);
|
|
}
|
|
const { showBikeBoardingInformation, showCarBoardingInformation } = leg;
|
|
|
|
const createNotification = notification => {
|
|
return (
|
|
<>
|
|
<div className="disruption-icon notification-icon">
|
|
<ServiceAlertIcon
|
|
className="inline-icon"
|
|
severityLevel={AlertSeverityLevelType.Info}
|
|
/>
|
|
</div>
|
|
<div
|
|
className={cx('info-notification', {
|
|
'no-header': !notification.header,
|
|
})}
|
|
>
|
|
{notification.header && (
|
|
<h3 className="info-header">{notification.header[lang]}</h3>
|
|
)}
|
|
<div
|
|
className={cx('info-content', {
|
|
'no-header': !notification.header,
|
|
})}
|
|
>
|
|
{notification.content[lang].join(' ')}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const createNotificationWithLink = notification => {
|
|
return (
|
|
<a
|
|
href={`https://www.${notification.link?.[lang]}`}
|
|
className="disruption-link"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
{createNotification(notification)}
|
|
<Icon
|
|
img="icon_arrow-collapse--right"
|
|
className="disruption-link-arrow"
|
|
color={config.colors.primary}
|
|
/>
|
|
</a>
|
|
);
|
|
};
|
|
const routeNotifications = [];
|
|
if (
|
|
config.NODE_ENV !== 'test' &&
|
|
config.routeNotifications &&
|
|
config.routeNotifications.length > 0
|
|
) {
|
|
for (let i = 0; i < config.routeNotifications.length; i++) {
|
|
const notification = config.routeNotifications[i];
|
|
if (
|
|
(showBikeBoardingInformation &&
|
|
notification.showForBikeWithPublic &&
|
|
showBikeBoardingNote(leg, config)) ||
|
|
(showCarBoardingInformation &&
|
|
notification.showForCarWithPublic &&
|
|
showCarBoardingNote(leg, config)) ||
|
|
notification.showForRoute?.(leg.route)
|
|
) {
|
|
routeNotifications.push(
|
|
<div
|
|
className={cx('disruption', {
|
|
'no-header': !notification.header,
|
|
'no-link': !notification.link,
|
|
})}
|
|
key={`note-${index}`}
|
|
>
|
|
{notification.link
|
|
? createNotificationWithLink(notification)
|
|
: createNotification(notification)}
|
|
</div>,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div key={index} className="row itinerary-row">
|
|
<span className="sr-only">{textVersionBeforeLink}</span>
|
|
<div className="small-2 columns itinerary-time-column">
|
|
<span className="sr-only">
|
|
<FormattedMessage
|
|
id={`${this.props.mode}-with-route-number`}
|
|
values={{
|
|
routeNumber: leg.route?.shortName,
|
|
headSign: leg.trip?.tripHeadsign,
|
|
}}
|
|
defaultMessage={`${this.props.mode} {routeNumber} {headSign}`}
|
|
/>
|
|
</span>
|
|
<span aria-hidden="true">
|
|
<div className="itinerary-time-column-time">
|
|
<span className={cx({ realtime: leg.realTime })}>
|
|
<span className={cx({ canceled: legHasCancelation(leg) })}>
|
|
{time}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
{zoneIcons}
|
|
</span>
|
|
</div>
|
|
<span className="sr-only">{textVersionAfterLink}</span>
|
|
<ItineraryCircleLine
|
|
index={index}
|
|
modeClassName={modeClassName}
|
|
color={leg.route ? `#${leg.route.color}` : 'currentColor'}
|
|
renderBottomMarker={
|
|
!this.state.showIntermediateStops ||
|
|
(leg.intermediatePlaces.length === 0 && interliningLegs.length < 1)
|
|
}
|
|
/>
|
|
<div
|
|
style={{
|
|
color: leg.route ? `#${leg.route.color}` : 'currentColor',
|
|
}}
|
|
className={cx(
|
|
'small-9 columns itinerary-instruction-column',
|
|
{ first: index === 0 },
|
|
modeClassName,
|
|
)}
|
|
>
|
|
<span className="sr-only">
|
|
<FormattedMessage
|
|
id="itinerary-summary.show-on-map"
|
|
values={{ target: leg.from.name || '' }}
|
|
/>
|
|
</span>
|
|
<div
|
|
className={cx('itinerary-leg-first-row', 'transit', {
|
|
first: index === 0,
|
|
})}
|
|
>
|
|
<div className="itinerary-leg-row">
|
|
<Link
|
|
aria-label={leg.from.name?.toLowerCase()}
|
|
onClick={e => {
|
|
e.stopPropagation();
|
|
addAnalyticsEvent({
|
|
category: 'Itinerary',
|
|
action: 'OpenRouteFromItinerary',
|
|
name: mode,
|
|
});
|
|
}}
|
|
to={stopPagePath(false, leg.from.stop.gtfsId)}
|
|
>
|
|
{leg.from.name}
|
|
{leg.isViaPoint && (
|
|
<Icon
|
|
img="icon_mapMarker"
|
|
className="itinerary-mapmarker-icon"
|
|
/>
|
|
)}
|
|
<Icon
|
|
img="icon_arrow-collapse--right"
|
|
className="itinerary-arrow-icon"
|
|
color={config.colors.primary}
|
|
/>
|
|
</Link>
|
|
<ServiceAlertIcon
|
|
className="inline-icon"
|
|
severityLevel={getActiveAlertSeverityLevel(
|
|
leg.from.stop && leg.from.stop.alerts,
|
|
startMs / 1000,
|
|
)}
|
|
/>
|
|
<div className="stop-code-container">
|
|
{stopCode(leg.from.stop && leg.from.stop.code)}
|
|
<PlatformNumber
|
|
number={leg.from.stop.platformCode}
|
|
short
|
|
isRailOrSubway={
|
|
modeClassName === 'rail' || modeClassName === 'subway'
|
|
}
|
|
updated={platformChanged}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<ItineraryMapAction
|
|
target={leg.from.name || ''}
|
|
focusAction={focusAction}
|
|
/>
|
|
</div>
|
|
<LegInfo
|
|
leg={leg}
|
|
hasNoShortName={hasNoShortName}
|
|
headsign={headsign}
|
|
alertSeverityLevel={alertSeverityLevel}
|
|
isAlternativeLeg={false}
|
|
displayTime={this.displayAlternativeLegs()}
|
|
changeHash={this.props.changeHash}
|
|
tabIndex={this.props.tabIndex}
|
|
isCallAgency={mode === 'call'}
|
|
/>
|
|
|
|
{this.state.showAlternativeLegs &&
|
|
!this.isRouteConstantOperation() &&
|
|
leg.nextLegs.map(l => (
|
|
<LegInfo
|
|
key={l.route.shortName + legTime(l.start)}
|
|
leg={l}
|
|
hasNoShortName={hasNoShortName}
|
|
headsign={l.trip.tripHeadsign}
|
|
isAlternativeLeg
|
|
alertSeverityLevel={getActiveLegAlertSeverityLevel(
|
|
l,
|
|
l.start / 1000,
|
|
)}
|
|
displayTime
|
|
isCallAgency={mode === 'call'}
|
|
/>
|
|
))}
|
|
{this.displayAlternativeLegs() && (
|
|
<AlternativeLegsInfo
|
|
legs={filterNextLegs(leg)}
|
|
showAlternativeLegs={this.state.showAlternativeLegs}
|
|
toggle={() =>
|
|
this.setState(prevState => ({
|
|
...prevState,
|
|
showAlternativeLegs: !prevState.showAlternativeLegs,
|
|
}))
|
|
}
|
|
/>
|
|
)}
|
|
{(alertSeverityLevel === AlertSeverityLevelType.Warning ||
|
|
alertSeverityLevel === AlertSeverityLevelType.Severe ||
|
|
alertSeverityLevel === AlertSeverityLevelType.Unknown) && (
|
|
<div className="disruption">
|
|
<div className="disruption-link-container">
|
|
<Link
|
|
to={
|
|
(hasEntitiesOfType(alert, AlertEntityType.Route) &&
|
|
routePagePath(
|
|
leg.route.gtfsId,
|
|
PREFIX_DISRUPTION,
|
|
leg.trip.pattern.code,
|
|
)) ||
|
|
(hasEntitiesOfType(alert, AlertEntityType.Stop) &&
|
|
stopPagePath(
|
|
false,
|
|
alert.entities[0].gtfsId,
|
|
PREFIX_DISRUPTION,
|
|
))
|
|
}
|
|
className="disruption-link"
|
|
>
|
|
<div className="disruption-icon">
|
|
<ServiceAlertIcon
|
|
className="inline-icon"
|
|
severityLevel={alertSeverityLevel}
|
|
/>
|
|
</div>
|
|
<div className="description">
|
|
{config.showAlertHeader
|
|
? alert.alertHeaderText
|
|
: alert.alertDescriptionText}
|
|
</div>
|
|
<Icon
|
|
img="icon_arrow-collapse--right"
|
|
className="disruption-link-arrow"
|
|
color={config.colors.primary}
|
|
/>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{interliningLegs?.length > 0 ? (
|
|
<InterlineInfo
|
|
legs={interliningLegs}
|
|
leg={leg}
|
|
usingOwnCarWholeTrip={usingOwnCarWholeTrip}
|
|
/>
|
|
) : (
|
|
!omitDivider &&
|
|
routeNotifications.length === 0 && <div className="divider" />
|
|
)}
|
|
{routeNotifications}
|
|
<LegAgencyInfo leg={leg} />
|
|
{children}
|
|
{intermediateStopCount !== 0 && (
|
|
<div className="intermediate-stops-button-container">
|
|
{(leg.intermediatePlaces.length > 1 ||
|
|
interliningLegs.length >= 1) && (
|
|
<StopInfo
|
|
toggleFunction={this.toggleShowIntermediateStops}
|
|
leg={leg}
|
|
intermediateStopCount={intermediateStopCount}
|
|
duration={
|
|
interliningLegs.length > 0
|
|
? legTime(
|
|
interliningLegs[interliningLegs.length - 1].end,
|
|
) - legTime(leg.start)
|
|
: leg.duration * 1000
|
|
}
|
|
showIntermediateStops={this.state.showIntermediateStops}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
{leg.fare?.isUnknown &&
|
|
!config.hideUnknownFares &&
|
|
shouldShowFareInfo(config) &&
|
|
(config.modeDisclaimers?.[mode]?.[lang] ? (
|
|
<div className="disclaimer-container unknown-fare-disclaimer__leg">
|
|
<div className="description-container">
|
|
{config.modeDisclaimers[mode][lang].disclaimer}
|
|
<a
|
|
href={config.modeDisclaimers[mode][lang].link}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
{config.modeDisclaimers[mode][lang].text}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="disclaimer-container unknown-fare-disclaimer__leg">
|
|
<div className="description-container">
|
|
<span className="accent">
|
|
{`${intl.formatMessage({ id: 'pay-attention' })} `}
|
|
</span>
|
|
{intl.formatMessage({ id: 'separate-ticket-required' })}
|
|
</div>
|
|
<div className="ticket-info">
|
|
<div className="accent">{LegRouteName}</div>
|
|
{leg.fare.agency &&
|
|
!config.hideExternalOperator(leg.fare.agency) && (
|
|
<React.Fragment>
|
|
<div>{leg.fare.agency.name}</div>
|
|
{leg.fare.agency.fareUrl && (
|
|
<ExternalLink
|
|
className="agency-link"
|
|
href={leg.fare.agency.fareUrl}
|
|
>
|
|
{intl.formatMessage({ id: 'extra-info' })}
|
|
</ExternalLink>
|
|
)}
|
|
</React.Fragment>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<span className="sr-only">{alertSeverityDescription}</span>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<React.Fragment>
|
|
{this.renderMain()}
|
|
{this.renderIntermediate()}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
TransitLeg.propTypes = {
|
|
leg: legShape.isRequired,
|
|
interliningLegs: PropTypes.arrayOf(legShape),
|
|
index: PropTypes.number.isRequired,
|
|
mode: PropTypes.string.isRequired,
|
|
focusAction: PropTypes.func.isRequired,
|
|
children: PropTypes.node,
|
|
lang: PropTypes.string.isRequired,
|
|
omitDivider: PropTypes.bool,
|
|
changeHash: PropTypes.func,
|
|
tabIndex: PropTypes.number,
|
|
usingOwnCarWholeTrip: PropTypes.bool,
|
|
};
|
|
|
|
TransitLeg.defaultProps = {
|
|
omitDivider: false,
|
|
interliningLegs: [],
|
|
changeHash: undefined,
|
|
tabIndex: undefined,
|
|
children: undefined,
|
|
usingOwnCarWholeTrip: false,
|
|
};
|
|
|
|
TransitLeg.contextTypes = {
|
|
config: configShape.isRequired,
|
|
intl: intlShape.isRequired,
|
|
};
|
|
|
|
const connectedComponent = connectToStores(
|
|
TransitLeg,
|
|
['PreferencesStore'],
|
|
context => ({
|
|
lang: context.getStore('PreferencesStore').getLanguage(),
|
|
}),
|
|
);
|
|
|
|
export { connectedComponent as default, TransitLeg as Component };
|