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

Support Exception Based Scheduling #999

Merged
merged 6 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions i18n/english.yml
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ components:
createStop: Right-click a location on map to create a new stop
editSchedules: Edit schedules
name: Name
ExceptionCalendarSelector:
selectCalendar: Select calendar...
unnamedDefault: "[unnamed]"
ExceptionDate:
addRange: Add range
dateRemoved: ⓘ Date has been removed. Date entered is already included in an existing range or single date!
Expand Down Expand Up @@ -1165,6 +1168,24 @@ components:
patterns: "%num% Patterns"
stops: "%num% Stops"
trips: "%num% Trips"
ScheduleExceptionForm:
addDate: Add date
customServiceID: Custom service ID
dates: Dates
exceptionBasedService: Exception Based Service
exceptionName: Exception name*
noDatesSpecified: No dates specified
noService: No Service
onTheseDates: On these dates*
ranges: Ranges
runFollowing: "Run the following schedule:"
selectCalendar: Select calendar to run*
selectCalendarToAdd: "Select calendars to add (optional):"
selectCalendarToRemove: "Select calendars to remove (optional):"
selectExemplar: -- Select exception type --
swapAddOrRemove: Swap, add, or remove
thanksgivingDay: Thanksgiving Day
unnamedDefault: "[unnamed]"
SelectFileModal:
ok: OK
cancel: Cancel
Expand Down Expand Up @@ -1469,6 +1490,8 @@ components:
save: Save
Validation:
agencyRequired: Field must be populated for feeds with more than one agency.
conflictingServiceId: Service ID already exists in a standard calendar!
customServiceId: Custom service ID
dateServiceIdCombinationDuplicate: Date (%exceptionDate%) and Service ID (%serviceId%) combination cannot appear more than once for all exceptions.
idMustBeUnique: Identifier must be unique.
idRequired: Identifier is required if more than one agency exists.
Expand Down
23 changes: 23 additions & 0 deletions i18n/german.yml
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,9 @@ components:
auf eine Örtlickeit in der Karte
editSchedules: Abfahrzeiten erstellen
name: Name
ExceptionCalendarSelector:
selectCalendar: Select calendar...
unnamedDefault: "[unnamed]"
ExceptionDate:
addRange: Add range
dateRemoved: ⓘ Date has been removed. Date entered is already included in an existing range or single date!
Expand Down Expand Up @@ -1167,6 +1170,24 @@ components:
patterns: "%num% Patterns"
stops: "%num% Stops"
trips: "%num% Trips"
ScheduleExceptionForm:
addDate: " Add date"
customServiceID: Custom service ID
dates: Dates
exceptionBasedService: Exception Based Service
exceptionName: Exception name*
noDatesSpecified: No dates specified
noService: No Service
onTheseDates: On these dates*
ranges: Ranges
runFollowing: "Run the following schedule:"
selectCalendar: Select calendar to run*
selectCalendarToAdd: "Select calendars to add (optional):"
selectCalendarToRemove: "Select calendars to remove (optional):"
selectExemplar: -- Select exception type --
swapAddOrRemove: Swap, add, or remove
thanksgivingDay: Thanksgiving Day
unnamedDefault: "[unnamed]"
SelectFileModal:
cancel: Abbrechen
ok: OK
Expand Down Expand Up @@ -1473,6 +1494,8 @@ components:
save: Speichern
Validation:
agencyRequired: Field must be populated for feeds with more than one agency.
conflictingServiceId: Service ID already exists in a standard calendar!
customServiceId: Custom service ID
dateServiceIdCombinationDuplicate: Date (%exceptionDate%) and Service ID (%serviceId%) combination cannot appear more than once for all exceptions.
idMustBeUnique: Identifier must be unique.
idRequired: Identifier is required if more than one agency exists.
Expand Down
23 changes: 23 additions & 0 deletions i18n/polish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ components:
createStop: Right-click a location on map to create a new stop
editSchedules: Edit schedules
name: Name
ExceptionCalendarSelector:
selectCalendar: Select calendar...
unnamedDefault: "[unnamed]"
ExceptionDate:
addRange: Add range
dateRemoved: ⓘ Date has been removed. Date entered is already included in an existing range or single date!
Expand Down Expand Up @@ -1153,6 +1156,24 @@ components:
patterns: "%num% Patterns"
stops: "%num% Stops"
trips: "%num% Trips"
ScheduleExceptionForm:
addDate: " Add date"
customServiceID: Custom service ID
dates: Dates
exceptionBasedService: Exception Based Service
exceptionName: Exception name*
noDatesSpecified: No dates specified
noService: No Service
onTheseDates: On these dates*
ranges: Ranges
runFollowing: "Run the following schedule:"
selectCalendar: Select calendar to run*
selectCalendarToAdd: "Select calendars to add (optional):"
selectCalendarToRemove: "Select calendars to remove (optional):"
selectExemplar: -- Select exception type --
swapAddOrRemove: Swap, add, or remove
thanksgivingDay: Thanksgiving Day
unnamedDefault: "[unnamed]"
SelectFileModal:
cancel: Cancel
ok: OK
Expand Down Expand Up @@ -1450,6 +1471,8 @@ components:
save: Save
Validation:
agencyRequired: Field must be populated for feeds with more than one agency.
conflictingServiceId: Service ID already exists in a standard calendar!
customServiceId: Custom service ID
dateServiceIdCombinationDuplicate: Date (%exceptionDate%) and Service ID (%serviceId%) combination cannot appear more than once for all exceptions.
idMustBeUnique: Identifier must be unique.
idRequired: Identifier is required if more than one agency exists.
Expand Down
4 changes: 4 additions & 0 deletions lib/editor/actions/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,10 @@ export function fetchBaseGtfs ({
# Fetch dates to ensure we can do validation in the UI
# (avoid duplicate dates).
dates
# Fetch exemplar for display of exception based service in calendar select
exemplar
miles-grant-ibigroup marked this conversation as resolved.
Show resolved Hide resolved
# Fetch custom_schedule for proper service_id in exception based services
custom_schedule
}
stops (limit: -1) {
id
Expand Down
60 changes: 44 additions & 16 deletions lib/editor/components/ScheduleExceptionForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import toSentenceCase from '../../common/util/text'
import {getRangesForDates} from '../../common/util/exceptions'
import {EXCEPTION_EXEMPLARS} from '../util'
import {getTableById} from '../util/gtfs'
import {getComponentMessages} from '../../common/util/config'
import type {ServiceCalendar, ScheduleException} from '../../types'
import type {EditorTables} from '../../types/reducers'
import type {EditorValidationIssue} from '../util/validation'
Expand All @@ -37,6 +38,8 @@ type SelectOption = {
}

export default class ScheduleExceptionForm extends Component<Props> {
messages = getComponentMessages('ScheduleExceptionForm')

_onAddDate = () => {
const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
const dates = [...activeEntity.dates]
Expand Down Expand Up @@ -87,12 +90,23 @@ export default class ScheduleExceptionForm extends Component<Props> {
})
}

_onCustomScheduleChange = (evt: SyntheticInputEvent<HTMLInputElement>) => {
const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
updateActiveGtfsEntity({
component: activeComponent,
entity: activeEntity,
props: {custom_schedule: evt.target.value}
})
}

_renderExceptionExemplars (exemplar: string, value: number): string {
switch (value) {
case EXCEPTION_EXEMPLARS.SWAP:
return 'Swap, add, or remove'
return this.messages('swapAddOrRemove')
case EXCEPTION_EXEMPLARS.NO_SERVICE:
return toSentenceCase(exemplar.replace('_', ' '))
miles-grant-ibigroup marked this conversation as resolved.
Show resolved Hide resolved
return this.messages('noService')
case EXCEPTION_EXEMPLARS.EXCEPTION_SERVICE:
return this.messages('exceptionBasedService')
default:
return toSentenceCase(exemplar)
}
Expand Down Expand Up @@ -147,11 +161,11 @@ export default class ScheduleExceptionForm extends Component<Props> {
validationState={this._checkValidation('name')}
>
<ControlLabel>
<small>Exception name*</small>
<small>{this.messages('exceptionName')}</small>
</ControlLabel>
<FormControl
value={name || ''} // If name not given, provide empty string to override FormControl state.
placeholder='Thanksgiving Day'
placeholder={this.messages('thanksgivingDay')}
onChange={this._onNameChange} />
</FormGroup>
<FormGroup
Expand All @@ -161,14 +175,14 @@ export default class ScheduleExceptionForm extends Component<Props> {
validationState={this._checkValidation('exemplar')}
>
<ControlLabel>
<small>Run the following schedule:</small>
<small>{this.messages('runFollowing')}</small>
</ControlLabel>
<FormControl
componentClass='select'
value={hasExemplar ? exemplar : ''}
onChange={this._onExemplarChange}>
<option value='' disabled>
-- Select exception type --
{this.messages('selectExemplar')}
</option>
{Object.keys(EXCEPTION_EXEMPLARS)
.map(exemplar => {
Expand All @@ -186,7 +200,7 @@ export default class ScheduleExceptionForm extends Component<Props> {
</FormGroup>
{exemplar === EXCEPTION_EXEMPLARS.CUSTOM
? <ExceptionCalendarSelector
label={'Select calendar to run*'}
label={this.messages('selectCalendar')}
id={`custom_schedule`}
value={customSchedule}
onChange={this._onCalendarChange}
Expand All @@ -197,13 +211,13 @@ export default class ScheduleExceptionForm extends Component<Props> {
? (
<div>
<ExceptionCalendarSelector
label={'Select calendars to add (optional):'}
label={this.messages('selectCalendarToAdd')}
id={`added_service`}
value={addedService}
onChange={this._onCalendarChange}
calendars={this._filterByList(calendars, removedService)} />
<ExceptionCalendarSelector
label={'Select calendars to remove (optional):'}
label={this.messages('selectCalendarToRemove')}
id={`removed_service`}
value={removedService}
onChange={this._onCalendarChange}
Expand All @@ -212,14 +226,26 @@ export default class ScheduleExceptionForm extends Component<Props> {
)
: null
}
{exemplar === EXCEPTION_EXEMPLARS.EXCEPTION_SERVICE &&
miles-grant-ibigroup marked this conversation as resolved.
Show resolved Hide resolved
<FormGroup className={`col-xs-12`} validationState={this._checkValidation('Custom service ID')}>
<ControlLabel>
<small>{this.messages('customServiceID')}</small>
</ControlLabel>
<FormControl
value={activeEntity.custom_schedule || ''} // If custom_schedule not provided yet, provide empty string to override FormControl state.
placeholder='Thanksgiving-custom-1'
onChange={this._onCustomScheduleChange}
/>
</FormGroup>
}
<FormGroup
className={`col-xs-12`}
controlId={`exception-dates`}
data-test-id='exception-dates-container'
validationState={this._checkValidation('dates')}
>
<ControlLabel>
<small>On these dates*</small>
<small>{this.messages('onTheseDates')}</small>
</ControlLabel>
<FlipMove
enterAnimation='accordionVertical'
Expand All @@ -229,7 +255,7 @@ export default class ScheduleExceptionForm extends Component<Props> {
maintainContainerHeight
typeName={null}
>
{datesAndRangesBothActive && <h6 className='exception-subtitle'>Ranges</h6>}
{datesAndRangesBothActive && <h6 className='exception-subtitle'>{this.messages('ranges')}</h6>}
{ranges && ranges.map((range, index) => {
const startDateIndex = dates.findIndex(d => d === range.startDate)
const endDateIndex = dates.findIndex(d => d === range.endDate)
Expand All @@ -242,7 +268,7 @@ export default class ScheduleExceptionForm extends Component<Props> {
{...this.props}
/>)
})}
{datesAndRangesBothActive && <h6 className='exception-subtitle'>Dates</h6>}
{datesAndRangesBothActive && <h6 className='exception-subtitle'>{this.messages('dates')}</h6>}
{parsedDates && parsedDates.map((date, index) => {
const dateIndex = dates.findIndex(d => d === date)
return (
Expand All @@ -256,7 +282,7 @@ export default class ScheduleExceptionForm extends Component<Props> {
})}
</FlipMove>
{(!parsedDates || parsedDates.length === 0) && (!ranges || ranges.length === 0) &&
<div>No dates specified</div>
<div>{this.messages('noDatesSpecified')}</div>
}
</FormGroup>
<div className={`col-xs-12`}>
Expand All @@ -265,7 +291,7 @@ export default class ScheduleExceptionForm extends Component<Props> {
disabled={this.props.validationErrors.find(el => el.field.includes('dates-'))} // Any dates validation issue blocks adding a new date
onClick={this._onAddDate}
>
<Icon type='plus' /> Add date
<Icon type='plus' />{this.messages('addDate')}
</Button>
</div>
</Form>
Expand All @@ -283,6 +309,8 @@ type SelectorProps = {
}

class ExceptionCalendarSelector extends Component<SelectorProps> {
messages = getComponentMessages('ExceptionCalendarSelector')

_onChange = (input: ?Array<SelectOption>) => {
const {id, onChange} = this.props
onChange && onChange(input, id)
Expand All @@ -295,7 +323,7 @@ class ExceptionCalendarSelector extends Component<SelectorProps> {
})

_getCalendarName = calendar => {
let name = '[unnamed]'
let name = this.messages('unnamedDefault')
if (calendar) {
name = calendar.description ? calendar.description : ''
name += calendar.id ? ` (${calendar.id})` : ''
Expand All @@ -309,7 +337,7 @@ class ExceptionCalendarSelector extends Component<SelectorProps> {
<FormGroup controlId={id} className={`col-xs-12`}>
<ControlLabel><small>{label}</small></ControlLabel>
<Select
placeholder='Select calendar...'
placeholder={this.messages('selectCalendar')}
clearable
multi
value={value}
Expand Down
Loading
Loading