Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import styled from 'styled-components'
import { showLegDiagram } from '../../../actions/map'
import { setViewedTrip } from '../../../actions/ui'
import TransitLegSubheader from './connected-transit-leg-subheader'
import RealtimeTimeColumn from './realtime-time-column'
import TripDetails from '../connected-trip-details'
import TripTools from '../trip-tools'

Expand Down Expand Up @@ -67,6 +68,7 @@ class ConnectedItineraryBody extends Component {
toRouteAbbreviation={noop}
TransitLegSubheader={TransitLegSubheader}
TransitLegSummary={TransitLegSummary}
TimeColumnContent={RealtimeTimeColumn}
/>
<TripDetails itinerary={itinerary} />
<TripTools itinerary={itinerary} />
Expand Down
139 changes: 139 additions & 0 deletions lib/components/narrative/line-itin/realtime-time-column.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { isTransit } from '@opentripplanner/core-utils/lib/itinerary'
import {
legType,
timeOptionsType
} from '@opentripplanner/core-utils/lib/types'
import { formatTime } from '@opentripplanner/core-utils/lib/time'
import PropTypes from 'prop-types'
import React from 'react'
import styled from 'styled-components'

const TimeText = styled.div``

const TimeStruck = styled.div`
text-decoration: line-through;
`

const TimeBlock = styled.div`
line-height: 1em;
margin-bottom: 4px;
`

const TimeColumnBase = styled.div``

const StatusText = styled.div`
color: #bbb;
font-size: 80%;
line-height: 1em;
`

const DelayText = styled.span`
display: block;
white-space: nowrap;
`

// Reusing stop viewer colors.
const TimeColumnOnTime = styled(TimeColumnBase)`
${TimeText}, ${StatusText} {
color: #5cb85c;
}
`
const TimeColumnEarly = styled(TimeColumnBase)`
${TimeText}, ${StatusText} {
color: #337ab7;
}
`
const TimeColumnLate = styled(TimeColumnBase)`
${TimeText}, ${StatusText} {
color: #d9534f;
}
`

/**
* This component displays the scheduled departure/arrival time for a leg,
* and, for transit legs, displays any delays or earliness where applicable.
*/
export default function RealtimeTimeColumn ({
isDestination,
leg,
timeOptions
}) {
const time = isDestination ? leg.endTime : leg.startTime
const formattedTime = time && formatTime(time, timeOptions)
const isTransitLeg = isTransit(leg.mode)

// For non-real-time legs, show only the scheduled time,
// except for transit legs where we add the "scheduled" text underneath.
if (!leg.realTime) {
return (
<>
<TimeText>{formattedTime}</TimeText>
{isTransitLeg && <StatusText>scheduled</StatusText>}
</>
)
}

// Delay in seconds.
const delay = isDestination ? leg.arrivalDelay : leg.departureDelay
// Time is in milliseconds.
const originalTime = time - delay * 1000
const originalFormattedTime =
originalTime && formatTime(originalTime, timeOptions)

// TODO: refine on-time thresholds.
// const isOnTime = delay >= -60 && delay <= 120;
const isOnTime = delay === 0

let statusText
let TimeColumn = TimeColumnBase
if (isOnTime) {
statusText = 'on time'
TimeColumn = TimeColumnOnTime
} else if (delay < 0) {
statusText = 'early'
TimeColumn = TimeColumnEarly
} else if (delay > 0) {
statusText = 'late'
TimeColumn = TimeColumnLate
}

// Absolute delay in rounded minutes, for display purposes.
const delayInMinutes = Math.abs(
Math.round((isDestination ? leg.arrivalDelay : leg.departureDelay) / 60)
)

let renderedTime
if (!isOnTime) {
// If the transit vehicle is not on time, strike the original scheduled time
// and display the updated time underneath.
renderedTime = (
<TimeBlock>
{!isOnTime && <TimeStruck>{originalFormattedTime}</TimeStruck>}
<TimeText>{formattedTime}</TimeText>
</TimeBlock>
)
} else {
renderedTime = <TimeText>{formattedTime}</TimeText>
}

return (
<TimeColumn>
{renderedTime}
<StatusText>
{/* Keep the '5 min' string on the same line. */}
{!isOnTime && <DelayText>{delayInMinutes} min</DelayText>}
{statusText}
</StatusText>
</TimeColumn>
)
}

RealtimeTimeColumn.propTypes = {
isDestination: PropTypes.bool.isRequired,
leg: legType.isRequired,
timeOptions: timeOptionsType
}

RealtimeTimeColumn.defaultProps = {
timeOptions: null
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@opentripplanner/geocoder": "^1.0.2",
"@opentripplanner/humanize-distance": "^0.0.22",
"@opentripplanner/icons": "^1.0.1",
"@opentripplanner/itinerary-body": "^1.0.2",
"@opentripplanner/itinerary-body": "^1.1.0",
"@opentripplanner/location-field": "^1.0.2",
"@opentripplanner/location-icon": "^1.0.0",
"@opentripplanner/park-and-ride-overlay": "^1.0.1",
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1331,12 +1331,12 @@
"@opentripplanner/core-utils" "^1.2.0"
prop-types "^15.7.2"

"@opentripplanner/itinerary-body@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-1.0.2.tgz#f280932a13723f49bac92c79feb83a088d14329b"
integrity sha512-3x8UvtkL3WmUeNTeWsWTJJtR2FqcWU42xw4833+6pMMzPoS2U4bYzSbw7ucQT64vqcoRQ0ykUjE7as32W+VgGg==
"@opentripplanner/itinerary-body@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-1.1.0.tgz#6fabc389ad25f6f14db5a5861603a127530e0d9e"
integrity sha512-svu2A0z+CnL5vrkDXuaMjJaDoSC4d52utzdGfgkXdfMaK9prO1D/6SHWiVILXmaDJExdBAoF3mxSuA7xPItTTA==
dependencies:
"@opentripplanner/core-utils" "^1.2.0"
"@opentripplanner/core-utils" "^2.1.0"
"@opentripplanner/humanize-distance" "^0.0.22"
"@opentripplanner/icons" "^1.0.0"
"@opentripplanner/location-icon" "^1.0.0"
Expand Down