Skip to content

Commit

Permalink
Merge pull request #999 from ibi-group/feature/DT-183-Exception-based…
Browse files Browse the repository at this point in the history
…-scheduling

Support Exception Based Scheduling
  • Loading branch information
philip-cline authored Nov 30, 2023
2 parents 6f2de56 + 369f85f commit 90eef44
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 51 deletions.
26 changes: 26 additions & 0 deletions i18n/english.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ components:
deployments: Deployments
projects: Projects
root: Explore
CalendarSelect:
exceptionBasedCalendar: (Exception based calendar)
selectCalendar: " Select calendar..."
ConfirmModal:
cancel: Cancel
ok: OK
Expand Down Expand Up @@ -331,6 +334,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 @@ -1193,6 +1199,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 @@ -1497,6 +1521,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
26 changes: 26 additions & 0 deletions i18n/german.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ components:
deployments: Deployments
projects: Projekte
root: Erkunden
CalendarSelect:
exceptionBasedCalendar: (Exception based calendar)
selectCalendar: " Select calendar..."
ConfirmModal:
cancel: Abbrechen
ok: OK
Expand Down Expand Up @@ -340,6 +343,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 @@ -1195,6 +1201,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 @@ -1501,6 +1525,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
26 changes: 26 additions & 0 deletions i18n/polish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ components:
deployments: Wdrożenia
projects: Projekty
root: Eksploruj
CalendarSelect:
exceptionBasedCalendar: (Exception based calendar)
selectCalendar: " Select calendar..."
ConfirmModal:
cancel: Cancel
ok: OK
Expand Down Expand Up @@ -337,6 +340,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 @@ -1181,6 +1187,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 @@ -1478,6 +1502,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
# 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('_', ' '))
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 &&
<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

0 comments on commit 90eef44

Please sign in to comment.