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

feat: realtime traveller capacity #4871

Merged
merged 11 commits into from
Nov 6, 2023
Merged
161 changes: 161 additions & 0 deletions app/component/CapacityModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Icon from './Icon';

const CapacityModal = ({ config }) => {
return (
<div className="capacity-information-modal">
<section>
<h2 className="capacity-heading">
<FormattedMessage
id="capacity-modal.heading"
defaultMessage="Is there room in the vehicle?"
/>
</h2>
<p className="capacity-text">
<FormattedMessage
id="capacity-modal.subheading"
defaultMessage="Real-time capacity information is available for some vehicles"
/>
</p>
</section>
<section>
<h3 className="explanations-heading">
<FormattedMessage
id="capacity-modal.legend"
defaultMessage="Legend"
/>
</h3>
</section>
<section>
<div className="capacity-info-row">
<div className="icon" style={{ color: config.colors.primary }}>
<Icon
img="icon-icon_MANY_SEATS_AVAILABLE"
width="1.5"
height="1.5"
/>
</div>
<h4 className="info-heading">
<FormattedMessage
id="capacity-modal.many-seats-available-heading"
defaultMessage="Not crowded"
/>
</h4>
</div>
<p className="capacity-info-explanation">
<FormattedMessage
id="capacity-modal.many-seats-available-body"
defaultMessage="Plenty of seats available"
/>
</p>
</section>
<section>
<div className="capacity-info-row">
<div className="icon">
<Icon
img="icon-icon_FEW_SEATS_AVAILABLE"
width="1.5"
height="1.5"
color={config.colors.primary}
/>
</div>
<h4 className="info-heading">
<FormattedMessage
id="capacity-modal.few-seats-available-heading"
defaultMessage="Not too crowded"
/>
</h4>
</div>
<p className="capacity-info-explanation">
<FormattedMessage
id="capacity-modal.few-seats-available-body"
defaultMessage="Some seats available"
/>
</p>
</section>
<section>
<div className="capacity-info-row">
<div className="icon">
<Icon
img="icon-icon_STANDING_ROOM_ONLY"
width="1.5"
height="1.5"
color={config.colors.primary}
/>
</div>
<h4 className="info-heading">
<FormattedMessage
id="capacity-modal.standing-room-only-heading"
defaultMessage="Nearly full"
/>
</h4>
</div>
<p className="capacity-info-explanation">
<FormattedMessage
id="capacity-modal.standing-room-only-body"
defaultMessage="Only a few seats and a little standing room available"
/>
</p>
</section>
<section>
<div className="capacity-info-row">
<div className="icon">
<Icon
img="icon-icon_CRUSHED_STANDING_ROOM_ONLY"
width="1.5"
height="1.5"
color={config.colors.primary}
/>
</div>
<h4 className="info-heading">
<FormattedMessage
id="capacity-modal.crushed-standing-room-only-heading"
defaultMessage="Very crowded"
/>
</h4>
</div>
<p className="capacity-info-explanation">
<FormattedMessage
id="capacity-modal.crushed-standing-room-only-body"
defaultMessage="Only a little standing room available"
/>
</p>
</section>
<section>
<div className="capacity-info-row">
<div className="icon">
<Icon
img="icon-icon_FULL"
width="1.5"
height="1.5"
color={config.colors.primary}
/>
</div>
<h4 className="info-heading">
<FormattedMessage
id="capacity-modal.full-capacity-heading"
defaultMessage="Full"
/>
</h4>
</div>
<p className="capacity-info-explanation">
<FormattedMessage
id="capacity-modal.full-capacity-body"
defaultMessage="No seats or standing room available"
/>
</p>
</section>
</div>
);
};

CapacityModal.propTypes = {
config: PropTypes.object.isRequired,
};

CapacityModal.defaultProps = {};

export default CapacityModal;
37 changes: 36 additions & 1 deletion app/component/DepartureRow.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
Expand All @@ -16,14 +17,22 @@ import Icon from './Icon';
import { addAnalyticsEvent } from '../util/analyticsUtils';
import { PREFIX_ROUTES, PREFIX_STOPS } from '../util/path';
import { getRouteMode } from '../util/modeUtils';
import { getCapacity } from '../util/occupancyUtil';

const getMostSevereAlert = route => {
const alerts = [...getAlertsForObject(route)];
return alerts.sort(alertSeverityCompare)[0];
};

const DepartureRow = (
{ departure, departureTime, showPlatformCode, canceled, ...props },
{
departure,
departureTime,
showPlatformCode,
canceled,
onCapacityClick,
...props
},
{ config, intl },
) => {
const { trip, trip: { route } = {} } = departure;
Expand Down Expand Up @@ -117,6 +126,12 @@ const DepartureRow = (
);
};

const capacity = getCapacity(
config,
trip?.occupancy?.occupancyStatus,
departureTime * 1000,
);

return (
<tr
className={cx(
Expand Down Expand Up @@ -215,6 +230,25 @@ const DepartureRow = (
)}
</td>
)}
{capacity && (
// Use inline styles here for simplicity, some overrides make it impossible via the SASS-file
<td
className="capacity-cell"
style={{ marginRight: '8px', color: config.colors.primary }}
>
<span
className="capacity-icon-container"
onClick={() => onCapacityClick()}
>
<Icon
width="1.5"
height="1.5"
img={`icon-icon_${capacity}`}
color={config.colors.primary}
/>
</span>
</td>
)}
</tr>
);
};
Expand All @@ -225,6 +259,7 @@ DepartureRow.propTypes = {
showPlatformCode: PropTypes.bool,
canceled: PropTypes.bool,
className: PropTypes.string,
onCapacityClick: PropTypes.func.isRequired,
};

DepartureRow.contextTypes = {
Expand Down
14 changes: 14 additions & 0 deletions app/component/ItineraryLegs.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class ItineraryLegs extends React.Component {
toggleCanceledLegsBanner: PropTypes.func.isRequired,
waitThreshold: PropTypes.number.isRequired,
focusToLeg: PropTypes.func,
changeHash: PropTypes.func,
tabIndex: PropTypes.number,
};

static defaultProps = {
Expand Down Expand Up @@ -230,6 +232,8 @@ class ItineraryLegs extends React.Component {
leg={leg}
interliningLegs={interliningLegs}
focusAction={this.focus(leg.from)}
changeHash={this.props.changeHash}
tabIndex={this.props.tabIndex}
/>,
);
} else if (leg.mode === 'TRAM' && !leg.interlineWithPreviousLeg) {
Expand All @@ -239,6 +243,8 @@ class ItineraryLegs extends React.Component {
leg={leg}
interliningLegs={interliningLegs}
focusAction={this.focus(leg.from)}
changeHash={this.props.changeHash}
tabIndex={this.props.tabIndex}
/>,
);
} else if (leg.mode === 'FERRY' && !leg.interlineWithPreviousLeg) {
Expand All @@ -248,6 +254,8 @@ class ItineraryLegs extends React.Component {
leg={leg}
interliningLegs={interliningLegs}
focusAction={this.focus(leg.from)}
changeHash={this.props.changeHash}
tabIndex={this.props.tabIndex}
/>,
);
} else if (leg.mode === 'FUNICULAR' && !leg.interlineWithPreviousLeg) {
Expand All @@ -257,6 +265,8 @@ class ItineraryLegs extends React.Component {
leg={leg}
interliningLegs={interliningLegs}
focusAction={this.focus(leg.from)}
changeHash={this.props.changeHash}
tabIndex={this.props.tabIndex}
/>,
);
} else if (leg.mode === 'RAIL' && !leg.interlineWithPreviousLeg) {
Expand All @@ -266,6 +276,8 @@ class ItineraryLegs extends React.Component {
leg={leg}
interliningLegs={interliningLegs}
focusAction={this.focus(leg.from)}
changeHash={this.props.changeHash}
tabIndex={this.props.tabIndex}
/>,
);
} else if (leg.mode === 'SUBWAY' && !leg.interlineWithPreviousLeg) {
Expand All @@ -275,6 +287,8 @@ class ItineraryLegs extends React.Component {
leg={leg}
interliningLegs={interliningLegs}
focusAction={this.focus(leg.from)}
changeHash={this.props.changeHash}
tabIndex={this.props.tabIndex}
/>,
);
} else if (leg.mode === 'AIRPLANE') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ const containerComponent = createFragmentContainer(
}
pickupType
}
occupancy {
occupancyStatus
}
}
from {
name
Expand Down
10 changes: 9 additions & 1 deletion app/component/ItineraryTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class ItineraryTab extends React.Component {
currentTime: PropTypes.number.isRequired,
hideTitle: PropTypes.bool,
currentLanguage: PropTypes.string,
changeHash: PropTypes.func,
};

static defaultProps = {
Expand Down Expand Up @@ -348,6 +349,8 @@ class ItineraryTab extends React.Component {
itinerary={itinerary}
focusToPoint={this.handleFocus}
focusToLeg={this.props.focusToLeg}
changeHash={this.props.changeHash}
tabIndex={suggestionIndex - 1}
/>
{config.showRouteInformation && <RouteInformation />}
</div>
Expand Down Expand Up @@ -437,7 +440,9 @@ const withRelay = createFragmentContainer(
pattern {
code
}

occupancy {
occupancyStatus
}
gtfsId
}
realTime
Expand Down Expand Up @@ -590,6 +595,9 @@ const withRelay = createFragmentContainer(
gtfsId
}
}
occupancy {
occupancyStatus
}
}
}
}
Expand Down
Loading
Loading