-
Notifications
You must be signed in to change notification settings - Fork 53
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
Calltaker: Print group itineraries #398
Changes from 11 commits
d24513f
73bfdbe
da26d25
05c0ba2
a3f2599
4339459
9e3a9ea
a266e27
134ab20
e5ace26
63bb394
db60c02
6ef72b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import PrintableItinerary from '@opentripplanner/printable-itinerary' | ||
import React, { Component } from 'react' | ||
import { Button } from 'react-bootstrap' | ||
import { connect } from 'react-redux' | ||
|
||
import * as callTakerActions from '../../actions/call-taker' | ||
import * as fieldTripActions from '../../actions/field-trip' | ||
import { getTripFromRequest, lzwDecode } from '../../util/call-taker' | ||
import { ComponentContext } from '../../util/contexts' | ||
import { addPrintViewClassToRootHtml, clearClassFromRootHtml } from '../../util/print' | ||
|
||
import { | ||
Header, | ||
ItineraryBody, | ||
ItineraryContainer, | ||
PrintLayout, | ||
TripBody, | ||
TripContainer, | ||
TripInfoList, | ||
TripSummary, | ||
TripTitle, | ||
Val | ||
} from './print-styled' | ||
|
||
/** | ||
* Component that renders the print version of field trip itineraries. | ||
*/ | ||
class PrintFieldTripLayout extends Component { | ||
static contextType = ComponentContext | ||
|
||
_print = () => { | ||
window.print() | ||
} | ||
|
||
componentDidMount () { | ||
const { initializeModules } = this.props | ||
// Add print-view class to html tag to ensure that iOS scroll fix only applies | ||
// to non-print views. | ||
addPrintViewClassToRootHtml() | ||
|
||
// Load call-taker/field-trip functionality (performs a fetch). | ||
initializeModules() | ||
} | ||
|
||
componentDidUpdate (prevProps) { | ||
const { | ||
fetchFieldTripDetails, | ||
receivedFieldTrips, | ||
request, | ||
requestId, | ||
session | ||
} = this.props | ||
if (!prevProps.session && session) { | ||
// When session is set, | ||
// create a placeholder in the calltaker redux state that has just one request | ||
// for fetching/receiving the details of the field trip per request id. | ||
receivedFieldTrips({ | ||
fieldTrips: [{ | ||
endTime: 0, | ||
id: requestId | ||
}] | ||
}) | ||
fetchFieldTripDetails(requestId) | ||
} | ||
|
||
if (request && request !== prevProps.request) { | ||
// Set window title when request has fully loaded | ||
// (appears in print headings) | ||
const { endLocation, schoolName } = request | ||
document.title = `Field Trip: ${schoolName} to ${endLocation}` | ||
} | ||
} | ||
|
||
componentWillUnmount () { | ||
clearClassFromRootHtml() | ||
} | ||
|
||
render () { | ||
const { config, request } = this.props | ||
const { LegIcon } = this.context | ||
if (!request) return null | ||
|
||
const { | ||
address, | ||
classpassId, | ||
emailAddress, | ||
endLocation, | ||
faxNumber, | ||
grade, | ||
numChaperones, | ||
numFreeStudents, | ||
numStudents, | ||
phoneNumber, | ||
schoolName, | ||
teacherName, | ||
timeStamp | ||
} = request | ||
|
||
// Outbound/inbound template | ||
const tripStructure = [ | ||
{ | ||
title: 'Outbound Trip (to Destination)', | ||
trip: getTripFromRequest(request, true), | ||
tripAbsentMessage: 'No Outbound Trip Planned' | ||
}, | ||
{ | ||
title: 'Inbound Trip (from Destination)', | ||
trip: getTripFromRequest(request, false), | ||
tripAbsentMessage: 'No Inbound Trip Planned' | ||
} | ||
] | ||
|
||
return ( | ||
<PrintLayout> | ||
<Header> | ||
<Button bsSize='small' onClick={this._print} style={{ float: 'right' }}> | ||
<i className='fa fa-print' /> Print | ||
</Button> | ||
<TripTitle>Field Trip Plan: {schoolName} to {endLocation}</TripTitle> | ||
</Header> | ||
<TripInfoList> | ||
<li><b>Teacher</b>: <Val>{teacherName}</Val> ({schoolName}, Grade: <Val>{grade}</Val>)</li> | ||
<li><b>Teacher Address</b>: <Val>{address}</Val></li> | ||
<li><b>Phone</b>: <Val>{phoneNumber}</Val> / <b>Fax</b>: <Val>{faxNumber}</Val></li> | ||
<li><b>Email</b>: <Val>{emailAddress}</Val></li> | ||
<li><b>Students Age 7 and Over</b>: {numStudents || 0}</li> | ||
<li><b>Students Age 6 and Under</b>: {numFreeStudents || 0}</li> | ||
<li><b>Chaperones</b>: {numChaperones || 0}</li> | ||
{classpassId && <li><b>Class Pass Hop Card #</b>: {classpassId}</li>} | ||
<li><i>Request submitted: {timeStamp}</i></li> | ||
</TripInfoList> | ||
|
||
{tripStructure.map(({ title, trip, tripAbsentMessage }, i) => ( | ||
<TripContainer key={i}> | ||
<h2>{title}</h2> | ||
{trip | ||
? trip.groupItineraries?.map((groupItin, i) => { | ||
const itinerary = JSON.parse(lzwDecode(groupItin.itinData)) | ||
return ( | ||
<TripBody key={i}> | ||
<ItineraryContainer> | ||
<h3>{groupItin.passengers} passengers on following itinerary:</h3> | ||
<ItineraryBody> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I got a little confused by this since we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used the class name in the old client that's why. Renamed in 6ef72b9. |
||
<PrintableItinerary | ||
config={config} | ||
itinerary={itinerary} | ||
LegIcon={LegIcon} | ||
/> | ||
<TripSummary itinerary={itinerary} /> | ||
</ItineraryBody> | ||
</ItineraryContainer> | ||
</TripBody> | ||
) | ||
}) | ||
: <TripBody><i>{tripAbsentMessage}</i></TripBody> | ||
} | ||
</TripContainer> | ||
))} | ||
</PrintLayout> | ||
) | ||
} | ||
} | ||
|
||
// connect to the redux store | ||
|
||
const mapStateToProps = (state, ownProps) => { | ||
const requestId = parseInt(state.router.location.query.requestId) | ||
const { requests } = state.callTaker.fieldTrip | ||
const request = requests.data.find(req => req.id === requestId) | ||
return { | ||
config: state.otp.config, | ||
request, | ||
requestId, | ||
session: state.callTaker.session | ||
} | ||
} | ||
|
||
const mapDispatchToProps = { | ||
fetchFieldTripDetails: fieldTripActions.fetchFieldTripDetails, | ||
initializeModules: callTakerActions.initializeModules, | ||
receivedFieldTrips: fieldTripActions.receivedFieldTrips | ||
} | ||
|
||
export default connect(mapStateToProps, mapDispatchToProps)(PrintFieldTripLayout) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import styled from 'styled-components' | ||
|
||
import TripDetails from '../narrative/connected-trip-details' | ||
|
||
// This file contains styles specific for rendering PrintFieldTripLayout. | ||
// They generally mimic the styles found in the OTP native client. | ||
|
||
export const PrintLayout = styled.div` | ||
font-size: 16px; | ||
line-height: 115%; | ||
margin: 8px; | ||
` | ||
|
||
export const Header = styled.div`` | ||
|
||
export const TripTitle = styled.h1` | ||
border-bottom: 3px solid gray; | ||
font-size: 30px; | ||
font-weight: bold; | ||
` | ||
|
||
export const TripInfoList = styled.ul` | ||
font-size: 16px; | ||
list-style: none; | ||
margin-top: 1em; | ||
padding: 0; | ||
` | ||
|
||
export const Val = styled.span` | ||
:empty:before { | ||
content: 'N/A'; | ||
} | ||
` | ||
|
||
// The styles below mirror those found in OTP native client. | ||
export const TripContainer = styled.div` | ||
background: #ddd; | ||
margin-top: 1em; | ||
|
||
& > h2 { | ||
font-size: 20px; | ||
font-weight: bold; | ||
margin: 0; | ||
padding: 4px; | ||
} | ||
` | ||
|
||
export const TripBody = styled.div` | ||
padding: 8px; | ||
` | ||
|
||
export const ItineraryContainer = styled.div` | ||
border: 3px solid #444; | ||
margin-top: .5em; | ||
|
||
& > h3 { | ||
background: #444; | ||
color: white; | ||
font-size: 18px; | ||
font-weight: bold; | ||
margin: 0; | ||
padding: 4px; | ||
} | ||
` | ||
|
||
export const ItineraryBody = styled.div` | ||
background: white; | ||
padding: 12px; | ||
` | ||
|
||
export const TripSummary = styled(TripDetails)` | ||
background: #eee; | ||
border: 1px solid #bbb; | ||
border-radius: 0; | ||
margin-top: 15px; | ||
padding: 5px; | ||
` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
session
could besessionId
instead sincesession
isn't used except to get thesessionId
field.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in 6ef72b9.