diff options
Diffstat (limited to 'app')
73 files changed, 3636 insertions, 155 deletions
diff --git a/app/assets/fonts/sBoiv/sboiv.eot b/app/assets/fonts/sBoiv/sboiv.eot Binary files differnew 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.ttf Binary files differnew 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.woff Binary files differnew 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 |
