aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/fonts/sBoiv/sboiv.eotbin0 -> 1496 bytes
-rw-r--r--app/assets/fonts/sBoiv/sboiv.svg12
-rw-r--r--app/assets/fonts/sBoiv/sboiv.ttfbin0 -> 1340 bytes
-rw-r--r--app/assets/fonts/sBoiv/sboiv.woffbin0 -> 1416 bytes
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js6
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js1
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js386
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/batch.js26
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/App.js38
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/ConfirmModal.js42
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/Filters.js137
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/Navigate.js55
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js52
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/ToggleArrivals.js30
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js36
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js142
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js142
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/CalendarsEditVehicleJourney.js147
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/CreateModal.js113
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DeleteVehicleJourneys.js29
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js118
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js136
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/NotesEditVehicleJourney.js124
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js104
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/MissionSelect2.js62
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/TimetableSelect2.js63
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/ConfirmModal.js30
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Filters.js42
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Modal.js23
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Navigate.js18
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js16
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/ToggleArrivals.js21
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js21
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js31
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/AddVehicleJourney.js33
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/CalendarsEditVehicleJourney.js39
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DeleteVehicleJourneys.js22
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js30
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js30
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/NotesEditVehicleJourney.js33
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/ShiftVehicleJourney.js30
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/index.js78
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/filters.js66
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js18
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js130
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/pagination.js34
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/status.js16
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/stopPointsList.js8
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js195
-rw-r--r--app/assets/javascripts/selectable_table.coffee2
-rw-r--r--app/assets/stylesheets/application.sass2
-rw-r--r--app/assets/stylesheets/base/_config.sass2
-rw-r--r--app/assets/stylesheets/base/_utilities.sass24
-rw-r--r--app/assets/stylesheets/components/_forms.sass228
-rw-r--r--app/assets/stylesheets/components/_select2.sass2
-rw-r--r--app/assets/stylesheets/components/_tables.sass42
-rw-r--r--app/assets/stylesheets/modules/_vj_collection.sass68
-rw-r--r--app/assets/stylesheets/typography/_fonts.sass9
-rw-r--r--app/assets/stylesheets/typography/_sboiv.sass57
-rw-r--r--app/assets/stylesheets/typography/_typography.sass2
-rw-r--r--app/controllers/autocomplete_time_tables_controller.rb25
-rw-r--r--app/controllers/journey_patterns_collections_controller.rb2
-rw-r--r--app/controllers/line_footnotes_controller.rb4
-rw-r--r--app/controllers/vehicle_journeys_collections_controller.rb19
-rw-r--r--app/controllers/vehicle_journeys_controller.rb72
-rw-r--r--app/models/chouette/route.rb4
-rw-r--r--app/models/chouette/vehicle_journey.rb41
-rw-r--r--app/views/api/v1/journey_patterns/show.rabl2
-rw-r--r--app/views/autocomplete_time_tables/index.rabl2
-rw-r--r--app/views/journey_patterns/show.html.slim2
-rw-r--r--app/views/line_footnotes/show.rabl9
-rw-r--r--app/views/vehicle_journeys/index.html.slim147
-rw-r--r--app/views/vehicle_journeys/show.rabl59
-rw-r--r--config/locales/vehicle_journeys.fr.yml2
-rw-r--r--config/routes.rb3
-rw-r--r--db/schema.rb2
-rw-r--r--package.json6
-rw-r--r--spec/controllers/vehicle_journeys_collections_controller_spec.rb5
-rw-r--r--spec/javascripts/journey_patterns/reducers/pagination_spec.js4
-rw-r--r--spec/javascripts/vehicle_journeys/actions_spec.js394
-rw-r--r--spec/javascripts/vehicle_journeys/reducers/filters_spec.js154
-rw-r--r--spec/javascripts/vehicle_journeys/reducers/modal_spec.js160
-rw-r--r--spec/javascripts/vehicle_journeys/reducers/pagination_spec.js114
-rw-r--r--spec/javascripts/vehicle_journeys/reducers/status_spec.js45
-rw-r--r--spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js259
-rw-r--r--spec/models/chouette/vehicle_journey_spec.rb49
88 files changed, 4832 insertions, 164 deletions
diff --git a/Gemfile b/Gemfile
index 38dd85736..9f17b69c4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -90,7 +90,7 @@ gem 'slim-rails', '~> 3.1'
gem 'formtastic', '2.3.1'
gem 'RedCloth', '~> 4.3.0'
gem 'simple_form', '~> 3.1.0'
-gem 'font-awesome-sass', '~> 4.2.0'
+gem 'font-awesome-sass', '~> 4.7'
gem 'will_paginate-bootstrap', '~> 1.0.1'
gem 'breadcrumbs_on_rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index c4116d330..90e6a8d9c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -212,8 +212,8 @@ GEM
ffi (1.9.10-java)
ffi-geos (1.1.0)
ffi (>= 1.0.0)
- font-awesome-sass (4.2.2)
- sass (~> 3.2)
+ font-awesome-sass (4.7.0)
+ sass (>= 3.2)
foreigner (1.7.4)
activerecord (>= 3.0.0)
formatador (0.2.5)
@@ -626,7 +626,7 @@ DEPENDENCIES
faraday (~> 0.9.1)
faraday_middleware (~> 0.9.1)
ffaker (~> 2.1.0)
- font-awesome-sass (~> 4.2.0)
+ font-awesome-sass (~> 4.7)
foreigner (~> 1.7.4)
formtastic (= 2.3.1)
georuby (= 2.3.0)
diff --git a/app/assets/fonts/sBoiv/sboiv.eot b/app/assets/fonts/sBoiv/sboiv.eot
new file mode 100644
index 000000000..f3df36d9a
--- /dev/null
+++ b/app/assets/fonts/sBoiv/sboiv.eot
Binary files differ
diff --git a/app/assets/fonts/sBoiv/sboiv.svg b/app/assets/fonts/sBoiv/sboiv.svg
new file mode 100644
index 000000000..c506dee04
--- /dev/null
+++ b/app/assets/fonts/sBoiv/sboiv.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata>Generated by IcoMoon</metadata>
+<defs>
+<font id="sboiv" horiz-adv-x="1024">
+<font-face units-per-em="1024" ascent="960" descent="-64" />
+<missing-glyph horiz-adv-x="1024" />
+<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
+<glyph unicode="&#xe900;" glyph-name="update-vj" d="M1017.856 448c0 266.935-216.393 483.328-483.328 483.328v-99.84c211.677-0.291 383.197-171.81 383.488-383.46zM918.016 448c0-0.071 0-0.154 0-0.238 0-211.795-171.693-383.488-383.488-383.488-187.627 0-343.782 134.745-376.977 312.746l-102.767 2.371c29.828-240.928 233.283-425.583 479.866-425.583 266.935 0 483.328 216.393 483.328 483.328 0 3.82-0.044 7.629-0.132 11.427zM534.528 755.2c169.662 0 307.2-137.538 307.2-307.2h-307.2zM460.8 669.184c-0.283-17.134-14.098-30.949-31.205-31.232h-136.731v-136.192c0.003-0.152 0.004-0.332 0.004-0.512 0-17.249-13.983-31.232-31.232-31.232-0.001 0-0.003 0-0.004 0h-62.976c-17.31 0.288-31.232 14.388-31.232 31.74 0 0.001 0 0.003 0 0.004v136.192h-136.192c-17.249 0-31.232 13.983-31.232 31.232v62.976c0 17.249 13.983 31.232 31.232 31.232h136.192v136.704c0.283 17.134 14.098 30.949 31.205 31.232h63.003c17.249 0 31.232-13.983 31.232-31.232v-136.704h136.192c0.001 0 0.003 0 0.004 0 17.352 0 31.452-13.922 31.74-31.205z" />
+<glyph unicode="&#xe901;" glyph-name="chrono" horiz-adv-x="931" d="M440.957-64c243.751 0 440.957 196.267 440.957 438.857 0 225.524-171.483 412.038-391.962 436.419v51.2h73.493v97.524h-244.976v-97.524h73.493v-51.2c-220.478-24.381-391.962-209.676-391.962-436.419 0-242.59 197.206-438.857 440.957-438.857zM756.974 864.917l-71.043-70.705c71.043-41.448 131.062-101.181 172.708-171.886l71.055 70.705c-44.108 69.486-104.115 128-172.72 171.886z" />
+</font></defs></svg> \ No newline at end of file
diff --git a/app/assets/fonts/sBoiv/sboiv.ttf b/app/assets/fonts/sBoiv/sboiv.ttf
new file mode 100644
index 000000000..f2b70a1cf
--- /dev/null
+++ b/app/assets/fonts/sBoiv/sboiv.ttf
Binary files differ
diff --git a/app/assets/fonts/sBoiv/sboiv.woff b/app/assets/fonts/sBoiv/sboiv.woff
new file mode 100644
index 000000000..8ede810d8
--- /dev/null
+++ b/app/assets/fonts/sBoiv/sboiv.woff
Binary files differ
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js b/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js
index f5a3357eb..a31f89841 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js
@@ -125,6 +125,9 @@ const actions = {
if(next) {
dispatch(next)
} else {
+ if(json.length != window.journeyPatternsPerPage){
+ dispatch(actions.updateTotalCount(window.journeyPatternsPerPage - json.length))
+ }
dispatch(actions.receiveJourneyPatterns(json))
}
}
@@ -189,9 +192,6 @@ const actions = {
deletable: false
})
}
- if(journeyPatterns.length != window.journeyPatternsPerPage){
- dispatch(actions.updateTotalCount(journeyPatterns.length - window.journeyPatternsPerPage))
- }
}
dispatch(actions.receiveJourneyPatterns(journeyPatterns))
}
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js b/app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js
index d9a4df099..5d12db37c 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js
@@ -11,6 +11,7 @@ class CreateModal extends Component {
handleSubmit() {
if(actions.validateFields(this.refs) == true) {
this.props.onAddJourneyPattern(this.refs)
+ this.props.onModalClose()
$('#NewJourneyPatternModal').modal('hide')
}
}
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js
new file mode 100644
index 000000000..25fa3b221
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js
@@ -0,0 +1,386 @@
+var batchActions = require('../batch').batchActions
+
+const actions = {
+ receiveVehicleJourneys : (json) => ({
+ type: "RECEIVE_VEHICLE_JOURNEYS",
+ json
+ }),
+ receiveErrors : (json) => ({
+ type: "RECEIVE_ERRORS",
+ json
+ }),
+ fetchingApi: () =>({
+ type: 'FETCH_API'
+ }),
+ unavailableServer : () => ({
+ type: 'UNAVAILABLE_SERVER'
+ }),
+ goToPreviousPage : (dispatch, pagination, queryString) => ({
+ type: 'GO_TO_PREVIOUS_PAGE',
+ dispatch,
+ pagination,
+ nextPage : false,
+ queryString
+ }),
+ goToNextPage : (dispatch, pagination, queryString) => ({
+ type: 'GO_TO_NEXT_PAGE',
+ dispatch,
+ pagination,
+ nextPage : true,
+ queryString
+ }),
+ checkConfirmModal : (event, callback, stateChanged, dispatch) => {
+ if(stateChanged === true){
+ return actions.openConfirmModal(callback)
+ }else{
+ dispatch(actions.fetchingApi())
+ return callback
+ }
+ },
+ openCreateModal : () => ({
+ type : 'CREATE_VEHICLEJOURNEY_MODAL'
+ }),
+ selectJPCreateModal : (selectedJP) => ({
+ type : 'SELECT_JP_CREATE_MODAL',
+ selectedItem: {
+ id: selectedJP.id,
+ objectid: selectedJP.object_id,
+ name: selectedJP.name,
+ published_name: selectedJP.published_name
+ }
+ }),
+ openEditModal : (vehicleJourney) => ({
+ type : 'EDIT_VEHICLEJOURNEY_MODAL',
+ vehicleJourney
+ }),
+ openNotesEditModal : (vehicleJourney) => ({
+ type : 'EDIT_NOTES_VEHICLEJOURNEY_MODAL',
+ vehicleJourney
+ }),
+ toggleFootnoteModal : (footnote, isShown) => ({
+ type: 'TOGGLE_FOOTNOTE_MODAL',
+ footnote,
+ isShown
+ }),
+ openCalendarsEditModal : (vehicleJourneys) => ({
+ type : 'EDIT_CALENDARS_VEHICLEJOURNEY_MODAL',
+ vehicleJourneys
+ }),
+ selectTTCalendarsModal: (selectedTT) =>({
+ type: 'SELECT_TT_CALENDAR_MODAL',
+ selectedItem:{
+ id: selectedTT.id,
+ comment: selectedTT.comment,
+ objectid: selectedTT.objectid
+ }
+ }),
+ addSelectedTimetable: () => ({
+ type: 'ADD_SELECTED_TIMETABLE'
+ }),
+ deleteCalendarModal : (timetable) => ({
+ type : 'DELETE_CALENDAR_MODAL',
+ timetable
+ }),
+ editVehicleJourneyCalendars : (vehicleJourneys) => ({
+ type: 'EDIT_VEHICLEJOURNEYS_CALENDARS',
+ vehicleJourneys
+ }),
+ openShiftModal : () => ({
+ type : 'SHIFT_VEHICLEJOURNEY_MODAL'
+ }),
+ openDuplicateModal : () => ({
+ type : 'DUPLICATE_VEHICLEJOURNEY_MODAL'
+ }),
+ selectVehicleJourney : (index) => ({
+ type : 'SELECT_VEHICLEJOURNEY',
+ index
+ }),
+ cancelSelection : () => ({
+ type: 'CANCEL_SELECTION'
+ }),
+ addVehicleJourney : (data, selectedJourneyPattern) => ({
+ type: 'ADD_VEHICLEJOURNEY',
+ data,
+ selectedJourneyPattern
+ }),
+ editVehicleJourney : (data) => ({
+ type: 'EDIT_VEHICLEJOURNEY',
+ data
+ }),
+ editVehicleJourneyNotes : (footnotes) => ({
+ type: 'EDIT_VEHICLEJOURNEY_NOTES',
+ footnotes
+ }),
+ shiftVehicleJourney : (data) => ({
+ type: 'SHIFT_VEHICLEJOURNEY',
+ data
+ }),
+ duplicateVehicleJourney : (data) => ({
+ type: 'DUPLICATE_VEHICLEJOURNEY',
+ data
+ }),
+ deleteVehicleJourneys : () => ({
+ type: 'DELETE_VEHICLEJOURNEYS'
+ }),
+ openConfirmModal : (callback) => ({
+ type : 'OPEN_CONFIRM_MODAL',
+ callback
+ }),
+ closeModal : () => ({
+ type : 'CLOSE_MODAL'
+ }),
+ resetValidation: (target) => {
+ $(target).parent().removeClass('has-error').children('.help-block').remove()
+ },
+ validateFields : (fields) => {
+ const test = []
+
+ Object.keys(fields).map(function(key) {
+ test.push(fields[key].validity.valid)
+ })
+ if(test.indexOf(false) >= 0) {
+ // Form is invalid
+ test.map(function(item, i) {
+ if(item == false) {
+ const k = Object.keys(fields)[i]
+ $(fields[k]).parent().addClass('has-error').children('.help-block').remove()
+ $(fields[k]).parent().append("<span class='small help-block'>" + fields[k].validationMessage + "</span>")
+ }
+ })
+ return false
+ } else {
+ // Form is valid
+ return true
+ }
+ },
+ toggleArrivals : () => ({
+ type: 'TOGGLE_ARRIVALS',
+ }),
+ updateTime : (val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => ({
+ type: 'UPDATE_TIME',
+ val,
+ subIndex,
+ index,
+ timeUnit,
+ isDeparture,
+ isArrivalsToggled
+ }),
+ resetStateFilters: () => ({
+ type: 'RESET_FILTERS'
+ }),
+ toggleWithoutSchedule: () => ({
+ type: 'TOGGLE_WITHOUT_SCHEDULE'
+ }),
+ updateStartTimeFilter: (val, unit) => ({
+ type: 'UPDATE_START_TIME_FILTER',
+ val,
+ unit
+ }),
+ updateEndTimeFilter: (val, unit) => ({
+ type: 'UPDATE_END_TIME_FILTER',
+ val,
+ unit
+ }),
+ filterSelect2Timetable: (selectedTT) =>({
+ type: 'SELECT_TT_FILTER',
+ selectedItem:{
+ id: selectedTT.id,
+ comment: selectedTT.comment,
+ objectid: selectedTT.objectid
+ }
+ }),
+ filterSelect2JourneyPattern: (selectedJP) => ({
+ type : 'SELECT_JP_FILTER',
+ selectedItem: {
+ id: selectedJP.id,
+ objectid: selectedJP.object_id,
+ name: selectedJP.name,
+ published_name: selectedJP.published_name
+ }
+ }),
+ createQueryString: () => ({
+ type: 'CREATE_QUERY_STRING'
+ }),
+ resetPagination: () => ({
+ type: 'RESET_PAGINATION'
+ }),
+ queryFilterVehicleJourneys: (dispatch) => ({
+ type: 'QUERY_FILTER_VEHICLEJOURNEYS',
+ dispatch
+ }),
+ resetFilters: (dispatch) => (
+ batchActions([
+ actions.resetStateFilters(),
+ actions.resetPagination(),
+ actions.queryFilterVehicleJourneys(dispatch)
+ ])
+ ),
+ filterQuery: (dispatch) => (
+ batchActions([
+ actions.createQueryString(),
+ actions.resetPagination(),
+ actions.queryFilterVehicleJourneys(dispatch)
+ ])
+ ),
+ updateTotalCount: (diff) => ({
+ type: 'UPDATE_TOTAL_COUNT',
+ diff
+ }),
+ fetchVehicleJourneys : (dispatch, currentPage, nextPage, queryString) => {
+ if(currentPage == undefined){
+ currentPage = 1
+ }
+ let vehicleJourneys = []
+ let page
+ switch (nextPage) {
+ case true:
+ page = currentPage + 1
+ break
+ case false:
+ if(currentPage > 1){
+ page = currentPage - 1
+ }
+ break
+ default:
+ page = currentPage
+ break
+ }
+ let str = ".json"
+ let sep = '?'
+ if(page > 1){
+ str = '.json?page=' + page.toString()
+ sep = '&'
+ }
+ let urlJSON = window.location.pathname + str
+ if (queryString){
+ urlJSON = urlJSON + sep + queryString
+ }
+ let req = new Request(urlJSON, {
+ credentials: 'same-origin',
+ })
+ let hasError = false
+ fetch(req)
+ .then(response => {
+ if(response.status == 500) {
+ hasError = true
+ }
+ return response.json()
+ }).then((json) => {
+ if(hasError == true) {
+ dispatch(actions.unavailableServer())
+ } else {
+ let val
+ for (val of json){
+ var timeTables = []
+ let tt
+ for (tt of val.time_tables){
+ timeTables.push({
+ objectid: tt.objectid,
+ comment: tt.comment,
+ id: tt.id
+ })
+ }
+ let vjasWithDelta = val.vehicle_journey_at_stops.map((vjas, i) => {
+ actions.fillEmptyFields(vjas)
+ return actions.getDelta(vjas)
+ })
+ vehicleJourneys.push({
+ journey_pattern: val.journey_pattern,
+ published_journey_name: val.published_journey_name,
+ objectid: val.objectid,
+ footnotes: val.footnotes,
+ time_tables: timeTables,
+ vehicle_journey_at_stops: vjasWithDelta,
+ deletable: false,
+ selected: false,
+ published_journey_name: val.published_journey_name || 'non renseigné',
+ published_journey_identifier: val.published_journey_name || 'non renseigné',
+ company_id: val.published_journey_name || 'non renseigné'
+ })
+ }
+ dispatch(actions.receiveVehicleJourneys(vehicleJourneys))
+ }
+ })
+ },
+ submitVehicleJourneys : (dispatch, state, next) => {
+ dispatch(actions.fetchingApi())
+ let urlJSON = window.location.pathname + "_collection.json"
+ let req = new Request(urlJSON, {
+ credentials: 'same-origin',
+ method: 'PATCH',
+ contentType: 'application/json; charset=utf-8',
+ Accept: 'application/json',
+ body: JSON.stringify(state),
+ headers: {
+ 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
+ }
+ })
+ let hasError = false
+ fetch(req)
+ .then(response => {
+ if(!response.ok) {
+ hasError = true
+ }
+ return response.json()
+ }).then((json) => {
+ if(hasError == true) {
+ dispatch(actions.receiveErrors(json))
+ } else {
+ if(next) {
+ dispatch(next)
+ } else {
+ if(json.length != window.vehicleJourneysPerPage){
+ dispatch(actions.updateTotalCount(window.vehicleJourneysPerPage - json.length))
+ }
+ dispatch(actions.receiveVehicleJourneys(json))
+ }
+ }
+ })
+ },
+ // VJAS HELPERS
+ getSelected: (vj) => {
+ return vj.filter((obj) =>{
+ return obj.selected
+ })
+ },
+ pad: (d) => {
+ if(d.toString().length == 1){
+ return (d < 10) ? '0' + d.toString() : d.toString();
+ }else{
+ return d.toString()
+ }
+ },
+ fillEmptyFields: (vjas) => {
+ if (vjas.departure_time.hour == null) vjas.departure_time.hour = '00'
+ if (vjas.departure_time.minute == null) vjas.departure_time.minute = '00'
+ if (vjas.arrival_time.hour == null) vjas.arrival_time.hour = '00'
+ if (vjas.arrival_time.minute == null) vjas.arrival_time.minute = '00'
+ return vjas
+ },
+ getDelta: (vjas) => {
+ let delta = 0
+ if (vjas.departure_time.hour != '' && vjas.departure_time.minute != '' && vjas.arrival_time.hour != '' && vjas.departure_time.minute != ''){
+ delta = (parseInt(vjas.departure_time.hour) - parseInt(vjas.arrival_time.hour)) * 60 + (parseInt(vjas.departure_time.minute) - parseInt(vjas.arrival_time.minute))
+ }
+ vjas.delta = delta
+ return vjas
+ },
+ checkSchedules: (schedule) => {
+ if (parseInt(schedule.departure_time.minute) > 59){
+ schedule.departure_time.minute = actions.pad(parseInt(schedule.departure_time.minute) - 60)
+ schedule.departure_time.hour = actions.pad(parseInt(schedule.departure_time.hour) + 1)
+ }
+ if (parseInt(schedule.arrival_time.minute) > 59){
+ schedule.arrival_time.minute = actions.pad(parseInt(schedule.arrival_time.minute) - 60)
+ schedule.arrival_time.hour = actions.pad(parseInt(schedule.arrival_time.hour) + 1)
+ }
+ if (parseInt(schedule.departure_time.hour) > 23){
+ schedule.departure_time.hour = actions.pad(parseInt(schedule.departure_time.hour) - 24)
+ }
+ if (parseInt(schedule.arrival_time.hour) > 23){
+ schedule.arrival_time.hour = actions.pad(parseInt(schedule.arrival_time.hour) - 24)
+ }
+ }
+}
+
+module.exports = actions
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/batch.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/batch.js
new file mode 100644
index 000000000..284d7b268
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/batch.js
@@ -0,0 +1,26 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.batchActions = batchActions;
+exports.enableBatching = enableBatching;
+var BATCH = exports.BATCH = 'BATCH';
+
+function batchActions(actions) {
+ return {
+ type: BATCH,
+ payload: actions
+ };
+}
+
+function enableBatching(reduce) {
+ return function batchingReducer(state, action) {
+ switch (action.type) {
+ case BATCH:
+ return action.payload.reduce(batchingReducer, state);
+ default:
+ return reduce(state, action);
+ }
+ };
+}
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/App.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/App.js
new file mode 100644
index 000000000..d5f419747
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/App.js
@@ -0,0 +1,38 @@
+var React = require('react')
+var VehicleJourneysList = require('../containers/VehicleJourneysList')
+var Navigate = require('../containers/Navigate')
+var ToggleArrivals = require('../containers/ToggleArrivals')
+var Filters = require('../containers/Filters')
+var SaveVehicleJourneys = require('../containers/SaveVehicleJourneys')
+var ConfirmModal = require('../containers/ConfirmModal')
+var Tools = require('../containers/Tools')
+
+const App = () => (
+ <div>
+
+ <div className='row'>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>
+ <ToggleArrivals />
+ </div>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right'>
+ <Navigate />
+ </div>
+ </div>
+
+ <Filters />
+ <VehicleJourneysList />
+
+ <div className='row'>
+ <div className='col-lg-12 text-right'>
+ <Navigate />
+ </div>
+ </div>
+
+ <SaveVehicleJourneys />
+ <Tools />
+
+ <ConfirmModal />
+ </div>
+)
+
+module.exports = App
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/ConfirmModal.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/ConfirmModal.js
new file mode 100644
index 000000000..e1f40a2f9
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/ConfirmModal.js
@@ -0,0 +1,42 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+
+const ConfirmModal = ({dispatch, modal, onModalAccept, onModalCancel, vehicleJourneys}) => (
+ <div className={ 'modal fade ' + ((modal.type == 'confirm') ? 'in' : '') } id='ConfirmModal'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-body'>
+ <p> Voulez-vous enregistrer vos modifications avant de changer de page? </p>
+ </div>
+ <div className='modal-footer'>
+ <button
+ className='btn btn-default'
+ data-dismiss='modal'
+ type='button'
+ onClick= {() => {onModalCancel(modal.confirmModal.callback)}}
+ >
+ Ne pas enregistrer
+ </button>
+ <button
+ className='btn btn-danger'
+ data-dismiss='modal'
+ type='button'
+ onClick = {() => {onModalAccept(modal.confirmModal.callback, vehicleJourneys)}}
+ >
+ Enregistrer
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+)
+
+ConfirmModal.propTypes = {
+ vehicleJourneys: PropTypes.array.isRequired,
+ modal: PropTypes.object.isRequired,
+ onModalAccept: PropTypes.func.isRequired,
+ onModalCancel: PropTypes.func.isRequired
+}
+
+module.exports = ConfirmModal
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Filters.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Filters.js
new file mode 100644
index 000000000..bddb29434
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Filters.js
@@ -0,0 +1,137 @@
+var React = require('react')
+var PropTypes = require('react').PropTypes
+var MissionSelect2 = require('./tools/select2s/MissionSelect2')
+var TimetableSelect2 = require('./tools/select2s/TimetableSelect2')
+
+const Filters = ({filters, pagination, onFilter, onResetFilters, onUpdateStartTimeFilter, onUpdateEndTimeFilter, onToggleWithoutSchedule, onSelect2Timetable, onSelect2JourneyPattern}) => {
+ return (
+ <div className='row'>
+ <div className='col-lg-12'>
+ <div className='form form-filter'>
+ <div className='ffg-row'>
+ {/* Missions */}
+ <div className='form-group w40'>
+ <MissionSelect2
+ onSelect2JourneyPattern={onSelect2JourneyPattern}
+ filters={filters}
+ isFilter={true}
+ />
+ </div>
+
+ {/* Calendriers */}
+ <div className='form-group w40'>
+ <TimetableSelect2
+ onSelect2Timetable={onSelect2Timetable}
+ hasRoute={true}
+ chunkURL={("/autocomplete_time_tables.json?route_id=" + String(window.route_id))}
+ filters={filters}
+ isFilter={true}
+ />
+ </div>
+ </div>
+
+ <div className='ffg-row'>
+ {/* Plage horaire */}
+ <div className='form-group togglable'>
+ <label className='control-label'>Plage horaire au départ de la course</label>
+ <div className='filter_menu'>
+ <div className='form-group time filter_menu-item'>
+ <label className='control-label time'>Début</label>
+ <div className='form-inline'>
+ <div className='input-group time'>
+ <input
+ type='number'
+ className='form-control'
+ min='00'
+ max='23'
+ onChange={(e) => {onUpdateStartTimeFilter(e, 'hour')}}
+ value={filters.query.interval.start.hour}
+ />
+ <span>:</span>
+ <input
+ type='number'
+ className='form-control'
+ min='00'
+ max='59'
+ onChange={(e) => {onUpdateStartTimeFilter(e, 'minute')}}
+ value={filters.query.interval.start.minute}
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group time filter_menu-item'>
+ <label className='control-label time'>Fin</label>
+ <div className='form-inline'>
+ <div className='input-group time'>
+ <input
+ type='number'
+ className='form-control'
+ min='00'
+ max='23'
+ onChange={(e) => {onUpdateEndTimeFilter(e, 'hour')}}
+ value={filters.query.interval.end.hour}
+ />
+ <span>:</span>
+ <input
+ type='number'
+ className='form-control'
+ min='00'
+ max='59'
+ onChange={(e) => {onUpdateEndTimeFilter(e, 'minute')}}
+ value={filters.query.interval.end.minute}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {/* Switch avec/sans horaires */}
+ <div className='form-group has_switch w40'>
+ <label className='control-label col-sm-8'>Afficher les courses sans horaires</label>
+ <div className='form-group col-sm-4' style={{padding: 0}}>
+ <div className='checkbox'>
+ <label>
+ <input
+ type='checkbox'
+ onChange={onToggleWithoutSchedule}
+ checked={filters.query.withoutSchedule}
+ ></input>
+ <span className='switch-label' data-checkedvalue='Oui' data-uncheckedvalue='Non'></span>
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {/* Actions */}
+ <div className='actions'>
+ <span
+ className='btn btn-link'
+ onClick={(e) => onResetFilters(e, pagination)}>
+ Effacer
+ </span>
+ <span
+ className='btn btn-default'
+ onClick={(e) => onFilter(e, pagination)}>
+ Filtrer
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+}
+
+Filters.propTypes = {
+ filters : PropTypes.object.isRequired,
+ pagination : PropTypes.object.isRequired,
+ onFilter: PropTypes.func.isRequired,
+ onResetFilters: PropTypes.func.isRequired,
+ onUpdateStartTimeFilter: PropTypes.func.isRequired,
+ onUpdateEndTimeFilter: PropTypes.func.isRequired,
+ onSelect2Timetable: PropTypes.func.isRequired,
+ onSelect2JourneyPattern: PropTypes.func.isRequired
+}
+
+module.exports = Filters
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Navigate.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Navigate.js
new file mode 100644
index 000000000..9b95c1bc6
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Navigate.js
@@ -0,0 +1,55 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var actions = require('../actions')
+
+let Navigate = ({ dispatch, vehicleJourneys, pagination, status, filters}) => {
+ let firstPage = 1
+ let lastPage = Math.ceil(pagination.totalCount / pagination.perPage)
+ let minVJ = (pagination.page - 1) * pagination.perPage + 1
+ let maxVJ = Math.min((pagination.page * pagination.perPage), pagination.totalCount)
+ if(status.isFetching == true) {
+ return false
+ }
+ if(status.fetchSuccess == true) {
+ return (
+ <div className="pagination">
+ Liste des horaires {minVJ} à {maxVJ} sur {pagination.totalCount}
+
+ <form className='page_links' onSubmit={e => {e.preventDefault()}}>
+ <button
+ onClick={e => {
+ e.preventDefault()
+ dispatch(actions.checkConfirmModal(e, actions.goToPreviousPage(dispatch, pagination, filters.queryString), pagination.stateChanged, dispatch))
+ }}
+ type='button'
+ data-target='#ConfirmModal'
+ className={(pagination.page == firstPage ? 'disabled ' : '') + 'previous_page'}
+ disabled={(pagination.page == firstPage ? true : false)}
+ ></button>
+ <button
+ onClick={e => {
+ e.preventDefault()
+ dispatch(actions.checkConfirmModal(e, actions.goToNextPage(dispatch, pagination, filters.queryString), pagination.stateChanged, dispatch))
+ }}
+ type='button'
+ data-target='#ConfirmModal'
+ className={(pagination.page == lastPage ? 'disabled ' : '') + 'next_page'}
+ disabled={(pagination.page == lastPage ? true : false)}
+ ></button>
+ </form>
+ </div>
+ )
+ } else {
+ return false
+ }
+}
+
+Navigate.propTypes = {
+ vehicleJourneys: PropTypes.array.isRequired,
+ status: PropTypes.object.isRequired,
+ pagination: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired
+}
+
+module.exports = Navigate
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js
new file mode 100644
index 000000000..2c4e75681
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js
@@ -0,0 +1,52 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var actions = require('../actions')
+
+class SaveVehicleJourneys extends Component{
+ constructor(props){
+ super(props)
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if(prevProps.status.isFetching == true) {
+ submitMover();
+ }
+ }
+
+ render() {
+ if(this.props.status.isFetching == true) {
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <div className='row mt-md'>
+ <div className='col-lg-12 text-right'>
+ <form className='vehicle_journeys formSubmitr ml-xs' onSubmit={e => {e.preventDefault()}}>
+ <button
+ className='btn btn-default'
+ type='button'
+ onClick={e => {
+ e.preventDefault()
+ actions.submitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys)
+ }}
+ >
+ Enregistrer
+ </button>
+ </form>
+ </div>
+ </div>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+SaveVehicleJourneys.propTypes = {
+ vehicleJourneys: PropTypes.array.isRequired,
+ page: PropTypes.number.isRequired,
+ status: PropTypes.object.isRequired
+}
+
+module.exports = SaveVehicleJourneys
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/ToggleArrivals.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/ToggleArrivals.js
new file mode 100644
index 000000000..48fee683f
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/ToggleArrivals.js
@@ -0,0 +1,30 @@
+var React = require('react')
+var PropTypes = require('react').PropTypes
+
+const ToggleArrivals = ({filters, onToggleArrivals}) => {
+ return (
+ <div className='has_switch form-group inline'>
+ <label htmlFor='toggleArrivals' className='control-label'>Afficher et éditer les horaires d'arrivée</label>
+ <div className='form-group'>
+ <div className='checkbox'>
+ <label>
+ <input
+ onChange={onToggleArrivals}
+ type='checkbox'
+ checked={filters.toggleArrivals}
+ >
+ </input>
+ <span className='switch-label'></span>
+ </label>
+ </div>
+ </div>
+ </div>
+ )
+}
+
+ToggleArrivals.propTypes = {
+ filters : PropTypes.object.isRequired,
+ onToggleArrivals: PropTypes.func.isRequired
+}
+
+module.exports = ToggleArrivals
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js
new file mode 100644
index 000000000..e486dd155
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js
@@ -0,0 +1,36 @@
+var React = require('react')
+var PropTypes = require('react').PropTypes
+var AddVehicleJourney = require('../containers/tools/AddVehicleJourney')
+var DeleteVehicleJourneys = require('../containers/tools/DeleteVehicleJourneys')
+var ShiftVehicleJourney = require('../containers/tools/ShiftVehicleJourney')
+var DuplicateVehicleJourney = require('../containers/tools/DuplicateVehicleJourney')
+var EditVehicleJourney = require('../containers/tools/EditVehicleJourney')
+var NotesEditVehicleJourney = require('../containers/tools/NotesEditVehicleJourney')
+var CalendarsEditVehicleJourney = require('../containers/tools/CalendarsEditVehicleJourney')
+var actions = require('../actions')
+
+const Tools = ({vehicleJourneys, onCancelSelection}) => {
+ return (
+ <div className='select_toolbox'>
+ <ul>
+ <AddVehicleJourney />
+ <DuplicateVehicleJourney />
+ <ShiftVehicleJourney />
+ <EditVehicleJourney />
+ <CalendarsEditVehicleJourney />
+ <NotesEditVehicleJourney />
+ <DeleteVehicleJourneys />
+ </ul>
+
+ <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span>
+ <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>Annuler la sélection</button>
+ </div>
+ )
+}
+
+Tools.propTypes = {
+ vehicleJourneys : PropTypes.array.isRequired,
+ onCancelSelection: PropTypes.func.isRequired
+}
+
+module.exports = Tools
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js
new file mode 100644
index 000000000..c88dda5f4
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js
@@ -0,0 +1,142 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+
+class VehicleJourney extends Component {
+ constructor(props) {
+ super(props)
+ this.previousCity = undefined
+ }
+
+ cityNameChecker(sp) {
+ let bool = false
+ if(sp.stop_area_cityname != this.previousCity){
+ bool = true
+ this.previousCity = sp.stop_area_cityname
+ }
+ return bool
+ }
+
+ timeTableURL(id) {
+ let refURL = window.location.pathname.split('/', 3).join('/')
+ let ttURL = refURL + '/time_tables/' + id
+
+ return (
+ <a href={ttURL} title='Voir le calendrier'><span className='fa fa-calendar'></span></a>
+ )
+ }
+
+ columnHasDelta() {
+ let a = []
+ this.props.value.vehicle_journey_at_stops.map((vj, i) => {
+ a.push(vj.delta)
+ })
+ let b = a.reduce((p, c) => p+c, 0)
+
+ if(b > 0) {
+ return true
+ }
+ }
+
+ isDisabled(bool1, bool2) {
+ return (bool1 || bool2)
+ }
+
+ render() {
+ this.previousCity = undefined
+
+ return (
+ <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '')}>
+ <div className='th'>
+ <div className='strong mb-xs'>{this.props.value.objectid ? this.props.value.objectid : '-'}</div>
+ <div>{this.props.value.journey_pattern.objectid}</div>
+ {this.props.value.time_tables.map((tt, i)=>
+ <div key={i}>{this.timeTableURL(tt.id)}</div>
+ )}
+
+ <div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}>
+ <input
+ id={this.props.index}
+ name={this.props.index}
+ onChange={(e) => this.props.onSelectVehicleJourney(this.props.index)}
+ type='checkbox'
+ disabled={this.props.value.deletable}
+ checked={this.props.value.selected}
+ ></input>
+ <label htmlFor={this.props.index}></label>
+ </div>
+ </div>
+
+ {this.props.value.vehicle_journey_at_stops.map((vj, i) =>
+ <div key={i} className='td text-center'>
+ <div className={'cellwrap' + (this.cityNameChecker(vj) ? ' headlined' : '')}>
+ {this.props.filters.toggleArrivals &&
+ <div data-headline='Départ à'>
+ <span className={((this.props.value.deletable && (!vj.dummy)) ? 'disabled ' : '') + 'input-group time'}>
+ <input
+ type='number'
+ min='00'
+ max='23'
+ className='form-control'
+ disabled={(this.props.value.deletable && (!vj.dummy))}
+ onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}}
+ value={vj.arrival_time['hour']}
+ />
+ <span>:</span>
+ <input
+ type='number'
+ min='00'
+ max='59'
+ className='form-control'
+ disabled={((this.props.value.deletable) && (!vj.dummy))}
+ onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false)}}
+ value={vj.arrival_time['minute']}
+ />
+ </span>
+ </div>
+ }
+ <div className={(this.columnHasDelta() ? '' : 'hidden')}>
+ {(vj.delta != 0) &&
+ <span className='sb sb-chrono sb-lg text-warning' data-textinside={vj.delta}></span>
+ }
+ </div>
+ <div data-headline='Arrivée à'>
+ <span className={(this.isDisabled(this.props.value.deletable, vj.dummy) ? 'disabled ' : '') + 'input-group time'}>
+ <input
+ type='number'
+ min='00'
+ max='23'
+ className='form-control'
+ disabled={this.isDisabled(this.props.value.deletable, vj.dummy)}
+ onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals)}}
+ value={vj.departure_time['hour']}
+ />
+ <span>:</span>
+ <input
+ type='number'
+ min='00'
+ max='59'
+ className='form-control'
+ disabled={this.isDisabled(this.props.value.deletable, vj.dummy)}
+ onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals)}}
+ value={vj.departure_time['minute']}
+ />
+ </span>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+ )
+ }
+}
+
+VehicleJourney.propTypes = {
+ value: PropTypes.object.isRequired,
+ filters: PropTypes.object.isRequired,
+ index: PropTypes.number.isRequired,
+ onUpdateTime: PropTypes.func.isRequired,
+ onSelectVehicleJourney: PropTypes.func.isRequired
+}
+
+module.exports = VehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js
new file mode 100644
index 000000000..ddf40b88d
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js
@@ -0,0 +1,142 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var VehicleJourney = require('./VehicleJourney')
+
+class VehicleJourneys extends Component{
+ constructor(props){
+ super(props)
+ this.previousCity = undefined
+ }
+ componentDidMount() {
+ this.props.onLoadFirstPage()
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if(this.props.status.isFetching == false){
+ $('.table-2entries').each(function() {
+ var refH = []
+ var refCol = []
+
+ $(this).find('.t2e-head').children('div').each(function() {
+ var h = $(this).outerHeight();
+ refH.push(h)
+ });
+
+ var i = 0
+ $(this).find('.t2e-item').children('div').each(function() {
+ var h = $(this).outerHeight();
+ if(refCol.length < refH.length){
+ refCol.push(h)
+ } else {
+ if(h > refCol[i]) {
+ refCol[i] = h
+ }
+ }
+ if(i == (refH.length - 1)){
+ i = 0
+ } else {
+ i++
+ }
+ });
+
+ for(var n = 0; n < refH.length; n++) {
+ if(refCol[n] < refH[n]) {
+ refCol[n] = refH[n]
+ }
+ }
+
+ $(this).find('.th').css('height', refCol[0]);
+
+ for(var nth = 1; nth < refH.length; nth++) {
+ $(this).find('.td:nth-child('+ (nth + 1) +')').css('height', refCol[nth]);
+ }
+ });
+ }
+ }
+
+ cityNameChecker(sp) {
+ let bool = false
+ if(sp.city_name != this.previousCity){
+ bool = true
+ this.previousCity = sp.city_name
+ }
+ return (
+ <div
+ className={(bool) ? 'headlined' : ''}
+ data-headline={(bool) ? sp.city_name : ''}
+ title={sp.city_name + ' (' + sp.zip_code +')'}
+ >
+ <span><span>{sp.name}</span></span>
+ </div>
+ )
+ }
+
+ render() {
+ this.previousCity = undefined
+
+ if(this.props.status.isFetching == true) {
+ return (
+ <div className="isLoading" style={{marginTop: 80, marginBottom: 80}}>
+ <div className="loader"></div>
+ </div>
+ )
+ } else {
+ return (
+ <div className='row'>
+ <div className='col-lg-12'>
+ {(this.props.status.fetchSuccess == false) && (
+ <div className='alert alert-danger'>
+ <strong>Erreur : </strong>
+ la récupération des missions a rencontré un problème. Rechargez la page pour tenter de corriger le problème.
+ </div>
+ )}
+
+ <div className='table table-2entries mt-sm mb-sm'>
+ <div className='t2e-head w20'>
+ <div className='th'>
+ <div className='strong mb-xs'>ID course</div>
+ <div>ID mission</div>
+ <div>Calendriers</div>
+ </div>
+ {this.props.stopPointsList.map((sp, i) =>{
+ return (
+ <div key={i} className='td'>
+ {this.cityNameChecker(sp)}
+ </div>
+ )
+ })}
+ </div>
+
+ <div className='t2e-item-list w80'>
+ <div>
+ {this.props.vehicleJourneys.map((vj, index) =>
+ <VehicleJourney
+ value={vj}
+ key={index}
+ index={index}
+ filters={this.props.filters}
+ onUpdateTime={this.props.onUpdateTime}
+ onSelectVehicleJourney={this.props.onSelectVehicleJourney}
+ />
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+ }
+ }
+}
+
+VehicleJourneys.propTypes = {
+ status: PropTypes.object.isRequired,
+ filters: PropTypes.object.isRequired,
+ stopPointsList: PropTypes.array.isRequired,
+ onLoadFirstPage: PropTypes.func.isRequired,
+ onUpdateTime: PropTypes.func.isRequired,
+ onSelectVehicleJourney: PropTypes.func.isRequired
+}
+
+module.exports = VehicleJourneys
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/CalendarsEditVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/CalendarsEditVehicleJourney.js
new file mode 100644
index 000000000..19a5af869
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/CalendarsEditVehicleJourney.js
@@ -0,0 +1,147 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var actions = require('../../actions')
+var TimetableSelect2 = require('./select2s/TimetableSelect2')
+
+class CalendarsEditVehicleJourney extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+ handleSubmit() {
+ this.props.onCalendarsEditVehicleJourney(this.props.modal.modalProps.vehicleJourneys)
+ this.props.onModalClose()
+ $('#CalendarsEditVehicleJourneyModal').modal('hide')
+ }
+
+ render() {
+ if(this.props.status.isFetching == true) {
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <li className='st_action'>
+ <a
+ href='#'
+ className={(actions.getSelected(this.props.vehicleJourneys).length > 0 && this.props.filters.policy['vehicle_journeys.edit']) ? '' : 'disabled'}
+ data-toggle='modal'
+ data-target='#CalendarsEditVehicleJourneyModal'
+ onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))}
+ >
+ <span className='fa fa-calendar'></span>
+ </a>
+
+ <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='CalendarsEditVehicleJourneyModal'>
+ <div className='modal-container'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>Calendriers associés</h4>
+ </div>
+
+ {(this.props.modal.type == 'calendars_edit') && (
+ <form>
+ <div className='modal-body'>
+ <div className='row'>
+ <div className='col-lg-12'>
+ <div className='subform'>
+ <div className='nested-head'>
+ <div className='wrapper'>
+ <div>
+ <div className='form-group'>
+ <label className='control-label'>Calendriers associés</label>
+ </div>
+ </div>
+ <div></div>
+ </div>
+ </div>
+ {this.props.modal.modalProps.timetables.map((tt, i) =>
+ <div className='nested-fields' key={i}>
+ <div className='wrapper'>
+ <div>{tt.comment}</div>
+ <div>
+ <a
+ href='#'
+ title='Supprimer'
+ className='fa fa-trash remove_fields'
+ style={{height: 'auto', lineHeight: 'normal'}}
+ onClick={(e) => {
+ e.preventDefault()
+ this.props.onDeleteCalendarModal(tt)
+ }}
+ ></a>
+ </div>
+ </div>
+ </div>
+ )}
+ <div className='nested-fields'>
+ <div className='wrapper'>
+ <div>
+ <TimetableSelect2
+ onSelect2Timetable={this.props.onSelect2Timetable}
+ chunkURL={'/autocomplete_time_tables.json'}
+ isFilter={false}
+ />
+ </div>
+ <div>
+ <a
+ href='#'
+ title='Ajouter'
+ className='fa fa-plus'
+ onClick={(e) => {
+ e.preventDefault()
+ this.props.onAddSelectedTimetable()
+ }}
+ ></a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div className='modal-footer'>
+ <button
+ className='btn btn-link'
+ data-dismiss='modal'
+ type='button'
+ onClick={this.props.onModalClose}
+ >
+ Annuler
+ </button>
+ <button
+ className='btn btn-primary'
+ type='button'
+ onClick={this.handleSubmit.bind(this)}
+ >
+ Valider
+ </button>
+ </div>
+ </form>
+ )}
+
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+CalendarsEditVehicleJourney.propTypes = {
+ onOpenCalendarsEditModal: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onCalendarsEditVehicleJourney: PropTypes.func.isRequired,
+ onDeleteCalendarModal: PropTypes.func.isRequired,
+ onSelect2Timetable: PropTypes.func.isRequired,
+ onAddSelectedTimetable: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired
+}
+
+module.exports = CalendarsEditVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/CreateModal.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/CreateModal.js
new file mode 100644
index 000000000..0c083c897
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/CreateModal.js
@@ -0,0 +1,113 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var actions = require('../../actions')
+var MissionSelect2 = require('./select2s/MissionSelect2')
+
+class CreateModal extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+ handleSubmit() {
+ if(actions.validateFields(this.refs) == true && this.props.modal.modalProps.selectedJPModal) {
+ this.props.onAddVehicleJourney(this.refs, this.props.modal.modalProps.selectedJPModal)
+ this.props.onModalClose()
+ $('#NewVehicleJourneyModal').modal('hide')
+ }
+ }
+
+ render() {
+ if(this.props.status.isFetching == true) {
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <li className='st_action'>
+ <a
+ href='#'
+ className={((this.props.filters.policy['vehicle_journeys.create']) ? '' : 'disabled')}
+ data-toggle='modal'
+ data-target='#NewVehicleJourneyModal'
+ onClick={this.props.onOpenCreateModal}
+ >
+ <span className='fa fa-plus'></span>
+ </a>
+
+ <div className={ 'modal fade ' + ((this.props.modal.type == 'create') ? 'in' : '') } id='NewVehicleJourneyModal'>
+ <div className='modal-container'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>Ajouter une mission</h4>
+ </div>
+
+ {(this.props.modal.type == 'create') && (
+ <form>
+ <div className='modal-body'>
+ <div className='row'>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label is-required'>Nom de la course</label>
+ <input
+ type='text'
+ ref='published_journey_name'
+ className='form-control'
+ onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ required
+ />
+ </div>
+ </div>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label is-required'>ID de la mission</label>
+ <MissionSelect2
+ onSelect2JourneyPattern={this.props.onSelect2JourneyPattern}
+ isFilter={false}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='modal-footer'>
+ <button
+ className='btn btn-link'
+ data-dismiss='modal'
+ type='button'
+ onClick={this.props.onModalClose}
+ >
+ Annuler
+ </button>
+ <button
+ className='btn btn-primary'
+ type='button'
+ onClick={this.handleSubmit.bind(this)}
+ >
+ Valider
+ </button>
+ </div>
+ </form>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+CreateModal.propTypes = {
+ index: PropTypes.number,
+ modal: PropTypes.object.isRequired,
+ status: PropTypes.object.isRequired,
+ onOpenCreateModal: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onAddVehicleJourney: PropTypes.func.isRequired,
+ onSelect2JourneyPattern: PropTypes.func.isRequired
+}
+
+module.exports = CreateModal
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DeleteVehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DeleteVehicleJourneys.js
new file mode 100644
index 000000000..e2425cc22
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DeleteVehicleJourneys.js
@@ -0,0 +1,29 @@
+var React = require('react')
+var PropTypes = require('react').PropTypes
+var actions = require('../../actions')
+
+const DeleteVehicleJourneys = ({onDeleteVehicleJourneys, vehicleJourneys, filters}) => {
+ return (
+ <li className='st_action'>
+ <a
+ href='#'
+ className={(actions.getSelected(vehicleJourneys).length > 0 && filters.policy['vehicle_journeys.destroy']) ? '' : 'disabled'}
+ onClick={e => {
+ e.preventDefault()
+ onDeleteVehicleJourneys()
+ }}
+ title='Supprimer'
+ >
+ <span className='fa fa-trash'></span>
+ </a>
+ </li>
+ )
+}
+
+DeleteVehicleJourneys.propTypes = {
+ onDeleteVehicleJourneys: PropTypes.func.isRequired,
+ vehicleJourneys: PropTypes.array.isRequired,
+ filters: PropTypes.object.isRequired
+}
+
+module.exports = DeleteVehicleJourneys
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js
new file mode 100644
index 000000000..b0cb1c850
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js
@@ -0,0 +1,118 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var actions = require('../../actions')
+
+class DuplicateVehicleJourney extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+ handleSubmit() {
+ if(actions.validateFields(this.refs) == true) {
+ this.props.onDuplicateVehicleJourney(this.refs)
+ this.props.onModalClose()
+ $('#DuplicateVehicleJourneyModal').modal('hide')
+ }
+ }
+
+ render() {
+ if(this.props.status.isFetching == true) {
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <li className='st_action'>
+ <a
+ href='#'
+ className={((actions.getSelected(this.props.vehicleJourneys).length == 1 && this.props.filters.policy['vehicle_journeys.edit']) ? '' : 'disabled')}
+ data-toggle='modal'
+ data-target='#DuplicateVehicleJourneyModal'
+ onClick={this.props.onOpenDuplicateModal}
+ >
+ <span className='fa fa-files-o'></span>
+ </a>
+
+ <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='DuplicateVehicleJourneyModal'>
+ <div className='modal-container'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>Dupliquer une course</h4>
+ {(this.props.modal.type == 'duplicate') && (
+ <em>Dupliquer les horaires de la course {actions.getSelected(this.props.vehicleJourneys)[0].objectid}</em>
+ )}
+ </div>
+
+ {(this.props.modal.type == 'duplicate') && (
+ <form>
+ <div className='modal-body'>
+ <div className='row'>
+ <div className='col-lg-8 col-md-8 col-sm-8 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label is-required'>Nombre de courses à créer et dupliquer</label>
+ <input
+ type='number'
+ ref='duplicate_number'
+ min='1'
+ max='20'
+ className='form-control'
+ onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ required
+ />
+ </div>
+ </div>
+ <div className='col-lg-4 col-md-4 col-sm-4 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label is-required'>Avec un décalage de</label>
+ <input
+ type='number'
+ ref='additional_time'
+ min='0'
+ max='59'
+ className='form-control'
+ onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ required
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='modal-footer'>
+ <button
+ className='btn btn-link'
+ data-dismiss='modal'
+ type='button'
+ onClick={this.props.onModalClose}
+ >
+ Annuler
+ </button>
+ <button
+ className='btn btn-primary'
+ type='button'
+ onClick={this.handleSubmit.bind(this)}
+ >
+ Valider
+ </button>
+ </div>
+ </form>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+DuplicateVehicleJourney.propTypes = {
+ onOpenDuplicateModal: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired
+}
+
+module.exports = DuplicateVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js
new file mode 100644
index 000000000..19482258d
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js
@@ -0,0 +1,136 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var actions = require('../../actions')
+
+class EditVehicleJourney extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+ handleSubmit() {
+ if(actions.validateFields(this.refs) == true) {
+ this.props.onEditVehicleJourney(this.refs)
+ this.props.onModalClose()
+ $('#EditVehicleJourneyModal').modal('hide')
+ }
+ }
+
+ render() {
+ if(this.props.status.isFetching == true) {
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <li className='st_action'>
+ <a
+ href='#'
+ className={(actions.getSelected(this.props.vehicleJourneys).length == 1 && this.props.filters.policy['vehicle_journeys.edit']) ? '' : 'disabled'}
+ data-toggle='modal'
+ data-target='#EditVehicleJourneyModal'
+ onClick={() => this.props.onOpenEditModal(actions.getSelected(this.props.vehicleJourneys)[0])}
+ >
+ <span className='fa fa-info'></span>
+ </a>
+
+ <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='EditVehicleJourneyModal'>
+ <div className='modal-container'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>Informations</h4>
+ </div>
+
+ {(this.props.modal.type == 'edit') && (
+ <form>
+ <div className='modal-body'>
+ <div className='row'>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label is-required'>Intitulé de la course</label>
+ <input
+ type='text'
+ ref='published_journey_name'
+ className='form-control'
+ defaultValue={this.props.modal.modalProps.vehicleJourney.published_journey_name}
+ onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ required
+ />
+ </div>
+ </div>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label'>Mission</label>
+ <input
+ type='text'
+ className='form-control'
+ value={(this.props.modal.modalProps.vehicleJourney.journey_pattern.objectid) + ' - ' + (this.props.modal.modalProps.vehicleJourney.journey_pattern.name)}
+ disabled={true}
+ />
+ </div>
+ </div>
+ </div>
+
+ <div className='row'>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <label className='control-label is-required'>Numéro de train</label>
+ <input
+ type='text'
+ ref='published_journey_identifier'
+ className='form-control'
+ defaultValue={this.props.modal.modalProps.vehicleJourney.published_journey_identifier}
+ onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ required
+ />
+ </div>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <label className='control-label'>Transporteur</label>
+ <input
+ type='text'
+ className='form-control'
+ value={this.props.modal.modalProps.vehicleJourney.company_id}
+ disabled={true}
+ />
+ </div>
+ </div>
+ </div>
+
+ <div className='modal-footer'>
+ <button
+ className='btn btn-link'
+ data-dismiss='modal'
+ type='button'
+ onClick={this.props.onModalClose}
+ >
+ Annuler
+ </button>
+ <button
+ className='btn btn-primary'
+ type='button'
+ onClick={this.handleSubmit.bind(this)}
+ >
+ Valider
+ </button>
+ </div>
+ </form>
+ )}
+
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+EditVehicleJourney.propTypes = {
+ onOpenEditModal: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired
+}
+
+module.exports = EditVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/NotesEditVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/NotesEditVehicleJourney.js
new file mode 100644
index 000000000..7c5df3333
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/NotesEditVehicleJourney.js
@@ -0,0 +1,124 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var actions = require('../../actions')
+
+class NotesEditVehicleJourney extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+ handleSubmit() {
+ this.props.onNotesEditVehicleJourney(this.props.modal.modalProps.vehicleJourney.footnotes)
+ this.props.onModalClose()
+ $('#NotesEditVehicleJourneyModal').modal('hide')
+ }
+
+ renderFootnoteButton(lf, vjArray){
+ let footnote_id = undefined
+ vjArray.forEach((f) => {
+ if(f.id == lf.id){
+ footnote_id = f.id
+ }
+ })
+
+ if(footnote_id){
+ return <button
+ type='button'
+ className='btn btn-outline-danger btn-xs'
+ onClick={() => this.props.onToggleFootnoteModal(lf, false)}
+ ><span className="fa fa-trash"></span></button>
+ }else{
+ return <button
+ type='button'
+ className='btn btn-outline-primary btn-xs'
+ onClick={() => this.props.onToggleFootnoteModal(lf, true)}
+ ><span className="fa fa-plus"></span></button>
+ }
+ }
+
+ render() {
+ if(this.props.status.isFetching == true) {
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <li className='st_action'>
+ <a
+ href='#'
+ className={(actions.getSelected(this.props.vehicleJourneys).length == 1 && this.props.filters.policy['vehicle_journeys.edit']) ? '' : 'disabled'}
+ data-toggle='modal'
+ data-target='#NotesEditVehicleJourneyModal'
+ onClick={() => this.props.onOpenNotesEditModal(actions.getSelected(this.props.vehicleJourneys)[0])}
+ >
+ <span className='fa fa-sticky-note'></span>
+ </a>
+
+ <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='NotesEditVehicleJourneyModal'>
+ <div className='modal-container'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>Notes</h4>
+ </div>
+
+ {(this.props.modal.type == 'notes_edit') && (
+ <form>
+ <div className='modal-body'>
+ {window.line_footnotes.map((lf, i) =>
+ <div
+ key={i}
+ className='panel panel-default'
+ >
+ <div className='panel-heading'>
+ <h4 className='panel-title clearfix'>
+ <div className='pull-left' style={{paddingTop: '3px'}}>{lf.label}</div>
+ <div className='pull-right'>{this.renderFootnoteButton(lf, this.props.modal.modalProps.vehicleJourney.footnotes)}</div>
+ </h4>
+ </div>
+ <div className='panel-body'><p>{lf.code}</p></div>
+ </div>
+ )}
+ </div>
+
+ <div className='modal-footer'>
+ <button
+ className='btn btn-link'
+ data-dismiss='modal'
+ type='button'
+ onClick={this.props.onModalClose}
+ >
+ Annuler
+ </button>
+ <button
+ className='btn btn-primary'
+ type='button'
+ onClick={this.handleSubmit.bind(this)}
+ >
+ Valider
+ </button>
+ </div>
+ </form>
+ )}
+
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+NotesEditVehicleJourney.propTypes = {
+ onOpenNotesEditModal: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onToggleFootnoteModal: PropTypes.func.isRequired,
+ onNotesEditVehicleJourney: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired
+}
+
+module.exports = NotesEditVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js
new file mode 100644
index 000000000..a373ed1e5
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js
@@ -0,0 +1,104 @@
+var React = require('react')
+var Component = require('react').Component
+var PropTypes = require('react').PropTypes
+var actions = require('../../actions')
+
+class ShiftVehicleJourney extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+ handleSubmit() {
+ if(actions.validateFields(this.refs) == true) {
+ this.props.onShiftVehicleJourney(this.refs)
+ this.props.onModalClose()
+ $('#ShiftVehicleJourneyModal').modal('hide')
+ }
+ }
+
+ render() {
+ if(this.props.status.isFetching == true) {
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <li className='st_action'>
+ <a
+ href='#'
+ className={(actions.getSelected(this.props.vehicleJourneys).length == 1 && this.props.filters.policy['vehicle_journeys.edit']) ? '' : 'disabled'}
+ data-toggle='modal'
+ data-target='#ShiftVehicleJourneyModal'
+ onClick={this.props.onOpenShiftModal}
+ >
+ <span className='sb sb-update-vj'></span>
+ </a>
+
+ <div className={ 'modal fade ' + ((this.props.modal.type == 'shift') ? 'in' : '') } id='ShiftVehicleJourneyModal'>
+ <div className='modal-container'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>Mettre à jour une course</h4>
+ {(this.props.modal.type == 'shift') && (
+ <em>Mettre à jour les horaires de la course {actions.getSelected(this.props.vehicleJourneys)[0].objectid}</em>
+ )}
+ </div>
+
+ {(this.props.modal.type == 'shift') && (
+ <form>
+ <div className='modal-body'>
+ <div className='row'>
+ <div className='col-lg-4 col-lg-offset-4 col-md-4 col-md-offset-4 col-sm-4 col-sm-offset-4 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label is-required'>Avec un décalage de</label>
+ <input
+ type='number'
+ ref='additional_time'
+ min='0'
+ max='59'
+ className='form-control'
+ onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ required
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='modal-footer'>
+ <button
+ className='btn btn-link'
+ data-dismiss='modal'
+ type='button'
+ onClick={this.props.onModalClose}
+ >
+ Annuler
+ </button>
+ <button
+ className='btn btn-primary'
+ type='button'
+ onClick={this.handleSubmit.bind(this)}
+ >
+ Valider
+ </button>
+ </div>
+ </form>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+ShiftVehicleJourney.propTypes = {
+ onOpenShiftModal: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired
+}
+
+module.exports = ShiftVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/MissionSelect2.js
new file mode 100644
index 000000000..2b4e1cd80
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/MissionSelect2.js
@@ -0,0 +1,62 @@
+var React = require('react')
+var PropTypes = require('react').PropTypes
+var Select2 = require('react-select2')
+
+// get JSON full path
+var origin = window.location.origin
+var path = window.location.pathname.split('/', 7).join('/')
+
+
+class BSelect4 extends React.Component{
+ constructor(props) {
+ super(props)
+ }
+
+ render() {
+ return (
+ <Select2
+ data={(this.props.isFilter) ? [this.props.filters.query.journeyPattern.published_name] : undefined}
+ value={(this.props.isFilter) ? this.props.filters.query.journeyPattern.published_name : undefined}
+ onSelect={(e) => this.props.onSelect2JourneyPattern(e)}
+ multiple={false}
+ ref='journey_pattern_id'
+ options={{
+ allowClear: false,
+ theme: 'bootstrap',
+ placeholder: 'Filtrer par mission...',
+ width: '100%',
+ ajax: {
+ url: origin + path + '/journey_patterns_collection.json',
+ dataType: 'json',
+ delay: '500',
+ data: function(params) {
+ return {
+ q: {published_name_cont: params.term},
+ };
+ },
+ processResults: function(data, params) {
+ return {
+ results: data.map(
+ item => Object.assign(
+ {},
+ item,
+ {text: item.published_name}
+ )
+ )
+ };
+ },
+ cache: true
+ },
+ minimumInputLength: 3,
+ templateResult: formatRepo
+ }}
+ />
+ )
+ }
+}
+
+const formatRepo = (props) => {
+ if(props.text) return props.text
+}
+
+module.exports = BSelect4
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/TimetableSelect2.js
new file mode 100644
index 000000000..fd1e30afb
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/TimetableSelect2.js
@@ -0,0 +1,63 @@
+var React = require('react')
+var PropTypes = require('react').PropTypes
+var Select2 = require('react-select2')
+
+// get JSON full path
+var origin = window.location.origin
+var path = window.location.pathname.split('/', 3).join('/')
+
+
+class BSelect4 extends React.Component{
+ constructor(props) {
+ super(props)
+ }
+
+ render() {
+ return (
+ <Select2
+ data={(this.props.isFilter) ? [this.props.filters.query.timetable.comment] : undefined}
+ value={(this.props.isFilter) ? this.props.filters.query.timetable.comment : undefined}
+ onSelect={(e) => this.props.onSelect2Timetable(e) }
+ multiple={false}
+ ref='timetable_id'
+ options={{
+ allowClear: false,
+ theme: 'bootstrap',
+ width: '100%',
+ placeholder: 'Filtrer par calendrier...',
+ ajax: {
+ url: origin + path + this.props.chunkURL,
+ dataType: 'json',
+ delay: '500',
+ data: function(params) {
+ return {
+ q: {comment_cont: params.term},
+ };
+ },
+ processResults: function(data, params) {
+
+ return {
+ results: data.map(
+ item => Object.assign(
+ {},
+ item,
+ {text: item.comment}
+ )
+ )
+ };
+ },
+ cache: true
+ },
+ minimumInputLength: 3,
+ templateResult: formatRepo
+ }}
+ />
+ )
+ }
+}
+
+const formatRepo = (props) => {
+ if(props.text) return props.text
+}
+
+module.exports = BSelect4
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/ConfirmModal.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/ConfirmModal.js
new file mode 100644
index 000000000..e91ba6bf0
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/ConfirmModal.js
@@ -0,0 +1,30 @@
+var actions = require('../actions')
+var connect = require('react-redux').connect
+var ConfirmModal = require('../components/ConfirmModal')
+
+const mapStateToProps = (state) => {
+ return {
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalAccept: (next, state) =>{
+ dispatch(actions.fetchingApi())
+ actions.submitVehicleJourneys(dispatch, state, next)
+ },
+ onModalCancel: (next) =>{
+ dispatch(actions.fetchingApi())
+ dispatch(next)
+ },
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ }
+ }
+}
+
+const ConfirmModalContainer = connect(mapStateToProps, mapDispatchToProps)(ConfirmModal)
+
+module.exports = ConfirmModalContainer
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Filters.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Filters.js
new file mode 100644
index 000000000..7570dd466
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Filters.js
@@ -0,0 +1,42 @@
+var actions = require('../actions')
+var connect = require('react-redux').connect
+var Filters = require('../components/Filters')
+
+const mapStateToProps = (state) => {
+ return {
+ filters: state.filters,
+ pagination: state.pagination
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onUpdateStartTimeFilter: (e, unit) =>{
+ e.preventDefault()
+ dispatch(actions.updateStartTimeFilter(e.target.value, unit))
+ },
+ onUpdateEndTimeFilter: (e, unit) =>{
+ e.preventDefault()
+ dispatch(actions.updateEndTimeFilter(e.target.value, unit))
+ },
+ onToggleWithoutSchedule: () =>{
+ dispatch(actions.toggleWithoutSchedule())
+ },
+ onResetFilters: (e, pagination) =>{
+ dispatch(actions.checkConfirmModal(e, actions.resetFilters(dispatch), pagination.stateChanged, dispatch))
+ },
+ onFilter: (e, pagination) =>{
+ dispatch(actions.checkConfirmModal(e, actions.filterQuery(dispatch), pagination.stateChanged, dispatch))
+ },
+ onSelect2Timetable: (e) => {
+ dispatch(actions.filterSelect2Timetable(e.params.data))
+ },
+ onSelect2JourneyPattern: (e) => {
+ dispatch(actions.filterSelect2JourneyPattern(e.params.data))
+ }
+ }
+}
+
+const FiltersList = connect(mapStateToProps, mapDispatchToProps)(Filters)
+
+module.exports = FiltersList
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Modal.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Modal.js
new file mode 100644
index 000000000..bb89ba92a
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Modal.js
@@ -0,0 +1,23 @@
+var connect = require('react-redux').connect
+var EditModal = require('../components/EditModal')
+var CreateModal = require('../components/CreateModal')
+var actions = require('../actions')
+
+const mapStateToProps = (state) => {
+ return {
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ }
+ }
+}
+
+const ModalContainer = connect(mapStateToProps, mapDispatchToProps)(CreateModal)
+
+module.exports = ModalContainer
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Navigate.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Navigate.js
new file mode 100644
index 000000000..a3eca13c8
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Navigate.js
@@ -0,0 +1,18 @@
+var React = require('react')
+var connect = require('react-redux').connect
+var actions = require('../actions')
+var NavigateComponent = require('../components/Navigate')
+
+const mapStateToProps = (state) => {
+ return {
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ pagination: state.pagination,
+ filters: state.filters
+ }
+}
+
+
+const Navigate = connect(mapStateToProps)(NavigateComponent)
+
+module.exports = Navigate
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js
new file mode 100644
index 000000000..5af30ab82
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js
@@ -0,0 +1,16 @@
+var React = require('react')
+var connect = require('react-redux').connect
+var actions = require('../actions')
+var SaveVehicleJourneysComponent = require('../components/SaveVehicleJourneys')
+
+const mapStateToProps = (state) => {
+ return {
+ vehicleJourneys: state.vehicleJourneys,
+ page: state.pagination.page,
+ status: state.status
+ }
+}
+
+const SaveVehicleJourneys = connect(mapStateToProps)(SaveVehicleJourneysComponent)
+
+module.exports = SaveVehicleJourneys
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/ToggleArrivals.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/ToggleArrivals.js
new file mode 100644
index 000000000..716485dbf
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/ToggleArrivals.js
@@ -0,0 +1,21 @@
+var actions = require('../actions')
+var connect = require('react-redux').connect
+var ToggleArrivals = require('../components/ToggleArrivals')
+
+const mapStateToProps = (state) => {
+ return {
+ filters: state.filters
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onToggleArrivals: () =>{
+ dispatch(actions.toggleArrivals())
+ }
+ }
+}
+
+const ToggleArrivalsList = connect(mapStateToProps, mapDispatchToProps)(ToggleArrivals)
+
+module.exports = ToggleArrivalsList
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js
new file mode 100644
index 000000000..35f492c98
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js
@@ -0,0 +1,21 @@
+var connect = require('react-redux').connect
+var ToolsComponent = require('../components/Tools')
+var actions = require('../actions')
+
+const mapStateToProps = (state) => {
+ return {
+ vehicleJourneys: state.vehicleJourneys
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onCancelSelection: () => {
+ dispatch(actions.cancelSelection())
+ }
+ }
+}
+
+const Tools = connect(mapStateToProps, mapDispatchToProps)(ToolsComponent)
+
+module.exports = Tools
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js
new file mode 100644
index 000000000..7cedc3ec0
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js
@@ -0,0 +1,31 @@
+var actions = require('../actions')
+var connect = require('react-redux').connect
+var VehicleJourneys = require('../components/VehicleJourneys')
+
+const mapStateToProps = (state) => {
+ return {
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ filters: state.filters,
+ stopPointsList: state.stopPointsList
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onLoadFirstPage: () =>{
+ dispatch(actions.fetchingApi())
+ actions.fetchVehicleJourneys(dispatch)
+ },
+ onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => {
+ dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled))
+ },
+ onSelectVehicleJourney: (index) => {
+ dispatch(actions.selectVehicleJourney(index))
+ }
+ }
+}
+
+const VehicleJourneysList = connect(mapStateToProps, mapDispatchToProps)(VehicleJourneys)
+
+module.exports = VehicleJourneysList
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/AddVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/AddVehicleJourney.js
new file mode 100644
index 000000000..1c8cf1063
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/AddVehicleJourney.js
@@ -0,0 +1,33 @@
+var actions = require('../../actions')
+var connect = require('react-redux').connect
+var CreateModal = require('../../components/tools/CreateModal')
+
+const mapStateToProps = (state) => {
+ return {
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ filters: state.filters
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ },
+ onAddVehicleJourney: (data, selectedJourneyPattern) =>{
+ dispatch(actions.addVehicleJourney(data, selectedJourneyPattern))
+ },
+ onOpenCreateModal: () =>{
+ dispatch(actions.openCreateModal())
+ },
+ onSelect2JourneyPattern: (e) =>{
+ dispatch(actions.selectJPCreateModal(e.params.data))
+ }
+ }
+}
+
+const AddVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(CreateModal)
+
+module.exports = AddVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/CalendarsEditVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/CalendarsEditVehicleJourney.js
new file mode 100644
index 000000000..130acb017
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/CalendarsEditVehicleJourney.js
@@ -0,0 +1,39 @@
+var connect = require('react-redux').connect
+var CalendarsEditComponent = require('../../components/tools/CalendarsEditVehicleJourney')
+var actions = require('../../actions')
+
+const mapStateToProps = (state) => {
+ return {
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ filters: state.filters
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ },
+ onOpenCalendarsEditModal: (vehicleJourneys) =>{
+ dispatch(actions.openCalendarsEditModal(vehicleJourneys))
+ },
+ onDeleteCalendarModal: (timetable) => {
+ dispatch(actions.deleteCalendarModal(timetable))
+ },
+ onCalendarsEditVehicleJourney: (calendars) =>{
+ dispatch(actions.editVehicleJourneyCalendars(calendars))
+ },
+ onSelect2Timetable: (e) =>{
+ dispatch(actions.selectTTCalendarsModal(e.params.data))
+ },
+ onAddSelectedTimetable: () => {
+ dispatch(actions.addSelectedTimetable())
+ }
+ }
+}
+
+const CalendarsEditVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(CalendarsEditComponent)
+
+module.exports = CalendarsEditVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DeleteVehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DeleteVehicleJourneys.js
new file mode 100644
index 000000000..c012c9706
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DeleteVehicleJourneys.js
@@ -0,0 +1,22 @@
+var actions = require('../../actions')
+var connect = require('react-redux').connect
+var DeleteVJComponent = require('../../components/tools/DeleteVehicleJourneys')
+
+const mapStateToProps = (state) => {
+ return {
+ vehicleJourneys: state.vehicleJourneys,
+ filters: state.filters
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onDeleteVehicleJourneys: () =>{
+ dispatch(actions.deleteVehicleJourneys())
+ },
+ }
+}
+
+const DeleteVehicleJourneys = connect(mapStateToProps, mapDispatchToProps)(DeleteVJComponent)
+
+module.exports = DeleteVehicleJourneys
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js
new file mode 100644
index 000000000..6cf6f4039
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js
@@ -0,0 +1,30 @@
+var actions = require('../../actions')
+var connect = require('react-redux').connect
+var DuplicateVJComponent = require('../../components/tools/DuplicateVehicleJourney')
+
+const mapStateToProps = (state) => {
+ return {
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ filters: state.filters
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ },
+ onOpenDuplicateModal: () =>{
+ dispatch(actions.openDuplicateModal())
+ },
+ onDuplicateVehicleJourney: (data) =>{
+ dispatch(actions.duplicateVehicleJourney(data))
+ }
+ }
+}
+
+const DuplicateVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(DuplicateVJComponent)
+
+module.exports = DuplicateVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js
new file mode 100644
index 000000000..a8d1687e0
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js
@@ -0,0 +1,30 @@
+var connect = require('react-redux').connect
+var EditComponent = require('../../components/tools/EditVehicleJourney')
+var actions = require('../../actions')
+
+const mapStateToProps = (state) => {
+ return {
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ filters: state.filters
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ },
+ onOpenEditModal: (vj) =>{
+ dispatch(actions.openEditModal(vj))
+ },
+ onEditVehicleJourney: (data) =>{
+ dispatch(actions.editVehicleJourney(data))
+ }
+ }
+}
+
+const EditVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(EditComponent)
+
+module.exports = EditVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/NotesEditVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/NotesEditVehicleJourney.js
new file mode 100644
index 000000000..1619300d0
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/NotesEditVehicleJourney.js
@@ -0,0 +1,33 @@
+var connect = require('react-redux').connect
+var NotesEditComponent = require('../../components/tools/NotesEditVehicleJourney')
+var actions = require('../../actions')
+
+const mapStateToProps = (state) => {
+ return {
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ filters: state.filters
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ },
+ onOpenNotesEditModal: (vj) =>{
+ dispatch(actions.openNotesEditModal(vj))
+ },
+ onToggleFootnoteModal: (footnote, isShown) => {
+ dispatch(actions.toggleFootnoteModal(footnote, isShown))
+ },
+ onNotesEditVehicleJourney: (footnotes) =>{
+ dispatch(actions.editVehicleJourneyNotes(footnotes))
+ }
+ }
+}
+
+const NotesEditVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(NotesEditComponent)
+
+module.exports = NotesEditVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/ShiftVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/ShiftVehicleJourney.js
new file mode 100644
index 000000000..196f6722a
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/ShiftVehicleJourney.js
@@ -0,0 +1,30 @@
+var actions = require('../../actions')
+var connect = require('react-redux').connect
+var ShiftVJComponent = require('../../components/tools/ShiftVehicleJourney')
+
+const mapStateToProps = (state) => {
+ return {
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ filters: state.filters
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ },
+ onOpenShiftModal: () =>{
+ dispatch(actions.openShiftModal())
+ },
+ onShiftVehicleJourney: (data) =>{
+ dispatch(actions.shiftVehicleJourney(data))
+ }
+ }
+}
+
+const ShiftVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(ShiftVJComponent)
+
+module.exports = ShiftVehicleJourney
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/index.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/index.js
new file mode 100644
index 000000000..d0226caaa
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/index.js
@@ -0,0 +1,78 @@
+var React = require('react')
+var render = require('react-dom').render
+var Provider = require('react-redux').Provider
+var createStore = require('redux').createStore
+var vehicleJourneysApp = require('./reducers')
+var App = require('./components/App')
+var enableBatching = require('./batch').enableBatching
+
+// logger, DO NOT REMOVE
+var applyMiddleware = require('redux').applyMiddleware
+var createLogger = require('redux-logger')
+var thunkMiddleware = require('redux-thunk').default
+var promise = require('redux-promise')
+
+var selectedJP = []
+
+if (window.journeyPatternId)
+ selectedJP.push(window.journeyPatternId)
+
+var initialState = {
+ filters: {
+ selectedJourneyPatterns : selectedJP,
+ policy: window.perms,
+ toggleArrivals: false,
+ queryString: '',
+ query: {
+ interval: {
+ start:{
+ hour: '00',
+ minute: '00'
+ },
+ end:{
+ hour: '23',
+ minute: '59'
+ }
+ },
+ journeyPattern: {
+ published_name: ''
+ },
+ timetable: {
+ comment: ''
+ },
+ withoutSchedule: false
+ }
+
+ },
+ status: {
+ fetchSuccess: true,
+ isFetching: false
+ },
+ vehicleJourneys: [],
+ stopPointsList: window.stopPoints,
+ pagination: {
+ page : 1,
+ totalCount: window.vehicleJourneysLength,
+ perPage: window.vehicleJourneysPerPage,
+ stateChanged: false
+ },
+ modal: {
+ type: '',
+ modalProps: {},
+ confirmModal: {}
+ }
+}
+const loggerMiddleware = createLogger()
+
+let store = createStore(
+ enableBatching(vehicleJourneysApp),
+ initialState,
+ applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
+)
+
+render(
+ <Provider store={store}>
+ <App />
+ </Provider>,
+ document.getElementById('vehicle_journeys_wip')
+)
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/filters.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/filters.js
new file mode 100644
index 000000000..c2deaf019
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/filters.js
@@ -0,0 +1,66 @@
+var actions = require("../actions")
+let newQuery, newInterval
+
+const filters = (state = {}, action) => {
+ switch (action.type) {
+ case 'RESET_FILTERS':
+ let interval = {
+ start:{
+ hour: '00',
+ minute: '00'
+ },
+ end:{
+ hour: '23',
+ minute: '59'
+ }
+ }
+ newQuery = Object.assign({}, state.query, {interval: interval, journeyPattern: {}, timetable: {}, withoutSchedule: false })
+ return Object.assign({}, state, {query: newQuery, queryString: ''})
+ case 'TOGGLE_WITHOUT_SCHEDULE':
+ newQuery = Object.assign({}, state.query, {withoutSchedule: !state.query.withoutSchedule})
+ return Object.assign({}, state, {query: newQuery})
+ case 'UPDATE_END_TIME_FILTER':
+ newInterval = JSON.parse(JSON.stringify(state.query.interval))
+ newInterval.end[action.unit] = actions.pad(action.val)
+ if(parseInt(newInterval.start.hour + newInterval.start.minute) < parseInt(newInterval.end.hour + newInterval.end.minute)){
+ newQuery = Object.assign({}, state.query, {interval: newInterval})
+ return Object.assign({}, state, {query: newQuery})
+ }else{
+ return state
+ }
+ case 'UPDATE_START_TIME_FILTER':
+ newInterval = JSON.parse(JSON.stringify(state.query.interval))
+ newInterval.start[action.unit] = actions.pad(action.val)
+ if(parseInt(newInterval.start.hour + newInterval.start.minute) < parseInt(newInterval.end.hour + newInterval.end.minute)){
+ newQuery = Object.assign({}, state.query, {interval: newInterval})
+ return Object.assign({}, state, {query: newQuery})
+ }else{
+ return state
+ }
+ case 'SELECT_TT_FILTER':
+ newQuery = Object.assign({}, state.query, {timetable : action.selectedItem})
+ return Object.assign({}, state, {query: newQuery})
+ case 'SELECT_JP_FILTER':
+ newQuery = Object.assign({}, state.query, {journeyPattern : action.selectedItem})
+ return Object.assign({}, state, {query: newQuery})
+ case 'TOGGLE_ARRIVALS':
+ return Object.assign({}, state, {toggleArrivals: !state.toggleArrivals})
+ case 'QUERY_FILTER_VEHICLEJOURNEYS':
+ actions.fetchVehicleJourneys(action.dispatch, undefined, undefined, state.queryString)
+ return state
+ case 'CREATE_QUERY_STRING':
+ let params = {
+ 'q[journey_pattern_id_eq]': state.query.journeyPattern.id || undefined,
+ 'q[time_tables_id_eq]': state.query.timetable.id || undefined,
+ 'q[vehicle_journey_at_stops_departure_time_gteq]': (state.query.interval.start.hour + ':' + state.query.interval.start.minute),
+ 'q[vehicle_journey_at_stops_departure_time_lteq]': (state.query.interval.end.hour + ':' + state.query.interval.end.minute)
+ }
+ let esc = encodeURIComponent
+ let queryString = Object.keys(params).map((k) => esc(k) + '=' + esc(params[k])).join('&')
+ return Object.assign({}, state, {queryString: queryString})
+ default:
+ return state
+ }
+}
+
+module.exports = filters
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js
new file mode 100644
index 000000000..bd4d7226b
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js
@@ -0,0 +1,18 @@
+var combineReducers = require('redux').combineReducers
+var vehicleJourneys = require('./vehicleJourneys')
+var pagination = require('./pagination')
+var modal = require('./modal')
+var status = require('./status')
+var filters = require('./filters')
+var stopPointsList = require('./stopPointsList')
+
+const vehicleJourneysApp = combineReducers({
+ vehicleJourneys,
+ pagination,
+ modal,
+ status,
+ filters,
+ stopPointsList
+})
+
+module.exports = vehicleJourneysApp
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js
new file mode 100644
index 000000000..de424c25e
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js
@@ -0,0 +1,130 @@
+var _ = require('lodash')
+let vehicleJourneysModal, newModalProps
+const modal = (state = {}, action) => {
+ switch (action.type) {
+ case 'OPEN_CONFIRM_MODAL':
+ $('#ConfirmModal').modal('show')
+ return Object.assign({}, state, {
+ type: 'confirm',
+ confirmModal: {
+ callback: action.callback,
+ }
+ })
+ case 'EDIT_NOTES_VEHICLEJOURNEY_MODAL':
+ let vehicleJourneyModal = Object.assign({}, action.vehicleJourney)
+ return {
+ type: 'notes_edit',
+ modalProps: {
+ vehicleJourney: vehicleJourneyModal
+ },
+ confirmModal: {}
+ }
+ case 'TOGGLE_FOOTNOTE_MODAL':
+ newModalProps = JSON.parse(JSON.stringify(state.modalProps))
+ if (action.isShown){
+ newModalProps.vehicleJourney.footnotes.push(action.footnote)
+ }else{
+ newModalProps.vehicleJourney.footnotes = newModalProps.vehicleJourney.footnotes.filter((f) => {return f.id != action.footnote.id })
+ }
+ return Object.assign({}, state, {modalProps: newModalProps})
+ case 'EDIT_VEHICLEJOURNEY_MODAL':
+ return {
+ type: 'edit',
+ modalProps: {
+ vehicleJourney: action.vehicleJourney
+ },
+ confirmModal: {}
+ }
+ case 'EDIT_CALENDARS_VEHICLEJOURNEY_MODAL':
+ vehicleJourneysModal = JSON.parse(JSON.stringify(action.vehicleJourneys))
+ let uniqTimetables = []
+ let timetable = {}
+ vehicleJourneysModal.map((vj, i) => {
+ vj.time_tables.map((tt, j) =>{
+ if(!(_.find(uniqTimetables, tt))){
+ uniqTimetables.push(tt)
+ }
+ })
+ })
+ return {
+ type: 'calendars_edit',
+ modalProps: {
+ vehicleJourneys: vehicleJourneysModal,
+ timetables: uniqTimetables
+ },
+ confirmModal: {}
+ }
+ case 'SELECT_TT_CALENDAR_MODAL':
+ newModalProps = Object.assign({}, state.modalProps, {selectedTimetable : action.selectedItem})
+ return Object.assign({}, state, {modalProps: newModalProps})
+ case 'ADD_SELECTED_TIMETABLE':
+ if(state.modalProps.selectedTimetable){
+ newModalProps = JSON.parse(JSON.stringify(state.modalProps))
+ newModalProps.vehicleJourneys.map((vj) => {
+ let isPresent = false
+ vj.time_tables.forEach((tt) =>{
+ if (_.isEqual(newModalProps.selectedTimetable.objectid, tt.objectid)){
+ isPresent = true
+ }
+ })
+ if (!isPresent){
+ vj.time_tables.push(newModalProps.selectedTimetable)
+ }
+ })
+ if (!_.find(newModalProps.timetables, newModalProps.selectedTimetable)){
+ newModalProps.timetables.push(newModalProps.selectedTimetable)
+ }
+ return Object.assign({}, state, {modalProps: newModalProps})
+ }
+ case 'DELETE_CALENDAR_MODAL':
+ newModalProps = JSON.parse(JSON.stringify(state.modalProps))
+ let timetablesModal = state.modalProps.timetables.slice(0)
+ timetablesModal.map((tt, i) =>{
+ if(tt == action.timetable){
+ timetablesModal.splice(i, 1)
+ }
+ })
+ vehicleJourneysModal = state.modalProps.vehicleJourneys.slice(0)
+ vehicleJourneysModal.map((vj) =>{
+ vj.time_tables.map((tt, i) =>{
+ if (_.isEqual(tt, action.timetable)){
+ vj.time_tables.splice(i, 1)
+ }
+ })
+ })
+ newModalProps.vehicleJourneys = vehicleJourneysModal
+ newModalProps.timetables = timetablesModal
+ return Object.assign({}, state, {modalProps: newModalProps})
+ case 'CREATE_VEHICLEJOURNEY_MODAL':
+ return {
+ type: 'create',
+ modalProps: {},
+ confirmModal: {}
+ }
+ case 'SELECT_JP_CREATE_MODAL':
+ newModalProps = {selectedJPModal : action.selectedItem}
+ return Object.assign({}, state, {modalProps: newModalProps})
+ case 'SHIFT_VEHICLEJOURNEY_MODAL':
+ return {
+ type: 'shift',
+ modalProps: {},
+ confirmModal: {}
+ }
+ case 'DUPLICATE_VEHICLEJOURNEY_MODAL':
+ return {
+ type: 'duplicate',
+ modalProps: {},
+ confirmModal: {}
+ }
+ case 'CLOSE_MODAL':
+ return {
+ type: '',
+ modalProps: {},
+ confirmModal: {}
+ }
+ default:
+ return state
+ }
+}
+
+module.exports = modal
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/pagination.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/pagination.js
new file mode 100644
index 000000000..203e37e5a
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/pagination.js
@@ -0,0 +1,34 @@
+const pagination = (state = {}, action) => {
+ switch (action.type) {
+ case 'RECEIVE_JOURNEY_PATTERNS':
+ return Object.assign({}, state, {stateChanged: false})
+ case 'GO_TO_PREVIOUS_PAGE':
+ if (action.pagination.page > 1){
+ return Object.assign({}, state, {page : action.pagination.page - 1, stateChanged: false})
+ }
+ return state
+ case 'GO_TO_NEXT_PAGE':
+ if (state.totalCount - (action.pagination.page * action.pagination.perPage) > 0){
+ return Object.assign({}, state, {page : action.pagination.page + 1, stateChanged: false})
+ }
+ return state
+ case 'ADD_VEHICLEJOURNEY':
+ case 'UPDATE_TIME':
+ toggleOnConfirmModal('modal')
+ return Object.assign({}, state, {stateChanged: true})
+ case 'RESET_PAGINATION':
+ return Object.assign({}, state, {page: 1, stateChanged: false})
+ case 'UPDATE_TOTAL_COUNT':
+ return Object.assign({}, state, {totalCount : state.totalCount - action.diff })
+ default:
+ return state
+ }
+}
+
+const toggleOnConfirmModal = (arg = '') =>{
+ $('.confirm').each(function(){
+ $(this).data('toggle','')
+ })
+}
+
+module.exports = pagination
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/status.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/status.js
new file mode 100644
index 000000000..3351aec4f
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/status.js
@@ -0,0 +1,16 @@
+var actions = require("../actions")
+
+const status = (state = {}, action) => {
+ switch (action.type) {
+ case 'UNAVAILABLE_SERVER':
+ return Object.assign({}, state, {fetchSuccess: false})
+ case 'FETCH_API':
+ return Object.assign({}, state, {isFetching: true})
+ case 'RECEIVE_VEHICLE_JOURNEYS':
+ return Object.assign({}, state, {fetchSuccess: true, isFetching: false})
+ default:
+ return state
+ }
+}
+
+module.exports = status
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/stopPointsList.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/stopPointsList.js
new file mode 100644
index 000000000..9abacc8c8
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/stopPointsList.js
@@ -0,0 +1,8 @@
+const stopPointsList = (state = [], action) => {
+ switch (action.type) {
+ default:
+ return state
+ }
+}
+
+module.exports = stopPointsList
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js
new file mode 100644
index 000000000..39fe3fceb
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js
@@ -0,0 +1,195 @@
+var actions = require("../actions")
+
+const vehicleJourney= (state = {}, action) => {
+ switch (action.type) {
+ case 'SELECT_VEHICLEJOURNEY':
+ return Object.assign({}, state, {selected: !state.selected})
+ case 'CANCEL_SELECTION':
+ return Object.assign({}, state, {selected: false})
+ case 'ADD_VEHICLEJOURNEY':
+ let pristineVjas = JSON.parse(JSON.stringify(state[0].vehicle_journey_at_stops))
+ pristineVjas.map((vj) =>{
+ vj.departure_time.hour = '00'
+ vj.departure_time.minute = '00'
+ vj.arrival_time.hour = '00'
+ vj.arrival_time.minute = '00'
+ vj.delta = 0
+ delete vj['stop_area_object_id']
+ })
+ return {
+ dummy: false,
+ journey_pattern: action.selectedJourneyPattern,
+ published_journey_name: action.data.published_journey_name.value,
+ objectid: '',
+ footnotes: [],
+ time_tables: [],
+ vehicle_journey_at_stops: pristineVjas,
+ selected: false,
+ deletable: false
+ }
+ case 'DUPLICATE_VEHICLEJOURNEY':
+ case 'SHIFT_VEHICLEJOURNEY':
+ let shiftedArray, shiftedSchedule, shiftedVjas
+ shiftedArray = state.vehicle_journey_at_stops.map((vjas, i) => {
+ shiftedSchedule = {
+ departure_time: {
+ hour: vjas.departure_time.hour,
+ minute: String(parseInt(vjas.departure_time.minute) + parseInt(action.data.additional_time.value))
+ },
+ arrival_time: {
+ hour: vjas.arrival_time.hour,
+ minute: String(parseInt(vjas.arrival_time.minute) + parseInt(action.data.additional_time.value))
+ }
+ }
+ actions.checkSchedules(shiftedSchedule)
+ shiftedVjas = Object.assign({}, state.vehicle_journey_at_stops[i], shiftedSchedule)
+ return Object.assign({}, state.vehicle_journey_at_stops[i], shiftedVjas)
+ })
+ return Object.assign({}, state, {vehicle_journey_at_stops: shiftedArray})
+ case 'UPDATE_TIME':
+ let vj, vjas, vjasArray, newSchedule
+ vjasArray = state.vehicle_journey_at_stops.map((vjas, i) =>{
+ if(i == action.subIndex){
+ newSchedule = {
+ departure_time: Object.assign({}, vjas.departure_time),
+ arrival_time: Object.assign({}, vjas.arrival_time)
+ }
+ if (action.isDeparture){
+ newSchedule.departure_time[action.timeUnit] = actions.pad(action.val)
+ if(!action.isArrivalsToggled)
+ newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val)
+ newSchedule = actions.getDelta(newSchedule)
+ return Object.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta})
+ }else{
+ newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val)
+ newSchedule = actions.getDelta(newSchedule)
+ return Object.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta})
+ }
+ }else{
+ return vjas
+ }
+ })
+ return Object.assign({}, state, {vehicle_journey_at_stops: vjasArray})
+ default:
+ return state
+ }
+}
+
+const vehicleJourneys = (state = [], action) => {
+ switch (action.type) {
+ case 'RECEIVE_VEHICLE_JOURNEYS':
+ return [...action.json]
+ case 'RECEIVE_ERRORS':
+ return [...action.json]
+ case 'GO_TO_PREVIOUS_PAGE':
+ if(action.pagination.page > 1){
+ actions.fetchVehicleJourneys(action.dispatch, action.pagination.page, action.nextPage, action.queryString)
+ }
+ return state
+ case 'GO_TO_NEXT_PAGE':
+ if (action.pagination.totalCount - (action.pagination.page * action.pagination.perPage) > 0){
+ actions.fetchVehicleJourneys(action.dispatch, action.pagination.page, action.nextPage, action.queryString)
+ }
+ return state
+ case 'ADD_VEHICLEJOURNEY':
+ return [
+ vehicleJourney(state, action),
+ ...state
+ ]
+ case 'EDIT_VEHICLEJOURNEY':
+ return state.map((vj, i) => {
+ if (vj.selected){
+ return Object.assign({}, vj, {
+ published_journey_name: action.data.published_journey_name.value,
+ published_journey_identifier: action.data.published_journey_identifier.value,
+ })
+ }else{
+ return vj
+ }
+ })
+ case 'EDIT_VEHICLEJOURNEY_NOTES':
+ return state.map((vj, i) => {
+ if (vj.selected){
+ return Object.assign({}, vj, {
+ footnotes: action.footnotes
+ })
+ }else{
+ return vj
+ }
+ })
+ case 'EDIT_VEHICLEJOURNEYS_CALENDARS':
+ return state.map((vj,i) =>{
+ if(vj.selected){
+ let updatedVJ = Object.assign({}, vj)
+ action.vehicleJourneys.map((vjm, j) =>{
+ if(vj.objectid == vjm.objectid){
+ updatedVJ.time_tables = vjm.time_tables
+ }
+ })
+ return updatedVJ
+ }else{
+ return vj
+ }
+ })
+ case 'SHIFT_VEHICLEJOURNEY':
+ return state.map((vj, i) => {
+ if (vj.selected){
+ return vehicleJourney(vj, action)
+ }else{
+ return vj
+ }
+ })
+ case 'DUPLICATE_VEHICLEJOURNEY':
+ let dupeVj
+ let dupes = []
+ let selectedIndex
+ state.map((vj, i) => {
+ if(vj.selected){
+ selectedIndex = i
+ for (i = 0; i< action.data.duplicate_number.value; i++){
+ action.data.additional_time.value *= (i + 1)
+ dupeVj = vehicleJourney(vj, action)
+ dupeVj.published_journey_name = dupeVj.published_journey_name + '-' + i
+ dupeVj.selected = false
+ delete dupeVj['objectid']
+ dupes.push(dupeVj)
+ }
+ }
+ })
+ let concatArray = state.slice(0, selectedIndex + 1).concat(dupes)
+ concatArray = concatArray.concat(state.slice(selectedIndex + 1))
+ return concatArray
+ case 'DELETE_VEHICLEJOURNEYS':
+ return state.map((vj, i) =>{
+ if (vj.selected){
+ return Object.assign({}, vj, {deletable: true, selected: false})
+ } else {
+ return vj
+ }
+ })
+ case 'SELECT_VEHICLEJOURNEY':
+ return state.map((vj, i) =>{
+ if (i == action.index){
+ return vehicleJourney(vj, action)
+ } else {
+ return vj
+ }
+ })
+ case 'CANCEL_SELECTION':
+ return state.map((vj) => {
+ return vehicleJourney(vj, action)
+ })
+ case 'UPDATE_TIME':
+ return state.map((vj, i) =>{
+ if (i == action.index){
+ return vehicleJourney(vj, action)
+ } else {
+ return vj
+ }
+ })
+ default:
+ return state
+ }
+}
+
+module.exports = vehicleJourneys
diff --git a/app/assets/javascripts/selectable_table.coffee b/app/assets/javascripts/selectable_table.coffee
index 84475857e..4d9f5122a 100644
--- a/app/assets/javascripts/selectable_table.coffee
+++ b/app/assets/javascripts/selectable_table.coffee
@@ -12,7 +12,7 @@
# Add each element to selection
selection.push($(this).attr('id'))
- # Remove th checkbox from selection
+ # Remove th checkbox from selection
selection.splice(0, 1)
else
diff --git a/app/assets/stylesheets/application.sass b/app/assets/stylesheets/application.sass
index c2cee88c9..e4a2a5445 100644
--- a/app/assets/stylesheets/application.sass
+++ b/app/assets/stylesheets/application.sass
@@ -6,6 +6,7 @@
@import 'base/utilities'
@import 'typography/fonts'
+@import 'typography/sboiv'
@import 'typography/typography'
@import 'layout'
@@ -13,3 +14,4 @@
@import 'components/*'
@import 'modules/routes_stopoints'
@import 'modules/jp_collection'
+@import 'modules/vj_collection'
diff --git a/app/assets/stylesheets/base/_config.sass b/app/assets/stylesheets/base/_config.sass
index 0c86270c1..6524e4720 100644
--- a/app/assets/stylesheets/base/_config.sass
+++ b/app/assets/stylesheets/base/_config.sass
@@ -18,4 +18,4 @@ $darkgrey: #4b4b4b
$grey: #a4a4a4
$green: #70b12b
-$red: #da2f36
+$orange: #ed7f00
diff --git a/app/assets/stylesheets/base/_utilities.sass b/app/assets/stylesheets/base/_utilities.sass
index 65091e904..24f2038f0 100644
--- a/app/assets/stylesheets/base/_utilities.sass
+++ b/app/assets/stylesheets/base/_utilities.sass
@@ -46,3 +46,27 @@
=emptyzone($col1, $col2)
background-image: linear-gradient(-45deg, $col1 25%, $col2 25%, $col2 50%, $col1 50%, $col1 75%, $col2 75%, transparent)
background-size: 40px 40px
+
+.cellwrap
+ display: block
+ // To avoid white spaces between childrend inline-block
+ letter-spacing: -0.31em
+ text-rendering: optimizespeed
+ font-weight: 400
+
+ > div
+ display: inline-block
+ // To avoid white spaces between children inline-block
+ letter-spacing: normal
+ word-spacing: normal
+ text-rendering: auto
+ overflow: hidden
+ vertical-align: middle
+ min-width: 20px
+
+ + div
+ margin-left: 5px
+
+ &.hidden:first-child
+ &, & + div
+ margin-left: 0
diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass
index 307182082..451f1a33d 100644
--- a/app/assets/stylesheets/components/_forms.sass
+++ b/app/assets/stylesheets/components/_forms.sass
@@ -38,6 +38,28 @@ input
.input-group-btn > .btn-link
padding-right: 0
+// Time input groups
+.input-group.time
+ > .form-control
+ float: none
+ width: auto
+ padding: 6px 8px
+
+ &:first-child
+ border-right: none
+ &:last-child
+ border-left: none
+
+ + span
+ display: table-cell
+ width: 1px
+ border-top: 1px solid #ccc
+ border-bottom: 1px solid #ccc
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075)
+
+ &.disabled > .form-control + span
+ background-color: #eee
+
// Validations
.control-label
&.is-required
@@ -91,6 +113,10 @@ input
// Checkbox
$cbx-size: 20px
+<<<<<<< HEAD
+=======
+$cbx-size-xs: 15px
+>>>>>>> 2498_vehicle_journeys
.checkbox
position: relative
display: block
@@ -100,6 +126,7 @@ $cbx-size: 20px
> input
&[type='checkbox']:not(:checked), &[type='checkbox']:checked
+<<<<<<< HEAD
position: absolute
margin: 0
left: -9999px
@@ -155,6 +182,105 @@ $cbx-size: 20px
> input[type='checkbox']
&:not(:checked), &:checked
position: absolute
+=======
+ position: absolute
+ margin: 0
+ left: -9999px
+
+ + label
+ position: relative
+ cursor: pointer
+ display: inline-block
+ vertical-align: top
+ height: $cbx-size
+ margin: 0
+ padding-left: $cbx-size
+
+ &:before
+ content: ''
+ position: absolute
+ left: 0
+ top: 50%
+ margin-top: -($cbx-size / 2)
+ width: $cbx-size
+ height: $cbx-size
+ border: 2px solid $blue
+ background-color: #fff
+ border-radius: 3px
+
+ &:after
+ content: '\f00c'
+ font: normal normal normal 14px/1 FontAwesome
+ font-size: inherit
+ text-rendering: auto
+ -webkit-font-smoothing: antialiased
+ position: absolute
+ left: 50%
+ top: 50%
+ margin-top: -($cbx-size / 2)
+ margin-left: -($cbx-size / 2)
+ font-size: $cbx-size - 2px
+ width: $cbx-size
+ line-height: $cbx-size
+ text-align: center
+ color: #fff
+
+ &[type='checkbox']:checked + label:before
+ background-color: $blue
+
+// Table adjustments
+table, .table
+ .td, td, .th, th
+ .form-control
+ height: 30px
+ padding: 4px 12px
+
+ .input-group.time > .form-control
+ padding: 4px 6px
+
+ // Table checkboxes
+ &.table-2entries .t2e-item
+ > .th
+ position: relative
+
+ > .checkbox
+ position: absolute
+ right: 0
+ top: 0
+ margin: 6px 8px
+ height: $cbx-size-xs
+ width: $cbx-size-xs
+
+ > input
+ &[type='checkbox']:not(:checked), &[type='checkbox']:checked
+ + label
+ height: $cbx-size-xs
+ padding-left: $cbx-size-xs
+
+ &:before
+ margin-top: -($cbx-size-xs / 2)
+ height: $cbx-size-xs
+ width: $cbx-size-xs
+ border-radius: 2px
+ &:after
+ margin-top: -($cbx-size-xs / 2)
+ margin-left: -($cbx-size-xs / 2)
+ font-size: $cbx-size-xs - 2px
+ width: $cbx-size-xs
+ line-height: $cbx-size-xs
+
+// Radio
+.has_radio
+ position: relative
+ display: block
+ height: $cbx-size
+ width: $cbx-size
+ margin: 0 auto
+
+ > input[type='checkbox']
+ &:not(:checked), &:checked
+ position: absolute
+>>>>>>> 2498_vehicle_journeys
z-index: 5
margin: 0
left: 0
@@ -249,12 +375,50 @@ $cbx-size: 20px
left: 20px
transition: 0.2s
+<<<<<<< HEAD
+=======
+ &.inline
+ > *
+ display: inline-block
+ vertical-align: top
+
+ > label
+ margin: 0
+ padding: 2px 15px 0 0
+
+ .checkbox
+ height: 22px
+ line-height: 22px
+
+ > label > input[type='checkbox']
+ + .switch-label
+ width: 37px
+ height: 22px
+ line-height: 22px
+ padding-left: 47px
+
+ &:before
+ width: 37px
+ height: 22px
+ border-radius: 14px
+ &:after
+ width: 18px
+ height: 18px
+
+ &:not(:checked) + .switch-label:after
+ left: 15px
+
+>>>>>>> 2498_vehicle_journeys
// Form filter
.form.form-filter
display: block
position: relative
background-color: rgba($grey, 0.15)
+<<<<<<< HEAD
padding-right: 185px
+=======
+ padding-right: 195px
+>>>>>>> 2498_vehicle_journeys
min-height: 42px
border-top: 1px solid #fff
@@ -266,7 +430,13 @@ $cbx-size: 20px
vertical-align: top
padding: 7px 15px
+<<<<<<< HEAD
> .form-group
+=======
+ > .form-group, > div > .form-group
+ display: inline-block
+ vertical-align: top
+>>>>>>> 2498_vehicle_journeys
border-right: 1px solid #fff
margin: 0
min-height: 44px
@@ -314,7 +484,11 @@ $cbx-size: 20px
color: #fff
transition: 0.2s
+<<<<<<< HEAD
> .form-group
+=======
+ > .form-group, > div > .form-group
+>>>>>>> 2498_vehicle_journeys
position: relative
&.togglable
@@ -334,6 +508,37 @@ $cbx-size: 20px
&:hover, &.open
background-color: $blue
color: #fff
+<<<<<<< HEAD
+
+ > .control-label, &:before
+ color: #fff
+
+ &:before
+ content: '\f078'
+ font: normal normal normal 14px/1 FontAwesome
+ font-size: inherit
+ text-rendering: auto
+ -webkit-font-smoothing: antialiased
+ position: absolute
+ right: 15px
+ top: 0
+ height: 44px
+ width: 15px
+ color: $blue
+ text-align: center
+ line-height: 44px
+
+ &.open:before
+ content: '\f077'
+ color: #fff
+
+ > .control-label + *
+ display: none
+
+ &.open > .control-label + *
+ display: block
+
+=======
> .control-label, &:before
color: #fff
@@ -363,6 +568,7 @@ $cbx-size: 20px
&.open > .control-label + *
display: block
+>>>>>>> 2498_vehicle_journeys
> .filter_menu
margin: 0
padding: 5px 0
@@ -476,6 +682,20 @@ $cbx-size: 20px
&[type='checkbox']:checked + span:before
background-color: $blue
+<<<<<<< HEAD
+=======
+ > .ffg-row
+ display: block
+ padding: 0
+ border-right: 1px solid #fff
+
+ + .ffg-row
+ border-top: 1px solid #fff
+
+ > .form-group:last-child
+ border-right: none
+
+>>>>>>> 2498_vehicle_journeys
// Form group date
.form-group.date
.form-inline
@@ -517,6 +737,14 @@ $cbx-size: 20px
.form-control, select.date, select.date:first-child, select.date:nth-child(3)
border-color: $red
+<<<<<<< HEAD
+=======
+// Form group time
+.form-group.time
+ .control-label.time
+ min-width: 60px
+
+>>>>>>> 2498_vehicle_journeys
// Nested fields
.nested-fields
margin: 0
diff --git a/app/assets/stylesheets/components/_select2.sass b/app/assets/stylesheets/components/_select2.sass
index 269cda08b..1eb6d01a8 100644
--- a/app/assets/stylesheets/components/_select2.sass
+++ b/app/assets/stylesheets/components/_select2.sass
@@ -7,6 +7,8 @@
position: relative
.select2-container--bootstrap
+ .select2-dropdown
+ z-index: 2050
.select2-dropdown--above
box-shadow: 0 -3px 6px rgba(0, 0, 0, 0.175)
diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass
index 495e27951..b35ceead9 100644
--- a/app/assets/stylesheets/components/_tables.sass
+++ b/app/assets/stylesheets/components/_tables.sass
@@ -77,10 +77,24 @@
background-color: #fff
box-shadow: 0 0 3px $darkgrey
position: fixed
- z-index: 999
+ z-index: 2000
right: 50px
bottom: 15px
+ body.modal-open &
+ // right: 65px
+
+ &:before
+ content: ''
+ display: block
+ position: absolute
+ top: 0
+ left: 0
+ right: 0
+ bottom: 0
+ z-index: 5
+ background-color: rgba(#000, 0.5)
+
> ul
display: block
margin: 0
@@ -102,6 +116,9 @@
background-color: $blue
color: #fff
+ &:focus
+ outline: none
+
&:hover
background-color: rgba($blue, 0.5)
@@ -111,6 +128,11 @@
&:hover
background-color: darken($red, 5%)
+ &.disabled
+ &, &[title='Supprimer'], &:hover, &:focus
+ background-color: rgba($grey, 0.3)
+ cursor: not-allowed
+
&.noselect
> ul > .st_action > a
&, &[title='Supprimer'], &:hover, &:focus
@@ -147,7 +169,7 @@
> div
position: relative
- height: 100%
+ height: calc(100% + 6px)
&.headlined:before
content: ''
@@ -209,6 +231,22 @@
&.headlined > .has_radio
margin-top: calc((1.4em + 6px) * -1)
+ .th + .td, .td:first-child
+ > .headlined
+ [data-headline]
+ position: relative
+ overflow: visible
+
+ &:before
+ content: attr(data-headline)
+ display: block
+ position: absolute
+ z-index: 5
+ bottom: calc(100% + 13px)
+ left: 0
+ right: 0
+ color: rgba($grey, 0.75)
+
&.disabled
color: rgba($darkgrey, 0.5)
background-color: rgba(#fff, 0.5)
diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass
new file mode 100644
index 000000000..cd9ea687c
--- /dev/null
+++ b/app/assets/stylesheets/modules/_vj_collection.sass
@@ -0,0 +1,68 @@
+//-----------------//
+// VJ Collection //
+//-----------------//
+
+#vehicle_journeys_wip
+ .table-2entries
+ .t2e-head
+ > .td
+ position: relative
+ padding-left: 25px
+
+ > .headlined
+ &:before
+ margin-left: -25px
+ padding-left: 25px
+
+ > div > span
+ position: relative
+ display: block
+ height: 100%
+
+ > span
+ position: absolute
+ display: block
+ line-height: 1em
+ top: 50%
+ transform: translateY(-50%)
+ margin-top: -2px
+
+ &:before
+ content: ''
+ display: block
+ width: 10px
+ height: 10px
+ background-color: #fff
+ border: 2px solid $blue
+ border-radius: 50%
+ position: absolute
+ z-index: 5
+ left: -20px
+ top: 50%
+ margin-top: -5px
+
+ &:after
+ content: ''
+ display: block
+ width: 4px
+ margin: 0 3px
+ background-color: rgba($grey, 0.5)
+ position: absolute
+ z-index: 3
+ top: -6px
+ left: -20px
+ bottom: 0
+
+ > .headlined > span
+ height: calc(100% - (1.4em + 12px))
+
+ &:after
+ top: calc((1.4em + 18px) * -1)
+
+ &:last-child > div > span
+ &:after
+ bottom: 50%
+
+ &:nth-child(2) > div > span
+ &:after
+ top: 50%
diff --git a/app/assets/stylesheets/typography/_fonts.sass b/app/assets/stylesheets/typography/_fonts.sass
index cb370eb86..c715fa2ab 100644
--- a/app/assets/stylesheets/typography/_fonts.sass
+++ b/app/assets/stylesheets/typography/_fonts.sass
@@ -2,6 +2,8 @@
// Fonts //
//---------//
+//-- OpenSans --//
+
@font-face
// Regular
font-family: 'Open Sans'
@@ -29,3 +31,10 @@
src: url(asset-path('OpenSans/Bold/OpenSans-BoldItalic.woff2')) format('woff2'), url(asset-path('OpenSans/Bold/OpenSans-BoldItalic.woff')) format('woff'), url(asset-path('OpenSans/Bold/OpenSans-BoldItalic.ttf')) format('ttf')
font-weight: 700
font-style: italic
+
+//-- sBoiv --//
+@font-face
+ font-family: 'sboiv'
+ src: url(asset-path('sBoiv/sboiv.woff')) format('woff'), url(asset-path('sBoiv/sboiv.ttf')) format('ttf')
+ font-weight: normal
+ font-style: normal
diff --git a/app/assets/stylesheets/typography/_sboiv.sass b/app/assets/stylesheets/typography/_sboiv.sass
new file mode 100644
index 000000000..3e8eb2868
--- /dev/null
+++ b/app/assets/stylesheets/typography/_sboiv.sass
@@ -0,0 +1,57 @@
+//-----------------------------//
+// sBoiv (custom icon fonts) //
+//-----------------------------//
+
+.sb
+ display: inline-block
+ font-family: 'sboiv' !important
+ speak: none
+ font-style: normal
+ font-weight: normal
+ font-variant: normal
+ text-transform: normal
+ line-height: 1
+ font-size: inherit
+ text-rendering: auto
+ -webkit-font-smoothing: antialiased
+ -moz-osx-font-smoothing: grayscale
+
+ &[data-textinside]
+ position: relative
+ &:after
+ content: attr(data-textinside)
+ font-family: $base-font-family
+ font-size: 0.6em
+ font-weight: 700
+ display: block
+ position: absolute
+ left: 0
+ top: 0
+ right: 0
+ bottom: 0
+ text-align: center
+ color: #fff
+
+
+ &.sb-lg
+ font-size: 1.33333333em
+ line-height: 0.75em
+ vertical-align: -15%
+
+ &.sb-2x
+ font-size: 2em
+
+ &.sb-3x
+ font-size: 3em
+
+ &.sb-4x
+ font-size: 4em
+
+ &.sb-5x
+ font-size: 5em
+
+.sb-update-vj:before
+ content: '\e900'
+
+.sb-chrono:before
+ content: '\e901'
diff --git a/app/assets/stylesheets/typography/_typography.sass b/app/assets/stylesheets/typography/_typography.sass
index d0d22e27b..56efa1adc 100644
--- a/app/assets/stylesheets/typography/_typography.sass
+++ b/app/assets/stylesheets/typography/_typography.sass
@@ -35,3 +35,5 @@ strong, .strong
color: $green
.text-info
color: $blue
+.text-warning
+ color: $orange
diff --git a/app/controllers/autocomplete_time_tables_controller.rb b/app/controllers/autocomplete_time_tables_controller.rb
index ce3824b7a..e977a28b0 100644
--- a/app/controllers/autocomplete_time_tables_controller.rb
+++ b/app/controllers/autocomplete_time_tables_controller.rb
@@ -1,25 +1,28 @@
class AutocompleteTimeTablesController < InheritedResources::Base
respond_to :json, :only => [:index]
+ before_action :switch_referential
include ReferentialSupport
+ def switch_referential
+ Apartment::Tenant.switch!(referential.slug)
+ end
+
+ def referential
+ @referential ||= current_organisation.referentials.find params[:referential_id]
+ end
+
protected
def select_time_tables
+ scope = referential.time_tables
if params[:route_id]
- referential.time_tables.joins( vehicle_journeys: :route).where( "routes.id IN (#{params[:route_id]})")
- else
- referential.time_tables
- end
- end
-
- def referential_time_tables
- @referential_time_tables ||= select_time_tables
+ scope = scope.joins(vehicle_journeys: :route).where( "routes.id IN (#{params[:route_id]})")
+ end
+ scope
end
def collection
- comment_selection = referential_time_tables.select{ |p| p.comment =~ /#{params[:q]}/i }
- tag_selection = referential_time_tables.tagged_with( params[:q], :wild => true)
- @time_tables = (comment_selection + tag_selection).uniq
+ @time_tables = select_time_tables.search(params[:q]).result.paginate(page: params[:page])
end
end
diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb
index d96d7f9c7..4d1a06fd3 100644
--- a/app/controllers/journey_patterns_collections_controller.rb
+++ b/app/controllers/journey_patterns_collections_controller.rb
@@ -10,7 +10,7 @@ class JourneyPatternsCollectionsController < ChouetteController
alias_method :route, :parent
def show
- @q = route.journey_patterns.includes(:stop_points)
+ @q = route.journey_patterns.search(params[:q]).result(distinct: true).includes(:stop_points)
@ppage = 10
@journey_patterns ||= @q.paginate(page: params[:page], per_page: @ppage).order(:name)
diff --git a/app/controllers/line_footnotes_controller.rb b/app/controllers/line_footnotes_controller.rb
index 192f902c8..8f7a38512 100644
--- a/app/controllers/line_footnotes_controller.rb
+++ b/app/controllers/line_footnotes_controller.rb
@@ -1,6 +1,9 @@
class LineFootnotesController < ChouetteController
defaults :resource_class => Chouette::Line, :instance_name => 'line'
include PolicyChecker
+ before_action :check_policy, only: [:edit, :update, :destroy]
+ respond_to :json, :only => :show
+
belongs_to :referential
def show
@@ -34,6 +37,7 @@ class LineFootnotesController < ChouetteController
def resource
@referential = Referential.find params[:referential_id]
@line = @referential.lines.find params[:line_id]
+ @footnotes = @line.footnotes
end
def line_params
diff --git a/app/controllers/vehicle_journeys_collections_controller.rb b/app/controllers/vehicle_journeys_collections_controller.rb
new file mode 100644
index 000000000..caaa2258e
--- /dev/null
+++ b/app/controllers/vehicle_journeys_collections_controller.rb
@@ -0,0 +1,19 @@
+class VehicleJourneysCollectionsController < ChouetteController
+ respond_to :json
+ belongs_to :referential do
+ belongs_to :line, :parent_class => Chouette::Line do
+ belongs_to :route, :parent_class => Chouette::Route
+ end
+ end
+ alias_method :route, :parent
+
+ def update
+ state = JSON.parse request.raw_post
+ Chouette::VehicleJourney.state_update route, state
+ errors = state.any? {|item| item['errors']}
+
+ respond_to do |format|
+ format.json { render json: state, status: errors ? :unprocessable_entity : :ok }
+ end
+ end
+end
diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb
index 45cb25344..8845b0c54 100644
--- a/app/controllers/vehicle_journeys_controller.rb
+++ b/app/controllers/vehicle_journeys_controller.rb
@@ -1,5 +1,9 @@
class VehicleJourneysController < ChouetteController
defaults :resource_class => Chouette::VehicleJourney
+ before_action :check_policy, only: [:edit, :update, :destroy]
+ before_action :user_permissions, only: :index
+ before_action :ransack_params, only: :index
+
respond_to :json, :only => :index
respond_to :js, :only => [:select_journey_pattern, :edit, :new, :index]
@@ -10,6 +14,9 @@ class VehicleJourneysController < ChouetteController
end
include PolicyChecker
+ alias_method :vehicle_journeys, :collection
+ alias_method :route, :parent
+ alias_method :vehicle_journey, :resource
def select_journey_pattern
if params[:journey_pattern_id]
@@ -29,6 +36,31 @@ class VehicleJourneysController < ChouetteController
end
def index
+ @stop_points_list = []
+ route.stop_points.each do |sp|
+ @stop_points_list << {
+ :id => sp.stop_area.id,
+ :route_id => sp.try(:route_id),
+ :object_id => sp.try(:objectid),
+ :position => sp.try(:position),
+ :for_boarding => sp.try(:for_boarding),
+ :for_alighting => sp.try(:for_alighting),
+ :name => sp.stop_area.try(:name),
+ :zip_code => sp.stop_area.try(:zip_code),
+ :city_name => sp.stop_area.try(:city_name),
+ :comment => sp.stop_area.try(:comment),
+ :area_type => sp.stop_area.try(:area_type),
+ :registration_number => sp.stop_area.try(:registration_number),
+ :nearest_topic_name => sp.stop_area.try(:nearest_topic_name),
+ :fare_code => sp.stop_area.try(:fare_code),
+ :longitude => sp.stop_area.try(:longitude),
+ :latitude => sp.stop_area.try(:latitude),
+ :long_lat_type => sp.stop_area.try(:long_lat_type),
+ :country_code => sp.stop_area.try(:country_code),
+ :street_name => sp.stop_area.try(:street_name)
+ }
+ end
+
index! do
if collection.out_of_bounds?
redirect_to params.merge(:page => 1)
@@ -37,7 +69,6 @@ class VehicleJourneysController < ChouetteController
end
end
-
# overwrite inherited resources to use delete instead of destroy
# foreign keys will propagate deletion)
def destroy_resource(object)
@@ -45,20 +76,14 @@ class VehicleJourneysController < ChouetteController
end
protected
-
- alias_method :vehicle_journey, :resource
-
def collection
- unless @vehicle_journeys
- @vehicle_filter = VehicleFilter.new adapted_params
- @vehicle_filter.journey_category_model = resource_class.model_name.route_key
- @q = @vehicle_filter.vehicle_journeys.search @vehicle_filter.filtered_params
- @vehicle_journeys = @q.result( :distinct => false ).paginate(:page => params[:page], :per_page => 8)
- end
- matrix
+ @ppage = 20
+ @q = route.sorted_vehicle_journeys('vehicle_journeys').search params[:q]
+ @vehicle_journeys = @q.result.paginate(:page => params[:page], :per_page => @ppage)
+ @footnotes = route.line.footnotes.to_json
+ @matrix = resource_class.matrix(@vehicle_journeys)
@vehicle_journeys
end
- alias_method :vehicle_journeys, :collection
def adapted_params
params.tap do |adapted_params|
@@ -79,7 +104,29 @@ class VehicleJourneysController < ChouetteController
@matrix = resource_class.matrix(@vehicle_journeys)
end
+ def check_policy
+ authorize resource
+ end
+
+ def user_permissions
+ @perms = {}.tap do |perm|
+ ['vehicle_journeys.create', 'vehicle_journeys.edit', 'vehicle_journeys.destroy'].each do |name|
+ perm[name] = current_user.permissions.include?(name)
+ end
+ end
+ @perms = @perms.to_json
+ end
+
private
+ def ransack_params
+ if params[:q]
+ params[:q] = params[:q].reject{|k| params[:q][k] == 'undefined'}
+ [:departure_time_gteq, :departure_time_lteq].each do |filter|
+ time = params[:q]["vehicle_journey_at_stops_#{filter}"]
+ params[:q]["vehicle_journey_at_stops_#{filter}"] = "2000-01-01 #{time}:00 UTC"
+ end
+ end
+ end
def vehicle_journey_params
params.require(:vehicle_journey).permit( { footnote_ids: [] } , :journey_pattern_id, :number, :published_journey_name,
@@ -92,5 +139,4 @@ class VehicleJourneysController < ChouetteController
:stop_point_id,
:departure_time] } )
end
-
end
diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb
index 436f19e9f..9007b177e 100644
--- a/app/models/chouette/route.rb
+++ b/app/models/chouette/route.rb
@@ -110,9 +110,9 @@ class Chouette::Route < Chouette::TridentActiveRecord
def sorted_vehicle_journeys(journey_category_model)
send(journey_category_model)
- .joins(:journey_pattern, :vehicle_journey_at_stops)
+ .joins(:journey_pattern, :vehicle_journey_at_stops, :time_tables)
.where("vehicle_journey_at_stops.stop_point_id=journey_patterns.departure_stop_point_id")
- .order( "vehicle_journey_at_stops.departure_time")
+ .order("vehicle_journey_at_stops.departure_time")
end
def stop_point_permutation?( stop_point_ids)
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index b4bd38850..d03b52e83 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -47,6 +47,47 @@ module Chouette
@presenter ||= ::VehicleJourneyPresenter.new( self)
end
+ def vehicle_journey_at_stops_matrix
+ fill = route.stop_points.count - self.vehicle_journey_at_stops.count
+ at_stops = self.vehicle_journey_at_stops.to_a.dup
+ fill.times do
+ at_stops << Chouette::VehicleJourneyAtStop.new
+ end
+ at_stops
+ end
+
+ def update_vehicle_journey_at_stops_state state
+ state.each do |vjas|
+ next if vjas["dummy"]
+ stop = vehicle_journey_at_stops.find(vjas['id']) if vjas['id']
+ if stop
+ stop.arrival_time ||= Time.now.beginning_of_day
+ stop.departure_time ||= Time.now.beginning_of_day
+ ['arrival_time', 'departure_time'].each do |field|
+ stop.assign_attributes({
+ field.to_sym => stop.send(field).change({ hour: vjas[field]['hour'], min: vjas[field]['minute'] })
+ })
+ end
+ stop.save
+ end
+ end
+ end
+
+ def self.state_update route, state
+ transaction do
+ state.each do |item|
+ item.delete('errors')
+ vj = find_by(objectid: item['objectid'])
+ vj.update_vehicle_journey_at_stops_state(item['vehicle_journey_at_stops'])
+ item['errors'] = vj.errors if vj.errors.any?
+ end
+
+ if state.any? {|item| item['errors']}
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
def increasing_times
previous = nil
vehicle_journey_at_stops.select{|vjas| vjas.departure_time && vjas.arrival_time}.each do |vjas|
diff --git a/app/views/api/v1/journey_patterns/show.rabl b/app/views/api/v1/journey_patterns/show.rabl
index 808808462..21f25e480 100644
--- a/app/views/api/v1/journey_patterns/show.rabl
+++ b/app/views/api/v1/journey_patterns/show.rabl
@@ -1,7 +1,7 @@
object @journey_pattern
extends "api/v1/trident_objects/show"
-[:name, :published_name, :registration_number, :comment].each do |attr|
+[:id, :name, :published_name, :registration_number, :comment].each do |attr|
attributes attr, :unless => lambda { |m| m.send( attr).nil?}
end
diff --git a/app/views/autocomplete_time_tables/index.rabl b/app/views/autocomplete_time_tables/index.rabl
index 0389c26b0..8ec7b314e 100644
--- a/app/views/autocomplete_time_tables/index.rabl
+++ b/app/views/autocomplete_time_tables/index.rabl
@@ -1,7 +1,7 @@
collection @time_tables, :object_root => false
node do |time_table|
- { :id => time_table.id, :comment => time_table.comment,
+ { :id => time_table.id, :comment => time_table.comment, :objectid => time_table.objectid,
:time_table_bounding => time_table.presenter.time_table_bounding,
:composition_info => time_table.presenter.composition_info,
:tags => time_table.tags.join(','),
diff --git a/app/views/journey_patterns/show.html.slim b/app/views/journey_patterns/show.html.slim
index 85ba4e7a5..823252480 100644
--- a/app/views/journey_patterns/show.html.slim
+++ b/app/views/journey_patterns/show.html.slim
@@ -45,6 +45,6 @@ h3.journey_pattern_stop_points = t('.stop_points')
i.fa.fa-exclamation-triangle
li
- = link_to t('journey_patterns.journey_pattern.vehicle_journey_at_stops'), referential_line_route_vehicle_journeys_path(@referential, @line, @route, :q => {:journey_pattern_id_eq => @journey_pattern.id}), class: 'clock'
+ = link_to t('journey_patterns.journey_pattern.vehicle_journey_at_stops'), referential_line_route_vehicle_journeys_path(@referential, @line, @route, :q => {:journey_pattern_id_eq => @journey_pattern.id, :journey_pattern_object_id => @journey_pattern.objectid}), class: 'clock'
= creation_tag(@journey_pattern)
diff --git a/app/views/line_footnotes/show.rabl b/app/views/line_footnotes/show.rabl
new file mode 100644
index 000000000..5cec1ceb5
--- /dev/null
+++ b/app/views/line_footnotes/show.rabl
@@ -0,0 +1,9 @@
+collection @footnotes
+
+node do |footnote|
+ {
+ :id => footnote.id,
+ :code => footnote.code,
+ :label => footnote.label
+ }
+end
diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim
index aa18a120f..dfd27b24b 100644
--- a/app/views/vehicle_journeys/index.html.slim
+++ b/app/views/vehicle_journeys/index.html.slim
@@ -1,93 +1,54 @@
-= title_tag t('vehicle_journeys.index.title', route: @route.name )
-
-= search_form_for @q, :url => referential_line_route_vehicle_journeys_path(@referential,@line,@route), remote: true, :html => {:method => :get, class: "form-inline", :id => "search", role: "form"} do |f|
- .panel.panel-default
- .panel-heading
- = f.label :journey_pattern_id_eq, "Missions"
- = f.text_field(:journey_pattern_id_eq, class: "form-control")
-
- button.btn.btn-default type="submit"
- i.fa.fa-search
-
- a.advanced_search data-toggle="collapse" data-parent="#search" href="#advanced_search"
- i.fa.fa-plus
- = "#{t('.advanced_search')}"
-
- #advanced_search.panel-collapse.collapse
- .panel-body
- div
- = f.label :time_tables_id_not_eq, "Sans calendrier"
- = f.check_box :time_tables_id_not_eq
-
- span.time_tables_id_eq
- = f.label :time_tables_id_eq, "Sélectionner calendriers"
- = f.text_field :time_tables_id_eq, :input_html => { :"data-pre" => [].to_json}
-
- / - if controller_name != 'vehicle_journey_frequencies'
- / div
- / = f.label :vehicle_journey_at_stops_departure_time_not_eq, "Sans horaire"
- / = f.check_box :vehicle_journey_at_stops_departure_time_not_eq
-
- / span.vehicle_journey_at_stops_departure_time_gt
- / input name="#{q[vehicle_journey_at_stops_departure_time_gt(3i)]}" type="hidden" value="1"
- / input name="#{q[vehicle_journey_at_stops_departure_time_gt(2i)]}" type="hidden" value="1"
- / input name="#{q[vehicle_journey_at_stops_departure_time_gt(1i)]}" type="hidden" value="2000"
-
- / = f.label :vehicle_journey_at_stops_departure_time_gt, t('.time_range')
- / = select_hour(@q.send( "vehicle_journey_at_stops_departure_time_gt") ? @q.send( "vehicle_journey_at_stops_departure_time_gt").hour : 0, :prefix => "q", :field_name => "vehicle_journey_at_stops_departure_time_gt(4i)")
- / = select_minute(@q.send( "vehicle_journey_at_stops_departure_time_gt") ? @q.send( "vehicle_journey_at_stops_departure_time_gt").min : 0, :prefix => "q", :field_name => "vehicle_journey_at_stops_departure_time_gt(5i)")
-
-#vehicle_journeys
- == render "vehicle_journeys"
-
-- content_for :sidebar do
- == render "sidebar"
-
-javascript:
- $(function() {
- var time_tables_url = function(){
- return "#{referential_autocomplete_time_tables_path(@referential, :format => :json)}?route_id=#{@route.id}";
- };
-
- var time_table_formatter = function(item){
- var day_types = '';
- if ( item.day_types.length >0 ){
- day_types = '<span class=\"day_types\">' + item.day_types + '</span>' ;
- }
- var tags = '';
- if ( item.tags.length >0 ){
- tags = '<div class=\"info\">' + item.tags + '</div>' ;
- }
- return '<li><div class=\"comment\">' + item.comment +
- '</div><div class=\"info\">' + item.time_table_bounding + ' ' + day_types + '</div>' +
- tags + '</li>';
- };
- $( "#q_time_tables_id_eq" ).tokenInput( time_tables_url, {
- crossDomain: false,
- prePopulate: $('#q_time_tables_id_eq').data('pre'),
- minChars: 2,
- propertyToSearch: 'comment',
- hintText: "#{t('search_hint')}",
- noResultsText: "#{t('no_result_text')}",
- searchingText: "#{t('searching_term')}",
- resultsFormatter: time_table_formatter,
- tokenFormatter: time_table_formatter
- });
- $( "#q_journey_pattern_id_eq" ).tokenInput("#{referential_line_route_journey_patterns_path(@referential, @line, @route, :format => :json)}", {
- crossDomain: false,
- prePopulate: $('#q_journey_pattern_id_eq').data('pre'),
- minChars: 1,
- queryParam: 'q[name_cont]',
- propertyToSearch: 'name',
- hintText: "#{t('search_hint')}",
- noResultsText: "#{t('no_result_text')}",
- searchingText: "#{t('searching_term')}",
- resultsFormatter: function(item){ return '<li><div class=\"name\">' + item.name + ', (' + item.id + ') </div></li>' },
- });
- $( 'input[name="q[time_tables_id_not_eq]"]').change( function(){
- $('span.time_tables_id_eq').toggle( $(this).filter(":checked").val()==undefined);
- });
- $( 'input[name="q[vehicle_journey_at_stops_departure_time_not_eq]"]').change( function(){
- $('span.vehicle_journey_at_stops_departure_time_gt').toggle( $(this).filter(":checked").val()==undefined);
- });
- });
+/ PageHeader
+= pageheader 'map-marker',
+ t('vehicle_journeys.index.title', route: @route.name ),
+ 'Lorem ipsum dolor sit amet',
+ t('last_update', time: l(@vehicle_journeys.last.updated_at, format: :short)) do
+
+/ PageContent
+.page_content
+ .container-fluid
+ .row
+ .col-lg-12
+ / = @perms.inspect
+ / = search_form_for @q, :url => referential_line_route_vehicle_journeys_path(@referential,@line,@route), remote: true, :html => {:method => :get, class: "form-inline", :id => "search", role: "form"} do |f|
+ / .panel.panel-default
+ / .panel-heading
+ / = f.label :journey_pattern_id_eq, "Missions"
+ / = f.text_field(:journey_pattern_id_eq, class: "form-control")
+ /
+ / button.btn.btn-default type="submit"
+ / i.fa.fa-search
+ /
+ / a.advanced_search data-toggle="collapse" data-parent="#search" href="#advanced_search"
+ / i.fa.fa-plus
+ / = "#{t('.advanced_search')}"
+ /
+ / #advanced_search.panel-collapse.collapse
+ / .panel-body
+ / div
+ / = f.label :time_tables_id_not_eq, "Sans calendrier"
+ / = f.check_box :time_tables_id_not_eq
+ /
+ / span.time_tables_id_eq
+ / = f.label :time_tables_id_eq, "Sélectionner calendriers"
+ / = f.text_field :time_tables_id_eq, :input_html => { :"data-pre" => [].to_json}
+ /
+ /
+ / #vehicle_journeys
+ / == render "vehicle_journeys"
+ /
+ / - content_for :sidebar do
+ / == render "sidebar"
+
+ #vehicle_journeys_wip
+
+= javascript_tag do
+ | window.route_id = #{params[:route_id]};
+ | window.stopPoints = #{(@stop_points_list.to_json).html_safe};
+ | window.journeyPatternId = #{params[:q].present? ? ('"' + params[:q].values[1] + '"').html_safe : false};
+ | window.vehicleJourneysLength = #{@vehicle_journeys.total_entries()};
+ | window.vehicleJourneysPerPage = #{@ppage};
+ | window.line_footnotes = #{raw @footnotes};
+ | window.perms = #{raw @perms}
+
+= javascript_include_tag 'es6_browserified/vehicle_journeys/index.js'
diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl
index 09375f5d5..f973ccbaa 100644
--- a/app/views/vehicle_journeys/show.rabl
+++ b/app/views/vehicle_journeys/show.rabl
@@ -1,39 +1,48 @@
object @vehicle_journey
-[ :objectid, :published_journey_name, :journey_pattern_id].each do |attr|
+[:objectid, :published_journey_name, :published_journey_identifier, :company_id].each do |attr|
attributes attr, :unless => lambda { |m| m.send( attr).nil?}
end
-child(:time_tables) do |time_tables|
- node do |tt|
- [:objectid, :start_date, :end_date].map do |att|
- node(att) { tt.send(att) }
- end
- node :calendar do |tt|
- {
- id: tt.calendar.id,
- name: tt.calendar.name,
- date_ranges: tt.calendar.date_ranges,
- dates: tt.calendar.dates,
- shared: tt.calendar.shared
- }
- end
- end
+child(:journey_pattern) do |journey_pattern|
+ attributes :id, :objectid, :name, :published_name
end
-child :footnotes do |footnotes|
- node do |footnote|
- node(:id) { footnote.id }
+child(:time_tables, :object_root => false) do |time_tables|
+ attributes :id, :objectid, :comment
+ child(:calendar) do
+ attributes :id, :name, :date_ranges, :dates, :shared
end
end
-child :vehicle_journey_at_stops do |vehicle_stops|
+child :footnotes, :object_root => false do |footnotes|
+ attributes :id, :code, :label
+end
+
+child(:vehicle_journey_at_stops_matrix, :object_root => false) do |vehicle_stops|
node do |vehicle_stop|
- node(:stop_area_object_id) { vehicle_stop.stop_point.stop_area.objectid }
- [ :connecting_service_id, :arrival_time, :departure_time, :boarding_alighting_possibility].each do |attr|
- node( attr) do
- vehicle_stop.send(attr)
- end unless vehicle_stop.send(attr).nil?
+ node(:dummy) { !vehicle_stop.stop_point_id? }
+ node(:stop_area_object_id) do
+ vehicle_stop.stop_point ? vehicle_stop.stop_point.stop_area.objectid : nil
+ end
+ node(:stop_area_name) do
+ vehicle_stop.stop_point ? vehicle_stop.stop_point.stop_area.name : nil
+ end
+ node(:stop_area_cityname) do
+ vehicle_stop.stop_point ? vehicle_stop.stop_point.stop_area.city_name : nil
+ end
+
+ [:id, :connecting_service_id, :boarding_alighting_possibility].map do |att|
+ node(att) { vehicle_stop.send(att) ? vehicle_stop.send(att) : nil }
+ end
+
+ [:arrival_time, :departure_time].map do |att|
+ node(att) do |vs|
+ {
+ hour: vs.send(att).try(:strftime, '%H'),
+ minute: vs.send(att).try(:strftime, '%M')
+ }
+ end
end
end
end
diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml
index 9597b992c..7b3b7181f 100644
--- a/config/locales/vehicle_journeys.fr.yml
+++ b/config/locales/vehicle_journeys.fr.yml
@@ -55,7 +55,7 @@ fr:
translation_form: "Cloner la course"
journey_frequencies: "Créneau horaire"
index:
- title: "Horaires de la séquence d'arrêts %{route}"
+ title: "Horaires de '%{route}'"
vehicle_journeys: "Horaires de départ aux arrêts"
selection: "Filtrer sur"
selection_all: "Tous"
diff --git a/config/routes.rb b/config/routes.rb
index 943acb5cb..33f3961b1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -79,7 +79,7 @@ ChouetteIhm::Application.routes.draw do
resources :autocomplete_stop_areas, only: [:show, :index] do
get 'around', on: :member
end
- resources :autocomplete_time_tables
+ resources :autocomplete_time_tables, only: [:index]
resources :autocomplete_route_sections
resources :autocomplete_timebands
resources :group_of_lines, controller: "referential_group_of_lines" do
@@ -115,6 +115,7 @@ ChouetteIhm::Application.routes.draw do
post 'selection'
end
end
+ resource :vehicle_journeys_collection, :only => [:show, :update]
resources :vehicle_journeys, :vehicle_journey_frequencies do
get 'select_journey_pattern', :on => :member
resources :vehicle_translations
diff --git a/db/schema.rb b/db/schema.rb
index 339713145..b6528e037 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -519,6 +519,8 @@ ActiveRecord::Schema.define(version: 20170309102656) do
t.datetime "updated_at"
t.spatial "input_geometry", limit: {:srid=>4326, :type=>"line_string"}
t.spatial "processed_geometry", limit: {:srid=>4326, :type=>"line_string"}
+ t.datetime "created_at"
+ t.datetime "updated_at"
end
create_table "routes", force: true do |t|
diff --git a/package.json b/package.json
index f23ab76f1..019e63063 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"babelify": "7.3.0",
"browserify": "13.1.1",
"browserify-incremental": "3.1.1",
+ "lodash": "^4.17.4",
"react": "15.3.2",
"react-dom": "15.3.2",
"react-redux": "4.4.5",
@@ -20,6 +21,9 @@
"devDependencies": {
"es6-object-assign": "1.0.3",
"react-addons-test-utils": "15.3.2",
- "sinon": "1.17.7"
+ "sinon": "1.17.7",
+ "redux-logger": "2.7.4",
+ "redux-thunk": "2.1.0",
+ "redux-promise": "0.5.3"
}
}
diff --git a/spec/controllers/vehicle_journeys_collections_controller_spec.rb b/spec/controllers/vehicle_journeys_collections_controller_spec.rb
new file mode 100644
index 000000000..39ea55761
--- /dev/null
+++ b/spec/controllers/vehicle_journeys_collections_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe VehicleJourneysCollectionsController, :type => :controller do
+
+end
diff --git a/spec/javascripts/journey_patterns/reducers/pagination_spec.js b/spec/javascripts/journey_patterns/reducers/pagination_spec.js
index 4800451e9..d0f9fef47 100644
--- a/spec/javascripts/journey_patterns/reducers/pagination_spec.js
+++ b/spec/javascripts/journey_patterns/reducers/pagination_spec.js
@@ -3,9 +3,9 @@ var reducer = require('es6_browserified/journey_patterns/reducers/pagination')
const diff = 1
let state = {
page : 2,
- totalCount : 25,
+ totalCount : 50,
stateChanged: false,
- perPage: 12
+ perPage: 20
}
let pagination = Object.assign({}, state)
const dispatch = function(){}
diff --git a/spec/javascripts/vehicle_journeys/actions_spec.js b/spec/javascripts/vehicle_journeys/actions_spec.js
new file mode 100644
index 000000000..ec52036d6
--- /dev/null
+++ b/spec/javascripts/vehicle_journeys/actions_spec.js
@@ -0,0 +1,394 @@
+var actions = require('es6_browserified/vehicle_journeys/actions')
+
+const dispatch = function(){}
+const currentPage = 1
+
+describe('when cannot fetch api', () => {
+ it('should create an action to toggle error', () => {
+ const expectedAction = {
+ type: 'UNAVAILABLE_SERVER',
+ }
+ expect(actions.unavailableServer()).toEqual(expectedAction)
+ })
+})
+describe('when fetching api', () => {
+ it('should create an action to fetch api', () => {
+ const expectedAction = {
+ type: 'FETCH_API',
+ }
+ expect(actions.fetchingApi()).toEqual(expectedAction)
+ })
+})
+describe('when receiveJourneyPatterns is triggered', () => {
+ it('should create an action to pass json to reducer', () => {
+ const json = undefined
+ const expectedAction = {
+ type: 'RECEIVE_VEHICLE_JOURNEYS',
+ json
+ }
+ expect(actions.receiveVehicleJourneys()).toEqual(expectedAction)
+ })
+})
+describe('when clicking on add button', () => {
+ it('should create an action to open a create modal', () => {
+ const expectedAction = {
+ type: 'CREATE_VEHICLEJOURNEY_MODAL',
+ }
+ expect(actions.openCreateModal()).toEqual(expectedAction)
+ })
+})
+describe('when using select2 to pick a journey pattern', () => {
+ it('should create an action to select a journey pattern inside modal', () => {
+ let selectedJP = {
+ id: 1,
+ object_id: 2,
+ name: 'test',
+ published_name: 'test'
+ }
+ const expectedAction = {
+ type: 'SELECT_JP_CREATE_MODAL',
+ selectedItem:{
+ id: selectedJP.id,
+ objectid: selectedJP.object_id,
+ name: selectedJP.name,
+ published_name: selectedJP.published_name
+ }
+ }
+ expect(actions.selectJPCreateModal(selectedJP)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on validate button inside create modal', () => {
+ it('should create an action to create a new vehicle journey', () => {
+ const data = {}
+ const selectedJourneyPattern = {}
+ const expectedAction = {
+ type: 'ADD_VEHICLEJOURNEY',
+ data,
+ selectedJourneyPattern
+ }
+ expect(actions.addVehicleJourney(data, selectedJourneyPattern)).toEqual(expectedAction)
+ })
+})
+describe('when previous navigation button is clicked', () => {
+ it('should create an action to go to previous page', () => {
+ const nextPage = false
+ const queryString = ''
+ const pagination = {
+ totalCount: 25,
+ perPage: 12,
+ page:1
+ }
+ const expectedAction = {
+ type: 'GO_TO_PREVIOUS_PAGE',
+ dispatch,
+ pagination,
+ nextPage,
+ queryString
+ }
+ expect(actions.goToPreviousPage(dispatch, pagination, queryString)).toEqual(expectedAction)
+ })
+})
+describe('when next navigation button is clicked', () => {
+ it('should create an action to go to next page', () => {
+ const queryString = ''
+ const nextPage = true
+ const pagination = {
+ totalCount: 25,
+ perPage: 12,
+ page:1
+ }
+ const expectedAction = {
+ type: 'GO_TO_NEXT_PAGE',
+ dispatch,
+ pagination,
+ nextPage,
+ queryString
+ }
+ expect(actions.goToNextPage(dispatch, pagination, queryString)).toEqual(expectedAction)
+ })
+})
+describe('when checking a vehicleJourney', () => {
+ it('should create an action to select vj', () => {
+ const index = 1
+ const expectedAction = {
+ type: 'SELECT_VEHICLEJOURNEY',
+ index
+ }
+ expect(actions.selectVehicleJourney(index)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on cancel selection button', () => {
+ it('should create an action to cancel whole selection', () => {
+ const index = 1
+ const expectedAction = {
+ type: 'CANCEL_SELECTION'
+ }
+ expect(actions.cancelSelection()).toEqual(expectedAction)
+ })
+})
+describe('when clicking on delete button', () => {
+ it('should create an action to delete vj', () => {
+ const expectedAction = {
+ type: 'DELETE_VEHICLEJOURNEYS',
+ }
+ expect(actions.deleteVehicleJourneys()).toEqual(expectedAction)
+ })
+})
+describe('when toggling arrivals', () => {
+ it('should create an action to toggleArrivals', () => {
+ const expectedAction = {
+ type: 'TOGGLE_ARRIVALS',
+ }
+ expect(actions.toggleArrivals()).toEqual(expectedAction)
+ })
+})
+describe('when updating vjas time', () => {
+ it('should create an action to update time', () => {
+ const val = 33, subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true
+ const expectedAction = {
+ type: 'UPDATE_TIME',
+ val,
+ subIndex,
+ index,
+ timeUnit,
+ isDeparture,
+ isArrivalsToggled
+ }
+ expect(actions.updateTime(val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on validate button inside shifting modal', () => {
+ it('should create an action to shift a vehiclejourney schedule', () => {
+ const data = {}
+ const expectedAction = {
+ type: 'SHIFT_VEHICLEJOURNEY',
+ data
+ }
+ expect(actions.shiftVehicleJourney(data)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on validate button inside editing modal', () => {
+ it('should create an action to update a vehiclejourney', () => {
+ const data = {}
+ const expectedAction = {
+ type: 'EDIT_VEHICLEJOURNEY',
+ data
+ }
+ expect(actions.editVehicleJourney(data)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on validate button inside duplicating modal', () => {
+ it('should create an action to duplicate a vehiclejourney schedule', () => {
+ const data = {}
+ const expectedAction = {
+ type: 'DUPLICATE_VEHICLEJOURNEY',
+ data
+ }
+ expect(actions.duplicateVehicleJourney(data)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on edit notes modal', () => {
+ it('should create an action to open footnotes modal', () => {
+ const vehicleJourney = {}
+ const expectedAction = {
+ type: 'EDIT_NOTES_VEHICLEJOURNEY_MODAL',
+ vehicleJourney
+ }
+ expect(actions.openNotesEditModal(vehicleJourney)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on a footnote button inside footnote modal', () => {
+ it('should create an action to toggle this footnote', () => {
+ const footnote = {}, isShown = true
+ const expectedAction = {
+ type: 'TOGGLE_FOOTNOTE_MODAL',
+ footnote,
+ isShown
+ }
+ expect(actions.toggleFootnoteModal(footnote, isShown)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on validate button inside footnote modal', () => {
+ it('should create an action to update vj footnotes', () => {
+ const footnotes = []
+ const expectedAction = {
+ type: 'EDIT_VEHICLEJOURNEY_NOTES',
+ footnotes
+ }
+ expect(actions.editVehicleJourneyNotes(footnotes)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on calendar button in toolbox', () => {
+ it('should create an action to open calendar modal', () => {
+ const vehicleJourneys = []
+ const expectedAction = {
+ type: 'EDIT_CALENDARS_VEHICLEJOURNEY_MODAL',
+ vehicleJourneys
+ }
+ expect(actions.openCalendarsEditModal(vehicleJourneys)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on delete button next to a timetable inside modal', () => {
+ it('should create an action to delete timetable from selected vehicle journeys', () => {
+ const timetable = {}
+ const expectedAction = {
+ type: 'DELETE_CALENDAR_MODAL',
+ timetable
+ }
+ expect(actions.deleteCalendarModal(timetable)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on validate button inside calendars modal', () => {
+ it('should create an action to update vj calendars', () => {
+ const vehicleJourneys = []
+ const expectedAction = {
+ type: 'EDIT_VEHICLEJOURNEYS_CALENDARS',
+ vehicleJourneys
+ }
+ expect(actions.editVehicleJourneyCalendars(vehicleJourneys)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on add button inside calendars modal', () => {
+ it('should create an action to add the selected timetable to preselected vjs', () => {
+ const expectedAction = {
+ type: 'ADD_SELECTED_TIMETABLE',
+ }
+ expect(actions.addSelectedTimetable()).toEqual(expectedAction)
+ })
+})
+describe('when using select2 to pick a timetable', () => {
+ it('should create an action to select a timetable inside modal', () => {
+ let selectedTT = {
+ id: 1,
+ objectid: 2,
+ comment: 'test',
+ }
+ const expectedAction = {
+ type: 'SELECT_TT_CALENDAR_MODAL',
+ selectedItem:{
+ id: selectedTT.id,
+ objectid: selectedTT.objectid,
+ comment: selectedTT.comment,
+ }
+ }
+ expect(actions.selectTTCalendarsModal(selectedTT)).toEqual(expectedAction)
+ })
+})
+describe('when clicking on reset button inside query filters', () => {
+ it('should create an action to reset the query filters', () => {
+ const expectedAction = {
+ type: 'BATCH',
+ payload: [
+ {type: 'RESET_FILTERS'},
+ {type: 'RESET_PAGINATION'},
+ {type: 'QUERY_FILTER_VEHICLEJOURNEYS', dispatch: undefined},
+ ]
+ }
+ expect(actions.resetFilters()).toEqual(expectedAction)
+ })
+})
+describe('when clicking on filter button inside query filters', () => {
+ it('should create an action to filter', () => {
+ const expectedAction = {
+ type: 'BATCH',
+ payload: [
+ {type: 'CREATE_QUERY_STRING'},
+ {type: 'RESET_PAGINATION'},
+ {type: 'QUERY_FILTER_VEHICLEJOURNEYS', dispatch: undefined},
+ ]
+ }
+ expect(actions.filterQuery()).toEqual(expectedAction)
+ })
+})
+describe('when clicking on checkbox to show vj without schedule', () => {
+ it('should create an action to toggle this filter', () => {
+ const expectedAction = {
+ type: 'TOGGLE_WITHOUT_SCHEDULE',
+ }
+ expect(actions.toggleWithoutSchedule()).toEqual(expectedAction)
+ })
+})
+describe('when setting new interval', () => {
+ const val = 1
+ const unit = 'hour'
+ it('should create actions to update intervals in state', () => {
+ let expectedAction = {
+ type: 'UPDATE_START_TIME_FILTER',
+ val,
+ unit
+ }
+ expect(actions.updateStartTimeFilter(val, unit)).toEqual(expectedAction)
+ })
+ it('should create actions to update intervals in state', () => {
+ let expectedAction = {
+ type: 'UPDATE_END_TIME_FILTER',
+ val,
+ unit
+ }
+ expect(actions.updateEndTimeFilter(val, unit)).toEqual(expectedAction)
+ })
+})
+describe('when using select2 to pick a timetable in the filters', () => {
+ it('should create an action to select a timetable as a filter', () => {
+ let selectedTT = {
+ id: 1,
+ objectid: 2,
+ comment: 'test',
+ }
+ const expectedAction = {
+ type: 'SELECT_TT_FILTER',
+ selectedItem:{
+ id: selectedTT.id,
+ objectid: selectedTT.objectid,
+ comment: selectedTT.comment,
+ }
+ }
+ expect(actions.filterSelect2Timetable(selectedTT)).toEqual(expectedAction)
+ })
+})
+describe('when using select2 to pick a journeypattern in the filters', () => {
+ it('should create an action to select a journey pattern as a filter', () => {
+ let selectedJP = {
+ id: 1,
+ object_id: 2,
+ name: 'test',
+ published_name: 'test'
+ }
+ const expectedAction = {
+ type: 'SELECT_JP_FILTER',
+ selectedItem:{
+ id: selectedJP.id,
+ objectid: selectedJP.object_id,
+ name: selectedJP.name,
+ published_name: selectedJP.published_name
+ }
+ }
+ expect(actions.filterSelect2JourneyPattern(selectedJP)).toEqual(expectedAction)
+ })
+})
+describe('when user clicked either on filter or reset button in filters', () => {
+ it('should create an action to reset pagination', () => {
+ const expectedAction = {
+ type: 'RESET_PAGINATION',
+ }
+ expect(actions.resetPagination()).toEqual(expectedAction)
+ })
+})
+describe('when user clicked either on filter or reset button in filters', () => {
+ it('should create an action to create a queryString with params filters', () => {
+ const expectedAction = {
+ type: 'CREATE_QUERY_STRING',
+ }
+ expect(actions.createQueryString()).toEqual(expectedAction)
+ })
+})
+describe('when submitting new vj', () => {
+ it('should create an action to update pagination totalCount', () => {
+ const diff = 1
+ const expectedAction = {
+ type: 'UPDATE_TOTAL_COUNT',
+ diff
+ }
+ expect(actions.updateTotalCount(diff)).toEqual(expectedAction)
+ })
+})
diff --git a/spec/javascripts/vehicle_journeys/reducers/filters_spec.js b/spec/javascripts/vehicle_journeys/reducers/filters_spec.js
new file mode 100644
index 000000000..84608243b
--- /dev/null
+++ b/spec/javascripts/vehicle_journeys/reducers/filters_spec.js
@@ -0,0 +1,154 @@
+var statusReducer = require('es6_browserified/vehicle_journeys/reducers/filters')
+
+let state = {}
+
+describe('filters reducer', () => {
+ const cleanInterval = {
+ start:{
+ hour: '00',
+ minute: '00'
+ },
+ end:{
+ hour: '23',
+ minute: '59'
+ }
+ }
+ beforeEach(() => {
+ state = {
+ toggleArrivals: false,
+ query: {
+ interval: {
+ start:{
+ hour: '11',
+ minute: '11'
+ },
+ end:{
+ hour: '22',
+ minute: '22'
+ }
+ },
+ journeyPattern: {},
+ timetable: {},
+ withoutSchedule: false,
+ },
+ queryString: ''
+ }
+ })
+
+ it('should return the initial state', () => {
+ expect(
+ statusReducer(undefined, {})
+ ).toEqual({})
+ })
+
+ it('should handle TOGGLE_ARRIVALS', () => {
+ expect(
+ statusReducer(state, {
+ type: 'TOGGLE_ARRIVALS'
+ })
+ ).toEqual(Object.assign({}, state, {toggleArrivals: true}))
+ })
+
+ it('should handle RESET_FILTERS', () => {
+ let cleanQuery = JSON.parse(JSON.stringify(state.query))
+ cleanQuery.interval = cleanInterval
+ expect(
+ statusReducer(state, {
+ type: 'RESET_FILTERS'
+ })
+ ).toEqual(Object.assign({}, state, {query: cleanQuery}))
+ })
+
+ it('should handle TOGGLE_WITHOUT_SCHEDULE', () => {
+ let rslt = JSON.parse(JSON.stringify(state.query))
+ rslt.withoutSchedule = true
+ expect(
+ statusReducer(state, {
+ type: 'TOGGLE_WITHOUT_SCHEDULE'
+ })
+ ).toEqual(Object.assign({}, state, {query: rslt}))
+ })
+
+ it('should handle UPDATE_START_TIME_FILTER', () => {
+ let val = 12
+ let unit = 'minute'
+ let rslt = JSON.parse(JSON.stringify(state.query))
+ rslt.interval.start.minute = String(val)
+ expect(
+ statusReducer(state, {
+ type: 'UPDATE_START_TIME_FILTER',
+ val,
+ unit
+ })
+ ).toEqual(Object.assign({}, state, {query: rslt}))
+ })
+
+ it('should handle UPDATE_START_TIME_FILTER and not make any update', () => {
+ let val = 23
+ let unit = 'hour'
+ expect(
+ statusReducer(state, {
+ type: 'UPDATE_START_TIME_FILTER',
+ val,
+ unit
+ })
+ ).toEqual(state)
+ })
+
+ it('should handle UPDATE_END_TIME_FILTER', () => {
+ let val = 12
+ let unit = 'minute'
+ let rslt = JSON.parse(JSON.stringify(state.query))
+ rslt.interval.end.minute = String(val)
+ expect(
+ statusReducer(state, {
+ type: 'UPDATE_END_TIME_FILTER',
+ val,
+ unit
+ })
+ ).toEqual(Object.assign({}, state, {query: rslt}))
+ })
+
+ it('should handle UPDATE_END_TIME_FILTER and not make any update', () => {
+ let val = 1
+ let unit = 'hour'
+ expect(
+ statusReducer(state, {
+ type: 'UPDATE_END_TIME_FILTER',
+ val,
+ unit
+ })
+ ).toEqual(state)
+ })
+
+ it('should handle SELECT_TT_FILTER', () => {
+ let newTimetable = {timetable : {id: 1}}
+ let newQuery = Object.assign({}, state.query, newTimetable)
+ expect(
+ statusReducer(state, {
+ type: 'SELECT_TT_FILTER',
+ selectedItem: {id: 1}
+ })
+ ).toEqual(Object.assign({}, state, {query: newQuery}))
+ })
+
+ it('should handle SELECT_JP_FILTER', () => {
+ let newJourneyPattern = {journeyPattern : {id: 1}}
+ let newQuery = Object.assign({}, state.query, newJourneyPattern)
+ expect(
+ statusReducer(state, {
+ type: 'SELECT_JP_FILTER',
+ selectedItem: {id: 1}
+ })
+ ).toEqual(Object.assign({}, state, {query: newQuery}))
+ })
+
+ it('should handle SELECT_JP_FILTER', () => {
+ let strResult = "q%5Bjourney_pattern_id_eq%5D=undefined&q%5Btime_tables_id_eq%5D=undefined&q%5Bvehicle_journey_at_stops_departure_time_gteq%5D=11%3A11&q%5Bvehicle_journey_at_stops_departure_time_lteq%5D=22%3A22"
+ expect(
+ statusReducer(state, {
+ type: 'CREATE_QUERY_STRING',
+ })
+ ).toEqual(Object.assign({}, state, {queryString: strResult}))
+ })
+})
diff --git a/spec/javascripts/vehicle_journeys/reducers/modal_spec.js b/spec/javascripts/vehicle_journeys/reducers/modal_spec.js
new file mode 100644
index 000000000..7c81d729b
--- /dev/null
+++ b/spec/javascripts/vehicle_journeys/reducers/modal_spec.js
@@ -0,0 +1,160 @@
+var modalReducer = require('es6_browserified/vehicle_journeys/reducers/modal')
+
+let state = {}
+
+const cb = function(){}
+
+describe('modal reducer', () => {
+ beforeEach(() => {
+ state = {
+ type: '',
+ modalProps: {},
+ confirmModal: {}
+ }
+ })
+
+ it('should return the initial state', () => {
+ expect(
+ modalReducer(undefined, {})
+ ).toEqual({})
+ })
+
+ it('should handle OPEN_CONFIRM_MODAL', () => {
+ let newState = Object.assign({}, state, {
+ type: 'confirm',
+ confirmModal: {
+ callback: cb
+ }
+ })
+ expect(
+ modalReducer(state, {
+ type: 'OPEN_CONFIRM_MODAL',
+ callback: cb
+ })
+ ).toEqual(newState)
+ })
+
+ it('should handle CREATE_VEHICLEJOURNEY_MODAL', () => {
+ expect(
+ modalReducer(state, {
+ type: 'CREATE_VEHICLEJOURNEY_MODAL'
+ })
+ ).toEqual(Object.assign({}, state, { type: 'create' }))
+ })
+
+ it('should handle SELECT_JP_CREATE_MODAL', () => {
+ let newModalProps = {selectedJPModal : {id: 1}}
+ expect(
+ modalReducer(state, {
+ type: 'SELECT_JP_CREATE_MODAL',
+ selectedItem: {id: 1}
+ })
+ ).toEqual(Object.assign({}, state, {modalProps: newModalProps}))
+ })
+
+ it('should handle CLOSE_MODAL', () => {
+ expect(
+ modalReducer(state, {
+ type: 'CLOSE_MODAL'
+ })
+ ).toEqual(state)
+ })
+
+ it('should handle EDIT_NOTES_VEHICLEJOURNEY_MODAL', () => {
+ let vehicleJourney = {}
+ let modalPropsResult = {
+ vehicleJourney: {}
+ }
+ expect(
+ modalReducer(state, {
+ type: 'EDIT_NOTES_VEHICLEJOURNEY_MODAL',
+ vehicleJourney
+ })
+ ).toEqual(Object.assign({}, state, {type: 'notes_edit', modalProps: modalPropsResult}))
+ })
+
+ it('should handle TOGGLE_FOOTNOTE_MODAL', () => {
+ state.modalProps = {vehicleJourney : {footnotes: [{}, {}]}}
+ let footnote = {}
+ let newState = {
+ // for the sake of the test, no need to specify the type
+ type: '',
+ modalProps:{vehicleJourney: {footnotes: [{},{},{}]}},
+ confirmModal: {}
+ }
+ expect(
+ modalReducer(state, {
+ type: 'TOGGLE_FOOTNOTE_MODAL',
+ footnote,
+ isShown: true
+ })
+ ).toEqual(newState)
+ })
+
+ it('should handle EDIT_CALENDARS_VEHICLEJOURNEY_MODAL', () => {
+ let vehicleJourneys = []
+ let modalPropsResult = {
+ vehicleJourneys: [],
+ timetables: []
+ }
+ expect(
+ modalReducer(state, {
+ type: 'EDIT_CALENDARS_VEHICLEJOURNEY_MODAL',
+ vehicleJourneys
+ })
+ ).toEqual(Object.assign({}, state, {type: 'calendars_edit', modalProps: modalPropsResult}))
+ })
+
+ it('should handle SELECT_TT_CALENDAR_MODAL', () => {
+ let newModalProps = {selectedTimetable : {id: 1}}
+ expect(
+ modalReducer(state, {
+ type: 'SELECT_TT_CALENDAR_MODAL',
+ selectedItem: {id: 1}
+ })
+ ).toEqual(Object.assign({}, state, {modalProps: newModalProps}))
+ })
+
+ it('should handle ADD_SELECTED_TIMETABLE', () => {
+ let fakeTimetables = [{'test': 'test'}, {'test 2': 'test 2'}, {'add': 'add'}]
+ let newTimeTables = [{'test': 'test'}, {'test 2': 'test 2'}, {'add': 'add'}]
+ let fakeVehicleJourneys= [{time_tables: fakeTimetables}, {time_tables: newTimeTables}]
+ state.modalProps.vehicleJourneys = fakeVehicleJourneys
+ state.modalProps.timetables = fakeTimetables
+ state.modalProps.selectedTimetable = {'add': 'add'}
+ let newState = {
+ type: '',
+ modalProps:{
+ vehicleJourneys: [{time_tables: newTimeTables},{time_tables: newTimeTables}],
+ timetables: [{'test': 'test'},{'test 2': 'test 2'},{'add': 'add'}],
+ selectedTimetable: {'add': 'add'}
+ },
+ confirmModal: {}
+ }
+ expect(
+ modalReducer(state, {
+ type: 'ADD_SELECTED_TIMETABLE',
+ })
+ ).toEqual(newState)
+ })
+
+ it('should handle DELETE_CALENDAR_MODAL', () => {
+ let deletableTimetable = {'delete': 'delete'}
+ let fakeTimetables = [{'test': 'test'}, {'test 2': 'test 2'}, deletableTimetable]
+ let newTimeTables = [{'test': 'test'}, {'test 2': 'test 2'}]
+ let fakeVehicleJourneys= [{time_tables: fakeTimetables}, {time_tables: fakeTimetables}]
+ state.modalProps = Object.assign({}, state.modalProps,{vehicleJourneys : fakeVehicleJourneys, timetables: fakeTimetables })
+ let newState = {
+ // for the sake of the test, no need to specify the type
+ type: '',
+ modalProps:{vehicleJourneys: [{time_tables: newTimeTables},{time_tables: newTimeTables}], timetables: [{'test': 'test'},{'test 2': 'test 2'}]},
+ confirmModal: {}
+ }
+ expect(
+ modalReducer(state, {
+ type: 'DELETE_CALENDAR_MODAL',
+ timetable: deletableTimetable
+ })
+ ).toEqual(newState)
+ })
+})
diff --git a/spec/javascripts/vehicle_journeys/reducers/pagination_spec.js b/spec/javascripts/vehicle_journeys/reducers/pagination_spec.js
new file mode 100644
index 000000000..ff74e1a0d
--- /dev/null
+++ b/spec/javascripts/vehicle_journeys/reducers/pagination_spec.js
@@ -0,0 +1,114 @@
+var reducer = require('es6_browserified/vehicle_journeys/reducers/pagination')
+
+const diff = 1
+let state = {
+ page : 2,
+ totalCount : 25,
+ stateChanged: false,
+ perPage: 12
+}
+let pagination = Object.assign({}, state)
+const dispatch = function(){}
+
+describe('pagination reducer, given parameters allowing page change', () => {
+
+ it('should return the initial state', () => {
+ expect(
+ reducer(undefined, {})
+ ).toEqual({})
+ })
+
+ it('should handle RESET_PAGINATION', () => {
+ expect(
+ reducer(state, {
+ type: 'RESET_PAGINATION',
+ })
+ ).toEqual(Object.assign({}, state, {page: 1}))
+ })
+
+ it('should handle UPDATE_TOTAL_COUNT', () => {
+ expect(
+ reducer(state, {
+ type: 'UPDATE_TOTAL_COUNT',
+ diff: 1
+ })
+ ).toEqual(Object.assign({}, state, {totalCount: 24}))
+ })
+
+ it('should handle GO_TO_NEXT_PAGE and change state', () => {
+ expect(
+ reducer(state, {
+ type: 'GO_TO_NEXT_PAGE',
+ dispatch,
+ pagination,
+ nextPage : true
+ })
+ ).toEqual(Object.assign({}, state, {page : state.page + 1, stateChanged: false}))
+ })
+
+ it('should return GO_TO_PREVIOUS_PAGE and change state', () => {
+ expect(
+ reducer(state, {
+ type: 'GO_TO_PREVIOUS_PAGE',
+ dispatch,
+ pagination,
+ nextPage : false
+ })
+ ).toEqual(Object.assign({}, state, {page : state.page - 1, stateChanged: false}))
+ })
+
+ it('should handle UPDATE_TIME', () => {
+ const val = '33', subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true
+ expect(
+ reducer(state, {
+ type: 'UPDATE_TIME',
+ val,
+ subIndex,
+ index,
+ timeUnit,
+ isDeparture,
+ isArrivalsToggled
+ })
+ ).toEqual(Object.assign({}, state, {stateChanged: true}))
+ })
+
+})
+
+
+describe('pagination reducer, given parameters not allowing to go to previous page', () => {
+
+ beforeEach(()=>{
+ state.page = 1
+ pagination.page = 1
+ })
+
+ it('should return GO_TO_PREVIOUS_PAGE and not change state', () => {
+ expect(
+ reducer(state, {
+ type: 'GO_TO_PREVIOUS_PAGE',
+ dispatch,
+ pagination,
+ nextPage : false
+ })
+ ).toEqual(state)
+ })
+})
+
+describe('pagination reducer, given parameters not allowing to go to next page', () => {
+
+ beforeEach(()=>{
+ state.page = 3
+ pagination.page = 3
+ })
+
+ it('should return GO_TO_NEXT_PAGE and not change state', () => {
+ expect(
+ reducer(state, {
+ type: 'GO_TO_NEXT_PAGE',
+ dispatch,
+ pagination,
+ nextPage : true
+ })
+ ).toEqual(state)
+ })
+})
diff --git a/spec/javascripts/vehicle_journeys/reducers/status_spec.js b/spec/javascripts/vehicle_journeys/reducers/status_spec.js
new file mode 100644
index 000000000..d48d48f4a
--- /dev/null
+++ b/spec/javascripts/vehicle_journeys/reducers/status_spec.js
@@ -0,0 +1,45 @@
+var statusReducer = require('es6_browserified/vehicle_journeys/reducers/status')
+
+let state = {}
+
+const dispatch = function(){}
+
+describe('status reducer', () => {
+ beforeEach(() => {
+ state = {
+ fetchSuccess: true,
+ isFetching: false
+ }
+ })
+
+ it('should return the initial state', () => {
+ expect(
+ statusReducer(undefined, {})
+ ).toEqual({})
+ })
+
+ it('should handle UNAVAILABLE_SERVER', () => {
+ expect(
+ statusReducer(state, {
+ type: 'UNAVAILABLE_SERVER'
+ })
+ ).toEqual(Object.assign({}, state, {fetchSuccess: false}))
+ })
+
+ it('should handle RECEIVE_VEHICLE_JOURNEYS', () => {
+ expect(
+ statusReducer(state, {
+ type: 'RECEIVE_VEHICLE_JOURNEYS'
+ })
+ ).toEqual(Object.assign({}, state, {fetchSuccess: true, isFetching: false}))
+ })
+
+ it('should handle FETCH_API', () => {
+ expect(
+ statusReducer(state, {
+ type: 'FETCH_API'
+ })
+ ).toEqual(Object.assign({}, state, {isFetching: true}))
+ })
+
+})
diff --git a/spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js b/spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js
new file mode 100644
index 000000000..dbdb253c7
--- /dev/null
+++ b/spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js
@@ -0,0 +1,259 @@
+var vjReducer = require('es6_browserified/vehicle_journeys/reducers/vehicleJourneys')
+
+let state = []
+let stateModal = {
+ type: '',
+ modalProps: {},
+ confirmModal: {}
+}
+let fakeFootnotes = [{
+ id: 1,
+ code: 1,
+ label: "1"
+},{
+ id: 2,
+ code: 2,
+ label: "2"
+}]
+
+let fakeTimeTables = [{
+ published_journey_name: 'test 1',
+ objectid: '1'
+},{
+ published_journey_name: 'test 2',
+ objectid: '2'
+},{
+ published_journey_name: 'test 3',
+ objectid: '3'
+}]
+let fakeVJAS = [{
+ delta : 627,
+ arrival_time : {
+ hour: '11',
+ minute: '55'
+ },
+ departure_time : {
+ hour: '22',
+ minute: '22'
+ },
+ stop_area_object_id : "FR:92024:ZDE:420553:STIF"
+}]
+
+describe('vehicleJourneys reducer', () => {
+ beforeEach(()=>{
+ state = [
+ {
+ journey_pattern_id: 1,
+ published_journey_name: "vj1",
+ objectid: '11',
+ deletable: false,
+ selected: true,
+ footnotes: fakeFootnotes,
+ time_tables: fakeTimeTables,
+ vehicle_journey_at_stops: fakeVJAS
+ },
+ {
+ journey_pattern_id: 2,
+ published_journey_name: "vj2",
+ objectid: '22',
+ selected: false,
+ deletable: false,
+ footnotes: fakeFootnotes,
+ time_tables: fakeTimeTables,
+ vehicle_journey_at_stops: fakeVJAS
+ }
+ ]
+ })
+
+ it('should return the initial state', () => {
+ expect(
+ vjReducer(undefined, {})
+ ).toEqual([])
+ })
+
+
+ it('should handle ADD_VEHICLEJOURNEY', () => {
+ let resultVJ = [{
+ delta : 0,
+ arrival_time : {
+ hour: '00',
+ minute: '00'
+ },
+ departure_time : {
+ hour: '00',
+ minute: '00'
+ }
+ }]
+ let fakeData = {
+ published_journey_name: {value: 'test'}
+ }
+ let fakeSelectedJourneyPattern = { id: "1"}
+ expect(
+ vjReducer(state, {
+ type: 'ADD_VEHICLEJOURNEY',
+ data: fakeData,
+ selectedJourneyPattern: fakeSelectedJourneyPattern
+ })
+ ).toEqual([{
+ dummy: false,
+ journey_pattern: fakeSelectedJourneyPattern,
+ published_journey_name: 'test',
+ objectid: '',
+ footnotes: [],
+ time_tables: [],
+ vehicle_journey_at_stops: resultVJ,
+ selected: false,
+ deletable: false
+ }, ...state])
+ })
+
+ it('should handle RECEIVE_VEHICLE_JOURNEYS', () => {
+ expect(
+ vjReducer(state, {
+ type: 'RECEIVE_VEHICLE_JOURNEYS',
+ json: state
+ })
+ ).toEqual(state)
+ })
+
+ it('should handle UPDATE_TIME', () => {
+ const val = '33', subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true
+ let newVJAS = [{
+ delta: 638,
+ arrival_time : {
+ hour: '11',
+ minute: '55'
+ },
+ departure_time : {
+ hour: '22',
+ minute: '33'
+ },
+ stop_area_object_id : "FR:92024:ZDE:420553:STIF"
+ }]
+ let newVJ = Object.assign({}, state[0], {vehicle_journey_at_stops: newVJAS})
+ expect(
+ vjReducer(state, {
+ type: 'UPDATE_TIME',
+ val,
+ subIndex,
+ index,
+ timeUnit,
+ isDeparture,
+ isArrivalsToggled
+ })
+ ).toEqual([newVJ, state[1]])
+ })
+
+ it('should handle SELECT_VEHICLEJOURNEY', () => {
+ const index = 1
+ const newVJ = Object.assign({}, state[1], {selected: true})
+ expect(
+ vjReducer(state, {
+ type: 'SELECT_VEHICLEJOURNEY',
+ index
+ })
+ ).toEqual([state[0], newVJ])
+ })
+
+ it('should handle CANCEL_SELECTION', () => {
+ const index = 1
+ const newVJ = Object.assign({}, state[0], {selected: false})
+ expect(
+ vjReducer(state, {
+ type: 'CANCEL_SELECTION',
+ index
+ })
+ ).toEqual([newVJ, state[1]])
+ })
+
+ it('should handle DELETE_VEHICLEJOURNEYS', () => {
+ const newVJ = Object.assign({}, state[0], {deletable: true, selected: false})
+ expect(
+ vjReducer(state, {
+ type: 'DELETE_VEHICLEJOURNEYS'
+ })
+ ).toEqual([newVJ, state[1]])
+ })
+
+ it('should handle SHIFT_VEHICLEJOURNEY', () => {
+ let newVJAS = [{
+ delta: 627,
+ arrival_time : {
+ hour: '12',
+ minute: '00'
+ },
+ departure_time : {
+ hour: '22',
+ minute: '27'
+ },
+ stop_area_object_id : "FR:92024:ZDE:420553:STIF"
+ }]
+ let fakeData = {
+ objectid: {value : '11'},
+ additional_time: {value: '5'}
+ }
+ let newVJ = Object.assign({}, state[0], {vehicle_journey_at_stops: newVJAS})
+ expect(
+ vjReducer(state, {
+ type: 'SHIFT_VEHICLEJOURNEY',
+ data: fakeData
+ })
+ ).toEqual([newVJ, state[1]])
+ })
+
+ it('should handle DUPLICATE_VEHICLEJOURNEY', () => {
+ let newVJAS = [{
+ delta: 627,
+ arrival_time : {
+ hour: '12',
+ minute: '00'
+ },
+ departure_time : {
+ hour: '22',
+ minute: '27'
+ },
+ stop_area_object_id : "FR:92024:ZDE:420553:STIF"
+ }]
+ let fakeData = {
+ duplicate_number: {value : 1},
+ additional_time: {value: '5'}
+ }
+ let newVJ = Object.assign({}, state[0], {vehicle_journey_at_stops: newVJAS, selected: false})
+ newVJ.published_journey_name = state[0].published_journey_name + '-0'
+ delete newVJ['objectid']
+ expect(
+ vjReducer(state, {
+ type: 'DUPLICATE_VEHICLEJOURNEY',
+ data: fakeData
+ })
+ ).toEqual([state[0], newVJ, state[1]])
+ })
+
+ it('should handle EDIT_VEHICLEJOURNEY', () => {
+ let fakeData = {
+ published_journey_name: {value : 'test'},
+ published_journey_identifier: {value: 'test'}
+ }
+ let newVJ = Object.assign({}, state[0], {published_journey_name: fakeData.published_journey_name.value, published_journey_identifier: fakeData.published_journey_identifier.value})
+ expect(
+ vjReducer(state, {
+ type: 'EDIT_VEHICLEJOURNEY',
+ data: fakeData
+ })
+ ).toEqual([newVJ, state[1]])
+ })
+
+
+ it('should handle EDIT_VEHICLEJOURNEYS_CALENDARS', () => {
+ let newState = JSON.parse(JSON.stringify(state))
+ newState.map((vj, i) =>{
+ return Object.assign({}, vj, {time_tables : vj.time_tables[0]})
+ })
+ expect(
+ vjReducer(state, {
+ type: 'EDIT_VEHICLEJOURNEYS_CALENDARS',
+ vehicleJourneys: newState
+ })
+ ).toEqual(newState)
+ })
+})
diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb
index 91f519a59..4f55ba774 100644
--- a/spec/models/chouette/vehicle_journey_spec.rb
+++ b/spec/models/chouette/vehicle_journey_spec.rb
@@ -1,6 +1,55 @@
require 'spec_helper'
describe Chouette::VehicleJourney, :type => :model do
+ describe "state_update" do
+ def vehicle_journey_to_state vj
+ vj.attributes.slice('objectid', 'published_journey_name', 'journey_pattern_id').tap do |item|
+ item['vehicle_journey_at_stops'] = []
+
+ vj.vehicle_journey_at_stops.each do |vs|
+ at_stops = {'stop_area_object_id' => vs.stop_point.stop_area.objectid }
+ [:id, :connecting_service_id, :boarding_alighting_possibility].map do |att|
+ at_stops[att.to_s] = vs.send(att) unless vs.send(att).nil?
+ end
+
+ [:arrival_time, :departure_time].map do |att|
+ at_stops[att.to_s] = {
+ 'hour' => vs.send(att).strftime('%H'),
+ 'minute' => vs.send(att).strftime('%M'),
+ }
+ end
+ item['vehicle_journey_at_stops'] << at_stops
+ end
+ end
+ end
+
+ let(:route) { create :route }
+ let(:vehicle_journey) { create :vehicle_journey, route: route }
+ let(:state) { vehicle_journey_to_state(vehicle_journey) }
+
+ it 'should update arrival_time' do
+ stop = state['vehicle_journey_at_stops'].first
+ stop['arrival_time']['hour'] = "10"
+ stop['arrival_time']['minute'] = "10"
+
+ vehicle_journey.update_vehicle_journey_at_stops_state(state['vehicle_journey_at_stops'])
+ stop = vehicle_journey.vehicle_journey_at_stops.find(stop['id'])
+ expect(stop.arrival_time).to eq('2000-01-01 10:10:00 UTC')
+ end
+
+ it 'should update departure_time' do
+ stop = state['vehicle_journey_at_stops'].first
+ stop['departure_time']['hour'] = "12"
+ stop['departure_time']['minute'] = "12"
+
+ vehicle_journey.update_vehicle_journey_at_stops_state(state['vehicle_journey_at_stops'])
+ stop = vehicle_journey.vehicle_journey_at_stops.find(stop['id'])
+ expect(stop.departure_time).to eq('2000-01-01 12:12:00 UTC')
+ end
+ end
+
+
+
subject { create(:vehicle_journey_odd) }
describe "in_relation_to_a_journey_pattern methods" do