diff options
88 files changed, 4832 insertions, 164 deletions
| @@ -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.eotBinary files differ new file mode 100644 index 000000000..f3df36d9a --- /dev/null +++ b/app/assets/fonts/sBoiv/sboiv.eot 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=" " horiz-adv-x="512" d="" /> +<glyph unicode="" 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="" 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.ttfBinary files differ new file mode 100644 index 000000000..f2b70a1cf --- /dev/null +++ b/app/assets/fonts/sBoiv/sboiv.ttf diff --git a/app/assets/fonts/sBoiv/sboiv.woff b/app/assets/fonts/sBoiv/sboiv.woffBinary files differ new file mode 100644 index 000000000..8ede810d8 --- /dev/null +++ b/app/assets/fonts/sBoiv/sboiv.woff 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 | 
