diff options
78 files changed, 1134 insertions, 302 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 9a7be2d84..62b7e3c99 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -305,7 +305,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mimemagic (0.3.2) - mini_portile2 (2.2.0) + mini_portile2 (2.3.0) minitest (5.10.3) multi_json (1.12.1) multi_test (0.1.2) @@ -319,8 +319,8 @@ GEM net-ssh-gateway (2.0.0) net-ssh (>= 4.0.0) newrelic_rpm (4.0.0.332) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) open4 (1.3.4) orm_adapter (0.5.0) parser (2.4.0.0) diff --git a/INSTALL.md b/INSTALL.md index ea6fbd6ab..899654a25 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -128,8 +128,22 @@ To create `Referential` objects with some data (`Route`, `JourneyPattern`, `Vehi # Troubleshooting +## Postgres + If Postgres complains about illegal type `hstore` in your tests that is probably because the shared extension is not installed, here is what to do: bundle exec rake db:test:purge Thanks to `lib/tasks/extensions.rake`. + +## macOS + +### Nokogiri + +http://www.nokogiri.org/tutorials/installing_nokogiri.html tells us that `xz` can cause troubles, here is what to do + +``` +brew unlink xz +gem install nokogiri # or bundle install +brew link xz +``` diff --git a/app/assets/javascripts/es6_browserified/helpers/clone.js b/app/assets/javascripts/es6_browserified/helpers/clone.js new file mode 100644 index 000000000..c3b627858 --- /dev/null +++ b/app/assets/javascripts/es6_browserified/helpers/clone.js @@ -0,0 +1,14 @@ +const _ = require("lodash") + +/* This function helps having a bit more security when we pass data from the backend to the React parts + It clones the obj (window variable) and then conditionnaly delete the window variable +*/ + +const clone = (window, key, deletable = false) => { + let obj = _.cloneDeep(window[key]) + + if (deletable) delete window[key] + return obj +} + +module.exports = clone
\ No newline at end of file diff --git a/app/assets/javascripts/es6_browserified/itineraries/components/App.js b/app/assets/javascripts/es6_browserified/itineraries/components/App.js index e662d140c..f4cc5e4a6 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/components/App.js +++ b/app/assets/javascripts/es6_browserified/itineraries/components/App.js @@ -1,12 +1,28 @@ var React = require('react') +var { Component, PropTypes } = require('react') var AddStopPoint = require('../containers/AddStopPoint') var VisibleStopPoints = require('../containers/VisibleStopPoints') +const clone = require('../../helpers/clone') +const I18n = clone(window , "I18n", true) -const App = () => ( - <div> - <VisibleStopPoints /> - <AddStopPoint /> - </div> -) +class App extends Component { + + getChildContext() { + return { I18n } + } + + render() { + return ( + <div> + <VisibleStopPoints /> + <AddStopPoint /> + </div> + ) + } +} + +App.childContextTypes = { + I18n: PropTypes.object +} module.exports = App diff --git a/app/assets/javascripts/es6_browserified/itineraries/components/BSelect2.js b/app/assets/javascripts/es6_browserified/itineraries/components/BSelect2.js index 64c6d3ac7..9a82b7925 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/components/BSelect2.js +++ b/app/assets/javascripts/es6_browserified/itineraries/components/BSelect2.js @@ -1,6 +1,6 @@ var _ = require('lodash') var React = require('react') -var PropTypes = require('react').PropTypes +var { Component, PropTypes } = require('react') var Select2 = require('react-select2') @@ -9,9 +9,9 @@ var origin = window.location.origin var path = window.location.pathname.split('/', 3).join('/') -class BSelect3 extends React.Component{ - constructor(props) { - super(props) +class BSelect3 extends Component{ + constructor(props, context) { + super(props, context) } onChange(e) { this.props.onChange(this.props.index, { @@ -73,7 +73,7 @@ class BSelect3 extends React.Component{ } } -class BSelect2 extends React.Component{ +class BSelect2 extends Component{ componentDidMount() { this.refs.newSelect.el.select2('open') } @@ -85,7 +85,7 @@ class BSelect2 extends React.Component{ onSelect={ this.props.onSelect } ref='newSelect' options={{ - placeholder: 'Sélectionnez un arrêt existant...', + placeholder: this.context.I18n.routes.edit.select2.placeholder, allowClear: true, language: 'fr', /* Doesn't seem to work... :( */ theme: 'bootstrap', @@ -121,4 +121,8 @@ class BSelect2 extends React.Component{ } } +BSelect2.contextTypes = { + I18n: PropTypes.object +} + module.exports = BSelect3 diff --git a/app/assets/javascripts/es6_browserified/itineraries/components/OlMap.js b/app/assets/javascripts/es6_browserified/itineraries/components/OlMap.js index b9e106c1a..937871346 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/components/OlMap.js +++ b/app/assets/javascripts/es6_browserified/itineraries/components/OlMap.js @@ -1,11 +1,10 @@ var _ = require('lodash') var React = require('react') -var Component = require('react').Component -var PropTypes = require('react').PropTypes +var { Component, PropTypes } = require('react') class OlMap extends Component{ - constructor(props){ - super(props) + constructor(props, context){ + super(props, context) } fetchApiURL(id){ @@ -116,40 +115,40 @@ class OlMap extends Component{ <strong>{this.props.value.olMap.json.name}</strong> </p> <p> - <strong>Type d'arrêt : </strong> + <strong>{this.context.I18n.routes.edit.stop_point_type} : </strong> {this.props.value.olMap.json.area_type} </p> <p> - <strong>Nom court : </strong> + <strong>{this.context.I18n.routes.edit.short_name} : </strong> {this.props.value.olMap.json.short_name} </p> <p> - <strong>ID Reflex : </strong> + <strong>{this.context.I18n.id_reflex} : </strong> {this.props.value.olMap.json.user_objectid} </p> - <p><strong>Coordonnées : </strong></p> + <p><strong>{this.context.I18n.routes.edit.map.coordinates} : </strong></p> <p style={{paddingLeft: 10, marginTop: 0}}> - <em>Proj.: </em>WSG84<br/> - <em>Lat.: </em>{this.props.value.olMap.json.latitude} <br/> - <em>Lon.: </em>{this.props.value.olMap.json.longitude} + <em>{this.context.I18n.routes.edit.map.proj}.: </em>WSG84<br/> + <em>{this.context.I18n.routes.edit.map.lat}.: </em>{this.props.value.olMap.json.latitude} <br/> + <em>{this.context.I18n.routes.edit.map.lon}.: </em>{this.props.value.olMap.json.longitude} </p> <p> - <strong>Code Postal : </strong> + <strong>{this.context.I18n.routes.edit.map.postal_code} : </strong> {this.props.value.olMap.json.zip_code} </p> <p> - <strong>Commune : </strong> + <strong>{this.context.I18n.routes.edit.map.city} : </strong> {this.props.value.olMap.json.city_name} </p> <p> - <strong>Commentaire : </strong> + <strong>{this.context.I18n.routes.edit.map.comment} : </strong> {this.props.value.olMap.json.comment} </p> {(this.props.value.stoparea_id != this.props.value.olMap.json.stoparea_id) &&( <div className='btn btn-outline-primary btn-sm' onClick= {() => {this.props.onUpdateViaOlMap(this.props.index, this.props.value.olMap.json)}} - >Sélectionner</div> + >{this.context.I18n.actions.select}</div> )} </div> <div className='map_content'> @@ -163,7 +162,11 @@ class OlMap extends Component{ } } -OlMap.propTypes = { +OlMap.PropTypes = { +} + +OlMap.contextTypes = { + I18n: PropTypes.object } module.exports = OlMap diff --git a/app/assets/javascripts/es6_browserified/itineraries/components/StopPoint.js b/app/assets/javascripts/es6_browserified/itineraries/components/StopPoint.js index 48f77b8e9..c3996f5b3 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/components/StopPoint.js +++ b/app/assets/javascripts/es6_browserified/itineraries/components/StopPoint.js @@ -3,7 +3,7 @@ var PropTypes = require('react').PropTypes var BSelect2 = require('./BSelect2') var OlMap = require('./OlMap') -const StopPoint = (props) => { +const StopPoint = (props, {I18n}) => { return ( <div className='nested-fields'> <div className='wrapper'> @@ -17,15 +17,15 @@ const StopPoint = (props) => { <div> <select className='form-control' value={props.value.for_boarding} id="for_boarding" onChange={props.onSelectChange}> - <option value="normal">Montée autorisée</option> - <option value="forbidden">Montée interdite</option> + <option value="normal">{I18n.routes.edit.stop_point.boarding.normal}</option> + <option value="forbidden">{I18n.routes.edit.stop_point.boarding.forbidden}</option> </select> </div> <div> <select className='form-control' value={props.value.for_alighting} id="for_alighting" onChange={props.onSelectChange}> - <option value="normal">Descente autorisée</option> - <option value="forbidden">Descente interdite</option> + <option value="normal">{I18n.routes.edit.stop_point.alighting.normal}</option> + <option value="forbidden">{I18n.routes.edit.stop_point.alighting.forbidden}</option> </select> </div> @@ -90,4 +90,8 @@ StopPoint.propTypes = { value: PropTypes.object } +StopPoint.contextTypes = { + I18n: PropTypes.object +} + module.exports = StopPoint diff --git a/app/assets/javascripts/es6_browserified/itineraries/components/StopPointList.js b/app/assets/javascripts/es6_browserified/itineraries/components/StopPointList.js index 77077dbd8..37a480fca 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/components/StopPointList.js +++ b/app/assets/javascripts/es6_browserified/itineraries/components/StopPointList.js @@ -2,29 +2,29 @@ var React = require('react') var PropTypes = require('react').PropTypes var StopPoint = require('./StopPoint') -const StopPointList = ({ stopPoints, onDeleteClick, onMoveUpClick, onMoveDownClick, onChange, onSelectChange, onToggleMap, onToggleEdit, onSelectMarker, onUnselectMarker, onUpdateViaOlMap }) => { +const StopPointList = ({ stopPoints, onDeleteClick, onMoveUpClick, onMoveDownClick, onChange, onSelectChange, onToggleMap, onToggleEdit, onSelectMarker, onUnselectMarker, onUpdateViaOlMap }, {I18n}) => { return ( <div className='subform'> <div className='nested-head'> <div className="wrapper"> <div style={{width: 100}}> <div className="form-group"> - <label className="control-label">ID Reflex</label> + <label className="control-label">{I18n.reflex_id}</label> </div> </div> <div> <div className="form-group"> - <label className="control-label">Arrêt</label> + <label className="control-label">{I18n.simple_form.labels.stop_point.name}</label> </div> </div> <div> <div className="form-group"> - <label className="control-label">Montée</label> + <label className="control-label">{I18n.simple_form.labels.stop_point.for_boarding}</label> </div> </div> <div> <div className="form-group"> - <label className="control-label">Descente</label> + <label className="control-label">{I18n.simple_form.labels.stop_point.for_alighting}</label> </div> </div> <div className='actions-5'></div> @@ -65,4 +65,8 @@ StopPointList.propTypes = { onUnselectMarker : PropTypes.func.isRequired } +StopPointList.contextTypes = { + I18n: PropTypes.object +} + module.exports = StopPointList diff --git a/app/assets/javascripts/es6_browserified/itineraries/form_helper.js b/app/assets/javascripts/es6_browserified/itineraries/form_helper.js index 0baba27ef..f682e39c0 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/form_helper.js +++ b/app/assets/javascripts/es6_browserified/itineraries/form_helper.js @@ -1,11 +1,55 @@ -const addInput = (name, value, index) => { - let form = document.querySelector('form') - let input = document.createElement('input') - let formatedName = 'route[stop_points_attributes]['+ index.toString()+']['+name+']' - input.setAttribute('type', 'hidden') - input.setAttribute('name', formatedName) - input.setAttribute('value', value) - form.appendChild(input) +const formHelper = { + addInput: (name, value, index) => { + let form = document.querySelector('form') + let input = document.createElement('input') + let formatedName = `route[stop_points_attributes][${index.toString()}][${name}]` + input.setAttribute('type', 'hidden') + input.setAttribute('name', formatedName) + input.setAttribute('value', value) + form.appendChild(input) + }, + addError: (ids) => { + ids.forEach((id) => { + if (!$(id).parents('.form-group').hasClass('has-error')) { + $(id).parents('.form-group').addClass('has-error') + $(id).parent().append(`<span class='help-block small'>${'doit être rempli(e)'}</span>`) + } + }) + }, + cleanInputs: (ids) => { + ids.forEach((id) =>{ + $(id).parents('.form-group').removeClass('has-error') + $(id).siblings('span').remove() + }) + }, + handleForm: (...ids) => { + let filledInputs = [] + let blankInputs = [] + ids.forEach(id => { + $(id).val() == "" ? blankInputs.push(id) : filledInputs.push(id) + }) + + if (filledInputs.length > 0) formHelper.cleanInputs(filledInputs) + if (blankInputs.length > 0) formHelper.addError(blankInputs) + }, + handleStopPoints: (event, state) => { + if (state.stopPoints.length >= 2) { + state.stopPoints.map((stopPoint, i) => { + formHelper.addInput('id', stopPoint.stoppoint_id ? stopPoint.stoppoint_id : '', i) + formHelper.addInput('stop_area_id', stopPoint.stoparea_id, i) + formHelper.addInput('position', i, i) + formHelper.addInput('for_boarding', stopPoint.for_boarding, i) + formHelper.addInput('for_alighting', stopPoint.for_alighting, i) + }) + if ($('.alert.alert-danger').length > 0) $('.alert.alert-danger').remove() + } else { + event.preventDefault() + let msg = "L'itinéraire doit comporter au moins deux arrêts" + if ($('.alert.alert-danger').length == 0) { + $('#stop_points').find('.subform').after(`<div class='alert alert-danger'><span class='fa fa-lg fa-exclamation-circle'></span><span>" ${msg} "</span></div>`) + } + } + } } -module.exports = addInput +module.exports = formHelper
\ No newline at end of file diff --git a/app/assets/javascripts/es6_browserified/itineraries/index.js b/app/assets/javascripts/es6_browserified/itineraries/index.js index ad32b9519..13d89bec5 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/index.js +++ b/app/assets/javascripts/es6_browserified/itineraries/index.js @@ -4,8 +4,10 @@ var Provider = require('react-redux').Provider var createStore = require('redux').createStore var reducers = require('./reducers') var App = require('./components/App') -var addInput = require('./form_helper') -let datas = JSON.parse(decodeURIComponent(window.itinerary_stop)) +var { handleForm, handleStopPoints } = require('./form_helper') +let clone = require('../helpers/clone') +let datas = clone(window, "itinerary_stop", true) +datas = JSON.parse(decodeURIComponent(datas)) // logger, DO NOT REMOVE // var applyMiddleware = require('redux').applyMiddleware @@ -67,17 +69,12 @@ render( document.querySelector('input[name=commit]').addEventListener('click', (event)=>{ let state = store.getState() - if(state.stopPoints.length >= 2) { - state.stopPoints.map((stopPoint, i) => { - addInput('id', stopPoint.stoppoint_id ? stopPoint.stoppoint_id : '', i) - addInput('stop_area_id',stopPoint.stoparea_id, i) - addInput('position',i, i) - addInput('for_boarding',stopPoint.for_boarding, i) - addInput('for_alighting',stopPoint.for_alighting, i) - }) - } else { + let name = $("#route_name").val() + let publicName = $("#route_published_name").val() + if (name == "" || publicName == "") { event.preventDefault() - let msg = "L'itinéraire doit comporter au moins deux arrêts" - $('#stop_points').find('.subform').after("<div class='alert alert-danger'><span class='fa fa-lg fa-exclamation-circle'></span><span>" + msg + "</span></div>") + handleForm("#route_name", "#route_published_name") } + + handleStopPoints(event, state) }) diff --git a/app/assets/javascripts/es6_browserified/itineraries/reducers/stopPoints.js b/app/assets/javascripts/es6_browserified/itineraries/reducers/stopPoints.js index a3b8accb3..f3a26b8d7 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/reducers/stopPoints.js +++ b/app/assets/javascripts/es6_browserified/itineraries/reducers/stopPoints.js @@ -1,5 +1,5 @@ var _ = require('lodash') -var addInput = require('../form_helper') +var { addInput } = require('../form_helper') const stopPoint = (state = {}, action, length) => { switch (action.type) { diff --git a/app/assets/javascripts/es6_browserified/itineraries/show.js b/app/assets/javascripts/es6_browserified/itineraries/show.js index 79a11701f..e88469900 100644 --- a/app/assets/javascripts/es6_browserified/itineraries/show.js +++ b/app/assets/javascripts/es6_browserified/itineraries/show.js @@ -1,4 +1,7 @@ +const clone = require('../helpers/clone') +let route = clone(window, "route", true) route = JSON.parse(decodeURIComponent(route)) + const geoColPts = [] const geoColLns= [] const geoColEdges = [ diff --git a/app/assets/javascripts/es6_browserified/time_tables/actions/index.js b/app/assets/javascripts/es6_browserified/time_tables/actions/index.js index 02ece1654..a421a8ed6 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/actions/index.js +++ b/app/assets/javascripts/es6_browserified/time_tables/actions/index.js @@ -1,19 +1,21 @@ const _ = require('lodash') +const clone = require('../../helpers/clone') +const I18n = clone(window, "I18n") const actions = { + weekDays: (index) => { + return _.range(1, 8).map(n => I18n.time_tables.edit.metas.days[n]) + }, strToArrayDayTypes: (str) =>{ - let weekDays = ['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa'] - return weekDays.map((day, i) => str.indexOf(day) !== -1) + return actions.weekDays().map(day => str.indexOf(day) !== -1) }, - arrayToStrDayTypes: (arr) => { - let weekDays = ['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa'] - let str = [] - arr.map((dayActive, i) => { - if(dayActive){ - str.push(weekDays[i]) - } - }) - return str.join(',') + arrayToStrDayTypes: (dayTypes) => { + let newDayTypes = dayTypes.reduce((arr, dayActive, i) => { + if (dayActive) arr.push(actions.weekDays()[i]) + return arr + }, []) + + return newDayTypes.join(',') }, fetchingApi: () =>({ type: 'FETCH_API' @@ -113,14 +115,26 @@ const actions = { timetableInDates, error }), - includeDateInPeriod: (index, dayTypes, date) => ({ - type: 'INCLUDE_DATE_IN_PERIOD', + addIncludedDate: (index, dayTypes, date) => ({ + type: 'ADD_INCLUDED_DATE', index, dayTypes, date }), - excludeDateFromPeriod: (index, dayTypes, date) => ({ - type: 'EXCLUDE_DATE_FROM_PERIOD', + removeIncludedDate: (index, dayTypes, date) => ({ + type: 'REMOVE_INCLUDED_DATE', + index, + dayTypes, + date + }), + addExcludedDate: (index, dayTypes, date) => ({ + type: 'ADD_EXCLUDED_DATE', + index, + dayTypes, + date + }), + removeExcludedDate: (index, dayTypes, date) => ({ + type: 'REMOVE_EXCLUDED_DATE', index, dayTypes, date @@ -137,8 +151,8 @@ const actions = { type : 'CLOSE_MODAL' }), monthName(strDate) { - let monthList = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"] - var date = new Date(strDate) + let monthList = _.range(1,13).map(n => I18n.calendars.months[n]) + let date = new Date(strDate) return monthList[date.getMonth()] }, getHumanDate(strDate, mLimit) { @@ -157,43 +171,31 @@ const actions = { let date = new Date(strDate) return date.toLocaleDateString() }, + updateSynthesis: ({current_month, time_table_dates: dates, time_table_periods: periods}) => { + let newPeriods = _.reject(periods, 'deleted') + let improvedCM = current_month.map((d, i) => { + let isInPeriod = actions.isInPeriod(newPeriods, d.date) + let isIncluded = _.some(dates, {'date': d.date, 'in_out': true}) - updateSynthesis: (state, daytypes) => { - let periods = state.time_table_periods - - let isInPeriod = function(d){ - let currentMonth = state.current_periode_range.split('-') - let twodigitsDay = d.mday < 10 ? ('0' + d.mday) : d.mday - let currentDate = new Date(currentMonth[0] + '-' + currentMonth[1] + '-' + twodigitsDay) - - // We compare periods & currentDate, to determine if it is included or not - let testDate = false - periods.map((p, i) => { - if (p.deleted) return false - - let begin = new Date(p.period_start) - let end = new Date(p.period_end) - - if(testDate === false){ - if(currentDate >= begin && currentDate <= end) { - testDate = true - // p.include_date = false - } - } - }) - return testDate - } - - let improvedCM = state.current_month.map((d, i) => { - let bool = isInPeriod(state.current_month[i]) - return _.assign({}, state.current_month[i], { - in_periods: bool, - include_date: bool ? false : state.current_month[i].include_date, - excluded_date: !bool ? false : state.current_month[i].excluded_date + return _.assign({}, current_month[i], { + in_periods: isInPeriod, + include_date: isIncluded, + excluded_date: !isInPeriod ? false : current_month[i].excluded_date }) }) return improvedCM }, + isInPeriod: (periods, date) => { + date = new Date(date) + + for (let period of periods) { + let begin = new Date(period.period_start) + let end = new Date(period.period_end) + if (date >= begin && date <= end) return true + } + + return false + }, checkConfirmModal: (event, callback, stateChanged, dispatch, metas, timetable) => { if(stateChanged){ const error = actions.errorModalKey(timetable.time_table_periods, metas.day_types) @@ -219,7 +221,7 @@ const actions = { let period = periods[i] if (index !== i && !period.deleted) { if (new Date(period.period_start) <= end && new Date(period.period_end) >= start) { - error = 'Les périodes ne peuvent pas se chevaucher' + error = I18n.time_tables.edit.error_submit.periods_overlaps break } } @@ -233,7 +235,7 @@ const actions = { for (let day of in_days) { if (start <= new Date(day.date) && end >= new Date(day.date)) { - error = 'Une période ne peut chevaucher une date dans un calendrier' + error = I18n.time_tables.edit.error_submit.dates_overlaps break } } @@ -241,7 +243,6 @@ const actions = { }, fetchTimeTables: (dispatch, nextPage) => { let urlJSON = window.location.pathname.split('/', 5).join('/') - // console.log(nextPage) if(nextPage) { urlJSON += "/month.json?date=" + nextPage }else{ @@ -310,21 +311,14 @@ const actions = { errorModalMessage: (errorKey) => { switch (errorKey) { case "withoutPeriodsWithDaysTypes": - return window.I18n.fr.time_tables.edit.error_modal.withoutPeriodsWithDaysTypes + return I18n.time_tables.edit.error_modal.withoutPeriodsWithDaysTypes case "withPeriodsWithoutDayTypes": - return window.I18n.fr.time_tables.edit.error_modal.withPeriodsWithoutDayTypes + return I18n.time_tables.edit.error_modal.withPeriodsWithoutDayTypes default: return errorKey } }, - checkIfTTHasDate: (dates, date) => { - if (_.some(dates, date)) { - return _.reject(dates, ['date', date.date]) - } else { - return dates.concat(date) - } - } } module.exports = actions diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/ConfirmModal.js b/app/assets/javascripts/es6_browserified/time_tables/components/ConfirmModal.js index 40ae0eccf..674a03296 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/ConfirmModal.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/ConfirmModal.js @@ -1,18 +1,17 @@ var React = require('react') -var Component = require('react').Component -var PropTypes = require('react').PropTypes +var { PropTypes } = require('react') -const ConfirmModal = ({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}) => ( +const ConfirmModal = ({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}, {I18n}) => ( <div className={ 'modal fade ' + ((modal.type == 'confirm') ? 'in' : '') } id='ConfirmModal'> <div className='modal-container'> <div className='modal-dialog'> <div className='modal-content'> <div className='modal-header'> - <h4 className='modal-title'>Confirmation</h4> + <h4 className='modal-title'>{I18n.time_tables.edit.confirm_modal.title}</h4> </div> <div className='modal-body'> <div className='mt-md mb-md'> - <p>Vous vous apprêtez à changer de page. Voulez-vous valider vos modifications avant cela ?</p> + <p>{I18n.time_tables.edit.confirm_modal.message}</p> </div> </div> <div className='modal-footer'> @@ -22,7 +21,7 @@ const ConfirmModal = ({dispatch, modal, onModalAccept, onModalCancel, timetable, type='button' onClick= {() => {onModalCancel(modal.confirmModal.callback)}} > - Ne pas valider + {I18n.cancel} </button> <button className='btn btn-primary' @@ -30,7 +29,7 @@ const ConfirmModal = ({dispatch, modal, onModalAccept, onModalCancel, timetable, type='button' onClick = {() => {onModalAccept(modal.confirmModal.callback, timetable, metas)}} > - Valider + {I18n.actions.submit} </button> </div> </div> @@ -45,4 +44,8 @@ ConfirmModal.propTypes = { onModalCancel: PropTypes.func.isRequired } +ConfirmModal.contextTypes = { + I18n: PropTypes.object +} + module.exports = ConfirmModal diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js b/app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js index 4e8f7e363..2597a4870 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js @@ -1,15 +1,14 @@ var React = require('react') -var Component = require('react').Component -var PropTypes = require('react').PropTypes -var errorModalMessage = require('../actions').errorModalMessage +var { PropTypes } = require('react') +var { errorModalMessage } = require('../actions') -const ErrorModal = ({dispatch, modal, I18n, onModalClose}) => ( +const ErrorModal = ({dispatch, modal, onModalClose}, {I18n}) => ( <div className={ 'modal fade ' + ((modal.type == 'error') ? 'in' : '') } id='ErrorModal'> <div className='modal-container'> <div className='modal-dialog'> <div className='modal-content'> <div className='modal-header'> - <h4 className='modal-title'>{window.I18n.fr.time_tables.edit.error_modal.title}</h4> + <h4 className='modal-title'>{I18n.time_tables.edit.error_modal.title}</h4> </div> <div className='modal-body'> <div className='mt-md mb-md'> @@ -23,7 +22,7 @@ const ErrorModal = ({dispatch, modal, I18n, onModalClose}) => ( type='button' onClick= {() => {onModalClose()}} > - Retour + {I18n.back} </button> </div> </div> @@ -37,4 +36,8 @@ ErrorModal.propTypes = { onModalClose: PropTypes.func.isRequired } +ErrorModal.contextTypes = { + I18n: PropTypes.object +} + module.exports = ErrorModal diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js b/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js index 4879e537f..80c2e4b7a 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js @@ -8,6 +8,15 @@ class ExceptionsInDay extends Component { super(props) } + handleClick() { + const {index, day, metas: {day_types} } = this.props + if (day.in_periods && day_types[day.wday]) { + day.excluded_date ? this.props.onRemoveExcludedDate(index, day_types, day.date) : this.props.onAddExcludedDate(index, day_types, day.date) + } else { + day.include_date ? this.props.onRemoveIncludedDate(index, day_types, day.date) : this.props.onAddIncludedDate(index, day_types, day.date) + } + } + render() { {/* display add or remove link, only if true in daytypes */} {/* display add or remove link, according to context (presence in period, or not) */} @@ -20,14 +29,14 @@ class ExceptionsInDay extends Component { data-actiontype='remove' onClick={(e) => { $(e.currentTarget).toggleClass('active') - this.props.onExcludeDateFromPeriod(this.props.index, this.props.metas.day_types, this.props.currentDate) + this.handleClick() }} > <span className='fa fa-times'></span> </button> </div> ) - } else if(this.props.value.current_month[this.props.index].in_periods == false) { + } else { return ( <div className='td'> <button @@ -36,20 +45,21 @@ class ExceptionsInDay extends Component { data-actiontype='add' onClick={(e) => { $(e.currentTarget).toggleClass('active') - this.props.onIncludeDateInPeriod(this.props.index, this.props.metas.day_types, this.props.currentDate) + this.handleClick() }} > <span className='fa fa-plus'></span> </button> </div> ) - } else if(this.props.value.current_month[this.props.index].in_periods == true && this.props.blueDaytype == false){ - return ( - <div className='td'></div> - ) - } else{ - return false - } + // } else if(this.props.value.current_month[this.props.index].in_periods == true && this.props.blueDaytype == false){ + // return ( + // <div className='td'></div> + // ) + // } else{ + // return false + // } + } } } diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/Metas.js b/app/assets/javascripts/es6_browserified/time_tables/components/Metas.js index a0fac84f3..26a96e4a6 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/Metas.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/Metas.js @@ -1,9 +1,9 @@ var React = require('react') -var PropTypes = require('react').PropTypes -let weekDays = ['D', 'L', 'Ma', 'Me', 'J', 'V', 'S'] +var { PropTypes } = require('react') +const { weekDays } = require('../actions') var TagsSelect2 = require('./TagsSelect2') -const Metas = ({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelect2Tags, onUnselect2Tags}) => { +const Metas = ({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelect2Tags, onUnselect2Tags}, {I18n}) => { let colorList = ["", "#9B9B9B", "#FFA070", "#C67300", "#7F551B", "#41CCE3", "#09B09C", "#3655D7", "#6321A0", "#E796C6", "#DD2DAA"] return ( <div className='form-horizontal'> @@ -12,7 +12,7 @@ const Metas = ({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelec {/* comment (name) */} <div className="form-group"> <label htmlFor="" className="control-label col-sm-4 required"> - Nom <abbr title="Champ requis">*</abbr> + {I18n.time_tables.edit.metas.name} <abbr title="">*</abbr> </label> <div className="col-sm-8"> <input @@ -27,7 +27,7 @@ const Metas = ({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelec {/* color */} <div className="form-group"> - <label htmlFor="" className="control-label col-sm-4">Couleur associée</label> + <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.color}</label> <div className="col-sm-8"> <div className="dropdown color_selector"> <button @@ -72,7 +72,7 @@ const Metas = ({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelec {/* tags */} <div className="form-group"> - <label htmlFor="" className="control-label col-sm-4">Etiquettes</label> + <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.tag_list}</label> <div className="col-sm-8"> <TagsSelect2 initialTags={metas.initial_tags} @@ -85,16 +85,16 @@ const Metas = ({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelec {/* calendar */} <div className="form-group"> - <label htmlFor="" className="control-label col-sm-4">Modèle de calendrier associé</label> + <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.calendar}</label> <div className="col-sm-8"> - <span>{metas.calendar ? metas.calendar.name : 'Aucun'}</span> + <span>{metas.calendar ? metas.calendar.name : I18n.time_tables.edit.metas.no_calendar}</span> </div> </div> {/* day_types */} <div className="form-group"> <label htmlFor="" className="control-label col-sm-4"> - Journées d'applications pour les périodes ci-dessous + {I18n.time_tables.edit.metas.day_types} </label> <div className="col-sm-8"> <div className="form-group labelled-checkbox-group"> @@ -112,7 +112,7 @@ const Metas = ({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelec type="checkbox" checked={day ? 'checked' : ''} /> - <span className='lcbx-group-item-label'>{weekDays[i]}</span> + <span className='lcbx-group-item-label'>{weekDays()[i]}</span> </label> </div> </div> @@ -135,4 +135,8 @@ Metas.propTypes = { onUnselect2Tags: PropTypes.func.isRequired } +Metas.contextTypes = { + I18n: PropTypes.object +} + module.exports = Metas diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js index 3234a3fd7..d494109cc 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js @@ -1,5 +1,5 @@ var React = require('react') -var PropTypes = require('react').PropTypes +var { PropTypes } = require('react') var _ = require('lodash') let monthsArray = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'] @@ -32,7 +32,7 @@ const makeYearsOptions = (yearSelected) => { return arr } -const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriodForm, onUpdatePeriodForm, onValidatePeriodForm}) => ( +const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriodForm, onUpdatePeriodForm, onValidatePeriodForm}, {I18n}) => ( <div className="container-fluid"> <div className="row"> <div className="col lg-6 col-lg-offset-3"> @@ -44,7 +44,7 @@ const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriod <div> <div className="form-group"> <label htmlFor="" className="control-label required"> - Début de période + {I18n.time_tables.edit.period_form.begin} <abbr title="requis">*</abbr> </label> </div> @@ -52,7 +52,7 @@ const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriod <div> <div className="form-group"> <label htmlFor="" className="control-label required"> - Fin de période + {I18n.time_tables.edit.period_form.end} <abbr title="requis">*</abbr> </label> </div> @@ -103,14 +103,14 @@ const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriod className='btn btn-link' onClick={onClosePeriodForm} > - Annuler + {I18n.cancel} </button> <button type='button' className='btn btn-outline-primary mr-sm' onClick={() => onValidatePeriodForm(modal.modalProps, timetable.time_table_periods, metas, _.filter(timetable.time_table_dates, ['in_out', true]))} > - Valider + {I18n.actions.submit} </button> </div> </div> @@ -122,7 +122,7 @@ const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriod className='btn btn-outline-primary' onClick={onOpenAddPeriodForm} > - Ajouter une période + {I18n.time_tables.actions.add_period} </button> </div> } @@ -142,4 +142,8 @@ PeriodForm.propTypes = { timetable: PropTypes.object.isRequired } +PeriodForm.contextTypes = { + I18n: PropTypes.object +} + module.exports = PeriodForm diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodManager.js b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodManager.js index cf4cbfb32..704e21331 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodManager.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodManager.js @@ -1,11 +1,10 @@ var React = require('react') -var Component = require('react').Component -var PropTypes = require('react').PropTypes +var { Component, PropTypes } = require('react') var actions = require('../actions') class PeriodManager extends Component { - constructor(props) { - super(props) + constructor(props, context) { + super(props, context) } toEndPeriod(curr, end) { @@ -82,4 +81,8 @@ PeriodManager.propTypes = { onOpenEditPeriodForm: PropTypes.func.isRequired } +PeriodManager.contextTypes = { + I18n: PropTypes.object +} + module.exports = PeriodManager diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodsInDay.js b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodsInDay.js index ca44d3a07..f56509b99 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodsInDay.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodsInDay.js @@ -35,7 +35,7 @@ class PeriodsInDay extends Component { render() { return ( <div - className={this.isIn(this.props.currentDate)} + className={this.isIn(this.props.currentDate) + (this.props.metas.day_types[this.props.day.wday] || !this.props.day.in_periods ? '' : ' out_from_daytypes')} > {this.props.value.map((p, i) => { if(!p.deleted){ diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/TagsSelect2.js b/app/assets/javascripts/es6_browserified/time_tables/components/TagsSelect2.js index a1f41a693..46188cdd1 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/TagsSelect2.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/TagsSelect2.js @@ -9,8 +9,8 @@ var path = window.location.pathname.split('/', 4).join('/') var _ = require('lodash') class TagsSelect2 extends React.Component{ - constructor(props) { - super(props) + constructor(props, context) { + super(props, context) } mapKeys(array){ @@ -38,7 +38,7 @@ class TagsSelect2 extends React.Component{ allowClear: true, theme: 'bootstrap', width: '100%', - placeholder: 'Ajoutez ou cherchez une étiquette...', + placeholder: this.context.I18n.time_tables.edit.select2.tag.placeholder, ajax: { url: origin + path + '/tags.json', dataType: 'json', @@ -74,4 +74,8 @@ const formatRepo = (props) => { if(props.name) return props.name } +TagsSelect2.contextTypes = { + I18n: PropTypes.object +} + module.exports = TagsSelect2 diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/TimeTableDay.js b/app/assets/javascripts/es6_browserified/time_tables/components/TimeTableDay.js index 71621c874..93a0a90fe 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/TimeTableDay.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/TimeTableDay.js @@ -1,6 +1,5 @@ var React = require('react') -var Component = require('react').Component -var PropTypes = require('react').PropTypes +var { Component, PropTypes } = require('react') class TimeTableDay extends Component { constructor(props) { diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js b/app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js index 3af1a11a4..22e971c6b 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js @@ -1,14 +1,13 @@ var React = require('react') -var Component = require('react').Component -var PropTypes = require('react').PropTypes +var { Component, PropTypes} = require('react') var TimeTableDay = require('./TimeTableDay') var PeriodsInDay = require('./PeriodsInDay') var ExceptionsInDay = require('./ExceptionsInDay') var actions = require('../actions') class Timetable extends Component{ - constructor(props){ - super(props) + constructor(props, context){ + super(props, context) } currentDate(mFirstday, day) { @@ -31,11 +30,11 @@ class Timetable extends Component{ <div className="table table-2entries mb-sm"> <div className="t2e-head w20"> <div className="th"> - <div className="strong">Synthèse</div> + <div className="strong">{this.context.I18n.time_tables.synthesis}</div> </div> - <div className="td"><span>Journées d'application</span></div> - <div className="td"><span>Périodes</span></div> - <div className="td"><span>Exceptions</span></div> + <div className="td"><span>{this.context.I18n.time_tables.edit.day_types}</span></div> + <div className="td"><span>{this.context.I18n.time_tables.edit.periods}</span></div> + <div className="td"><span>{this.context.I18n.time_tables.edit.exceptions}</span></div> </div> <div className="t2e-item-list w80"> <div> @@ -60,13 +59,14 @@ class Timetable extends Component{ {this.props.timetable.current_month.map((d, i) => <div key={i} - className={'td-group' + (this.props.metas.day_types[d.wday] || !d.in_periods ? '' : ' out_from_daytypes') + (d.wday == 0 ? ' last_wday' : '')} + className={'td-group'+ (d.wday == 0 ? ' last_wday' : '')} > {/* day_types */} - <div className="td"></div> + <div className={"td" + (this.props.metas.day_types[d.wday] || !d.in_periods ? '' : ' out_from_daytypes') }></div> {/* periods */} <PeriodsInDay + day={d} index={i} value={this.props.timetable.time_table_periods} currentDate={this.currentDate(this.props.timetable.current_periode_range, d.mday)} @@ -77,11 +77,16 @@ class Timetable extends Component{ {/* exceptions */} <ExceptionsInDay + day={d} index={i} value={this.props.timetable} currentDate={d.date} metas={this.props.metas} blueDaytype={this.props.metas.day_types[d.wday]} + onAddIncludedDate={this.props.onAddIncludedDate} + onRemoveIncludedDate={this.props.onRemoveIncludedDate} + onAddExcludedDate={this.props.onAddExcludedDate} + onRemoveExcludedDate={this.props.onRemoveExcludedDate} onExcludeDateFromPeriod={this.props.onExcludeDateFromPeriod} onIncludeDateInPeriod={this.props.onIncludeDateInPeriod} /> @@ -105,4 +110,8 @@ Timetable.propTypes = { onIncludeDateInPeriod: PropTypes.func.isRequired } +Timetable.contextTypes = { + I18n: PropTypes.object +} + module.exports = Timetable diff --git a/app/assets/javascripts/es6_browserified/time_tables/containers/App.js b/app/assets/javascripts/es6_browserified/time_tables/containers/App.js index 02f0ddbd8..f12fb8a71 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/containers/App.js +++ b/app/assets/javascripts/es6_browserified/time_tables/containers/App.js @@ -1,6 +1,6 @@ var React = require('react') var connect = require('react-redux').connect -var Component = require('react').Component +var { Component, PropTypes} = require('react') var actions = require('../actions') var Metas = require('./Metas') var Timetable = require('./Timetable') @@ -10,11 +10,18 @@ var SaveTimetable = require('./SaveTimetable') var ConfirmModal = require('./ConfirmModal') var ErrorModal = require('./ErrorModal') +const clone = require('../../helpers/clone') +const I18n = clone(window, "I18n", true) + class App extends Component { componentDidMount(){ this.props.onLoadFirstPage() } + getChildContext() { + return { I18n } + } + render(){ return( <div className='row'> @@ -41,6 +48,10 @@ const mapDispatchToProps = (dispatch) => { } } +App.childContextTypes = { + I18n: PropTypes.object +} + const timeTableApp = connect(null, mapDispatchToProps)(App) module.exports = timeTableApp diff --git a/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js b/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js index 639a1e2ab..a37e99982 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js +++ b/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js @@ -15,6 +15,18 @@ const mapDispatchToProps = (dispatch) => { onDeletePeriod: (index, dayTypes) =>{ dispatch(actions.deletePeriod(index, dayTypes)) }, + onAddIncludedDate: (index, dayTypes, date) => { + dispatch(actions.addIncludedDate(index, dayTypes, date)) + }, + onRemoveIncludedDate: (index, dayTypes, date) => { + dispatch(actions.removeIncludedDate(index, dayTypes, date)) + }, + onAddExcludedDate: (index, dayTypes, date) => { + dispatch(actions.addExcludedDate(index, dayTypes, date)) + }, + onRemoveExcludedDate: (index, dayTypes, date) => { + dispatch(actions.removeExcludedDate(index, dayTypes, date)) + }, onExcludeDateFromPeriod: (index, dayTypes, date) => { dispatch(actions.excludeDateFromPeriod(index, dayTypes, date)) }, diff --git a/app/assets/javascripts/es6_browserified/time_tables/index.js b/app/assets/javascripts/es6_browserified/time_tables/index.js index a91747991..6c352df6b 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/index.js +++ b/app/assets/javascripts/es6_browserified/time_tables/index.js @@ -5,6 +5,9 @@ var createStore = require('redux').createStore var timeTablesApp = require('./reducers') var App = require('./containers/App') +const clone = require('../helpers/clone') +const actionType = clone(window, "actionType", true) + // logger, DO NOT REMOVE // var applyMiddleware = require('redux').applyMiddleware // var createLogger = require('redux-logger') @@ -13,7 +16,7 @@ var App = require('./containers/App') var initialState = { status: { - actionType: window.actionType, + actionType: actionType, policy: window.perms, fetchSuccess: true, isFetching: false diff --git a/app/assets/javascripts/es6_browserified/time_tables/reducers/metas.js b/app/assets/javascripts/es6_browserified/time_tables/reducers/metas.js index 2ce084efd..ab5ed3d91 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/reducers/metas.js +++ b/app/assets/javascripts/es6_browserified/time_tables/reducers/metas.js @@ -15,8 +15,10 @@ const metas = (state = {}, action) => { case 'RECEIVE_MONTH': let dt = (typeof state.day_types === 'string') ? actions.strToArrayDayTypes(state.day_types) : state.day_types return _.assign({}, state, {day_types: dt}) - case 'INCLUDE_DATE_IN_PERIOD': - case 'EXCLUDE_DATE_FROM_PERIOD': + case 'ADD_INCLUDED_DATE': + case 'REMOVE_INCLUDED_DATE': + case 'ADD_EXCLUDED_DATE': + case 'REMOVE_EXCLUDED_DATE': case 'DELETE_PERIOD': case 'VALIDATE_PERIOD_FORM': return _.assign({}, state, {calendar: null}) diff --git a/app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js b/app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js index 45fec6b5f..f38b124d9 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js +++ b/app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js @@ -20,8 +20,10 @@ const pagination = (state = {}, action) => { case 'CHANGE_PAGE': toggleOnConfirmModal() return _.assign({}, state, {currentPage : action.page, stateChanged: false}) - case 'INCLUDE_DATE_IN_PERIOD': - case 'EXCLUDE_DATE_FROM_PERIOD': + case 'ADD_INCLUDED_DATE': + case 'REMOVE_INCLUDED_DATE': + case 'ADD_EXCLUDED_DATE': + case 'REMOVE_EXCLUDED_DATE': case 'DELETE_PERIOD': case 'VALIDATE_PERIOD_FORM': case 'UPDATE_COMMENT': diff --git a/app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js b/app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js index 390bdffb0..712808abd 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js +++ b/app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js @@ -1,7 +1,6 @@ const _ = require('lodash') var actions = require('../actions') -let newState = {} -let newDates = [] +let newState, newPeriods, newDates, newCM const timetable = (state = {}, action) => { switch (action.type) { @@ -11,14 +10,14 @@ const timetable = (state = {}, action) => { current_periode_range: action.json.current_periode_range, periode_range: action.json.periode_range, time_table_periods: action.json.time_table_periods, - time_table_dates: action.json.time_table_dates + time_table_dates: _.sortBy(action.json.time_table_dates, ['date']) }) - return _.assign({}, fetchedState, {current_month: actions.updateSynthesis(fetchedState, actions.strToArrayDayTypes(action.json.day_types))}) + return _.assign({}, fetchedState, {current_month: actions.updateSynthesis(fetchedState)}) case 'RECEIVE_MONTH': newState = _.assign({}, state, { current_month: action.json.days }) - return _.assign({}, newState, {current_month: actions.updateSynthesis(newState, actions.strToArrayDayTypes(action.json.day_types))}) + return _.assign({}, newState, {current_month: actions.updateSynthesis(newState)}) case 'GO_TO_PREVIOUS_PAGE': case 'GO_TO_NEXT_PAGE': let nextPage = action.nextPage ? 1 : -1 @@ -31,34 +30,44 @@ const timetable = (state = {}, action) => { actions.fetchTimeTables(action.dispatch, action.page) return _.assign({}, state, {current_periode_range: action.page}) case 'DELETE_PERIOD': - let ttperiods = state.time_table_periods.map((period, i) =>{ + newPeriods = state.time_table_periods.map((period, i) =>{ if(i == action.index){ period.deleted = true } return period }) - newState = _.assign({}, state, {time_table_periods : ttperiods}) - return _.assign({}, newState, {current_month: actions.updateSynthesis(newState, action.dayTypes)}) - case 'INCLUDE_DATE_IN_PERIOD': - newDates = actions.checkIfTTHasDate(state.time_table_dates, {date: action.date, in_out: true}) - let newCMi = state.current_month.map((d, i) => { - if(i == action.index){ - d.include_date = !d.include_date - } + let deletedPeriod = Array.of(state.time_table_periods[action.index]) + newDates = _.reject(state.time_table_dates, d => actions.isInPeriod(deletedPeriod, d.date) && !d.in_out) + newState = _.assign({}, state, {time_table_periods : newPeriods, time_table_dates: newDates}) + return _.assign({}, newState, { current_month: actions.updateSynthesis(newState)}) + case 'ADD_INCLUDED_DATE': + newDates = state.time_table_dates.concat({date: action.date, in_out: true}) + newCM = state.current_month.map((d, i) => { + if (i == action.index) d.include_date = true return d }) - newState = _.assign({}, state, {current_month: newCMi, time_table_dates: newDates}) - return _.assign({}, newState, {current_month: actions.updateSynthesis(newState, action.dayTypes)}) - case 'EXCLUDE_DATE_FROM_PERIOD': - newDates = actions.checkIfTTHasDate(state.time_table_dates, {date: action.date, in_out: false}) - let newCMe = state.current_month.map((d, i) => { - if(i == action.index){ - d.excluded_date = !d.excluded_date - } + return _.assign({}, state, {current_month: newCM, time_table_dates: newDates}) + case 'REMOVE_INCLUDED_DATE': + newDates = _.reject(state.time_table_dates, ['date', action.date]) + newCM = state.current_month.map((d, i) => { + if (i == action.index) d.include_date = false + return d + }) + return _.assign({}, state, {current_month: newCM, time_table_dates: newDates}) + case 'ADD_EXCLUDED_DATE': + newDates = state.time_table_dates.concat({date: action.date, in_out: false}) + newCM = state.current_month.map((d, i) => { + if (i == action.index) d.excluded_date = true + return d + }) + return _.assign({}, state, {current_month: newCM, time_table_dates: newDates}) + case 'REMOVE_EXCLUDED_DATE': + newDates = _.reject(state.time_table_dates, ['date', action.date]) + newCM = state.current_month.map((d, i) => { + if (i == action.index) d.excluded_date = false return d }) - newState = _.assign({}, state, {current_month: newCMe, time_table_dates: newDates}) - return _.assign({}, newState, {current_month: actions.updateSynthesis(newState, action.dayTypes)}) + return _.assign({}, state, {current_month: newCM, time_table_dates: newDates}) case 'UPDATE_DAY_TYPES': // We get the week days of the activated day types to reject the out_dates that that are out of newDayTypes let weekDays = _.reduce(action.dayTypes, (array, dt, i) => { @@ -67,11 +76,17 @@ const timetable = (state = {}, action) => { }, []) newDates = _.reject(state.time_table_dates, (d) => { - return d.in_out == false && !weekDays.includes(new Date(d.date).getDay()) + let weekDay = new Date(d.date).getDay() + + if (d.in_out) { + return actions.isInPeriod(state.time_table_periods, d.date) && weekDays.includes(weekDay) + } else { + return !weekDays.includes(weekDay) + } }) return _.assign({}, state, {time_table_dates: newDates}) case 'UPDATE_CURRENT_MONTH_FROM_DAYTYPES': - return _.assign({}, state, {current_month: actions.updateSynthesis(state, action.dayTypes)}) + return _.assign({}, state, {current_month: actions.updateSynthesis(state)}) case 'VALIDATE_PERIOD_FORM': if (action.error != '') return state @@ -81,8 +96,10 @@ const timetable = (state = {}, action) => { let newPeriods = JSON.parse(JSON.stringify(action.timeTablePeriods)) if (action.modalProps.index !== false){ - newPeriods[action.modalProps.index].period_start = period_start - newPeriods[action.modalProps.index].period_end = period_end + let updatedPeriod = newPeriods[action.modalProps.index] + updatedPeriod.period_start = period_start + updatedPeriod.period_end = period_end + newDates = _.reject(state.time_table_dates, d => actions.isInPeriod(newPeriods, d.date) && !d.in_out) }else{ let newPeriod = { period_start: period_start, @@ -90,8 +107,10 @@ const timetable = (state = {}, action) => { } newPeriods.push(newPeriod) } - newState =_.assign({}, state, {time_table_periods: newPeriods}) - return _.assign({}, newState, {current_month: actions.updateSynthesis(newState, action.metas.day_types)}) + + newDates = newDates || state.time_table_dates + newState =_.assign({}, state, {time_table_periods: newPeriods, time_table_dates: newDates}) + return _.assign({}, newState, {current_month: actions.updateSynthesis(newState)}) default: return state } diff --git a/app/assets/stylesheets/modules/_timetables.sass b/app/assets/stylesheets/modules/_timetables.sass index 84f1af043..b06972ef9 100644 --- a/app/assets/stylesheets/modules/_timetables.sass +++ b/app/assets/stylesheets/modules/_timetables.sass @@ -85,11 +85,14 @@ &:not(:last-child) > .td border-right: 2px solid $darkgrey - &.out_from_daytypes - background-image: linear-gradient(45deg, rgba($grey, 0.15) 0%, rgba($grey, 0.15) 49%, rgba($grey, 0.5) 50%, rgba($grey, 0.15) 51%, rgba($grey, 0.15) 99%, rgba($grey, 0.15) 100%) - background-size: 25px 25px + // &.out_from_daytypes + // background-image: linear-gradient(45deg, rgba($grey, 0.15) 0%, rgba($grey, 0.15) 49%, rgba($grey, 0.5) 50%, rgba($grey, 0.15) 51%, rgba($grey, 0.15) 99%, rgba($grey, 0.15) 100%) + // background-size: 25px 25px > .td + &.out_from_daytypes + background-image: linear-gradient(45deg, rgba($grey, 0.15) 0%, rgba($grey, 0.15) 49%, rgba($grey, 0.5) 50%, rgba($grey, 0.15) 51%, rgba($grey, 0.15) 99%, rgba($grey, 0.15) 100%) + background-size: 25px 25px &.in_periods background-color: rgba($gold, 0.5) border-left-color: rgba($gold, 0.5) diff --git a/app/controllers/compliance_control_sets_controller.rb b/app/controllers/compliance_control_sets_controller.rb index 6edfa3fcc..20ddcbe97 100644 --- a/app/controllers/compliance_control_sets_controller.rb +++ b/app/controllers/compliance_control_sets_controller.rb @@ -1,10 +1,12 @@ class ComplianceControlSetsController < BreadcrumbController defaults resource_class: ComplianceControlSet + before_action :ransack_updated_at_params, only: [:index] respond_to :html def index index! do |format| - @q_for_form = @compliance_control_sets.ransack(params[:q]) + scope = ransack_period @compliance_control_sets + @q_for_form = scope.ransack(params[:q]) format.html { @compliance_control_sets = decorate_compliance_control_sets(@q_for_form.result) } @@ -24,8 +26,41 @@ class ComplianceControlSetsController < BreadcrumbController ) end + protected + + # def begin_of_association_chain + # current_organisation + # end + private + def ransack_updated_at_params + start_date = [] + end_date = [] + + if params[:q] && params[:q][:updated_at] && !params[:q][:updated_at].has_value?(nil) && !params[:q][:updated_at].has_value?("") + [1, 2, 3].each do |key| + start_date << params[:q][:updated_at]["begin(#{key}i)"].to_i + end_date << params[:q][:updated_at]["end(#{key}i)"].to_i + end + params[:q].delete([:updated_at]) + @begin_range = DateTime.new(*start_date,0,0,0) rescue nil + @end_range = DateTime.new(*end_date,23,59,59) rescue nil + end + end + + # Fake ransack filter + def ransack_period scope + return scope unless !!@begin_range && !!@end_range + + if @begin_range > @end_range + flash.now[:error] = t('imports.filters.error_period_filter') + else + scope = scope.where_updated_at_between(@begin_range, @end_range) + end + scope + end + def compliance_control_set_params params.require(:compliance_control_set).permit(:name, :id) end diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb index fa8919f20..f2e65e445 100644 --- a/app/controllers/imports_controller.rb +++ b/app/controllers/imports_controller.rb @@ -1,4 +1,5 @@ class ImportsController < BreadcrumbController + include PolicyChecker skip_before_action :authenticate_user!, only: [:download] defaults resource_class: Import, collection_name: 'imports', instance_name: 'import' before_action :ransack_started_at_params, only: [:index] @@ -89,7 +90,6 @@ class ImportsController < BreadcrumbController def ransack_status_params if params[:q] - binding.pry return params[:q].delete(:status_eq_any) if params[:q][:status_eq_any].empty? || ( (Import.status.values & params[:q][:status_eq_any]).length >= 4 ) params[:q][:status_eq_any].push("new", "running") if params[:q][:status_eq_any].include?("pending") params[:q][:status_eq_any].push("aborted", "canceled") if params[:q][:status_eq_any].include?("failed") diff --git a/app/decorators/company_decorator.rb b/app/decorators/company_decorator.rb index 402bd3ab6..764cce3a0 100644 --- a/app/decorators/company_decorator.rb +++ b/app/decorators/company_decorator.rb @@ -19,8 +19,6 @@ class CompanyDecorator < Draper::Decorator links = [] if h.policy(Chouette::Company).create? - require 'pry' - binding.pry links << Link.new( content: h.t('companies.actions.new'), href: h.new_line_referential_company_path(context[:referential]) diff --git a/app/helpers/compliance_control_sets_helper.rb b/app/helpers/compliance_control_sets_helper.rb index 3e02e0ef7..6ba4bed4f 100644 --- a/app/helpers/compliance_control_sets_helper.rb +++ b/app/helpers/compliance_control_sets_helper.rb @@ -1,2 +1,7 @@ module ComplianceControlSetsHelper + + def organisations_filters_values + [current_organisation, Organisation.find_by_name("STIF")].uniq + end + end diff --git a/app/models/chouette/stif_netex_objectid.rb b/app/models/chouette/stif_netex_objectid.rb index a0a91668a..93e7a1e85 100644 --- a/app/models/chouette/stif_netex_objectid.rb +++ b/app/models/chouette/stif_netex_objectid.rb @@ -3,7 +3,7 @@ class Chouette::StifNetexObjectid < String parts.present? end - @@format = /^([A-Za-z_]+):([A-Za-z]+):([0-9A-Za-z_-]+):([A-Za-z]+)$/ + @@format = /^([A-Za-z_-]+):([A-Za-z]+):([0-9A-Za-z_-]+):([A-Za-z]+)$/ cattr_reader :format def parts diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb index 156cc761f..a4a4a02c8 100644 --- a/app/models/chouette/vehicle_journey_at_stop.rb +++ b/app/models/chouette/vehicle_journey_at_stop.rb @@ -41,7 +41,7 @@ module Chouette :arrival_day_offset, I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', - local_id: vehicle_journey.objectid.local_id, + short_id: vehicle_journey.objectid.short_id, max: DAY_OFFSET_MAX + 1 ) ) @@ -52,7 +52,7 @@ module Chouette :departure_day_offset, I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', - local_id: vehicle_journey.objectid.local_id, + short_id: vehicle_journey.objectid.short_id, max: DAY_OFFSET_MAX + 1 ) ) diff --git a/app/models/compliance_control_set.rb b/app/models/compliance_control_set.rb index cefdfbf1f..4dafd48c7 100644 --- a/app/models/compliance_control_set.rb +++ b/app/models/compliance_control_set.rb @@ -4,4 +4,8 @@ class ComplianceControlSet < ActiveRecord::Base validates :name, presence: true + scope :where_updated_at_between, ->(start_date, end_date) do + where('updated_at BETWEEN ? AND ?', start_date, end_date) + end + end diff --git a/app/models/concerns/stif_netex_attributes_support.rb b/app/models/concerns/stif_netex_attributes_support.rb index 795872755..0d569b613 100644 --- a/app/models/concerns/stif_netex_attributes_support.rb +++ b/app/models/concerns/stif_netex_attributes_support.rb @@ -49,7 +49,7 @@ module StifNetexAttributesSupport end def provider_id - self.referential.workbench.organisation.name.parameterize + self.referential.workbench.organisation.name.parameterize.underscore end def boiv_id diff --git a/app/models/referential.rb b/app/models/referential.rb index af08aa868..c7b52ddf8 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -45,6 +45,8 @@ class Referential < ActiveRecord::Base has_many :stop_areas, through: :stop_area_referential belongs_to :workbench + belongs_to :referential_suite + scope :ready, -> { where(ready: true) } scope :in_periode, ->(periode) { where(id: referential_ids_in_periode(periode)) } scope :include_metadatas_lines, ->(line_ids) { where('referential_metadata.line_ids && ARRAY[?]::bigint[]', line_ids) } diff --git a/app/models/referential_suite.rb b/app/models/referential_suite.rb new file mode 100644 index 000000000..9fd25ef3f --- /dev/null +++ b/app/models/referential_suite.rb @@ -0,0 +1,6 @@ +class ReferentialSuite < ActiveRecord::Base + belongs_to :new, class_name: 'Referential' + belongs_to :current, class_name: 'Referential' + + has_many :referentials +end diff --git a/app/models/vehicle_journey_import.rb b/app/models/vehicle_journey_import.rb index 44a6d457e..250f3a9e9 100644 --- a/app/models/vehicle_journey_import.rb +++ b/app/models/vehicle_journey_import.rb @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - class VehicleJourneyImport include ActiveModel::Validations include ActiveModel::Conversion diff --git a/app/policies/import_policy.rb b/app/policies/import_policy.rb index 9e1d99a66..b12dcc167 100644 --- a/app/policies/import_policy.rb +++ b/app/policies/import_policy.rb @@ -4,4 +4,16 @@ class ImportPolicy < ApplicationPolicy scope end end + + def create? + !archived? && user.has_permission?('imports.create') + end + + def destroy? + !archived? && user.has_permission?('imports.destroy') + end + + def update? + !archived? && user.has_permission?('imports.update') + end end diff --git a/app/views/compliance_control_sets/_filters.html.slim b/app/views/compliance_control_sets/_filters.html.slim index 7ee050636..56cac8bd2 100644 --- a/app/views/compliance_control_sets/_filters.html.slim +++ b/app/views/compliance_control_sets/_filters.html.slim @@ -5,6 +5,18 @@ span.input-group-btn button.btn.btn-default type='submit' span.fa.fa-search + .ffg-row + .form-group.togglable + = f.label t('activerecord.models.organisation.one'), required: false, class: 'control-label' + = f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'} + + .form-group.togglable + = f.label Import.human_attribute_name(:updated_at), required: false, class: 'control-label' + .filter_menu + = f.simple_fields_for :updated_at do |p| + = p.input :begin, as: :date, label: false, wrapper_html: {class: 'date smart_date filter_menu-item'}, default: @begin_range, include_blank: @begin_range ? false : true + = p.input :end, as: :date, label: false, wrapper_html: {class: 'date smart_date filter_menu-item'}, default: @end_range, include_blank: @end_range ? false : true + .actions = link_to t('actions.erase'), @compliance_control_set, class: 'btn btn-link' - = f.submit t('actions.filter'), class: 'btn btn-default', id: 'referential_filter_btn' + = f.submit t('actions.filter'), class: 'btn btn-default', id: 'referential_filter_btn'
\ No newline at end of file diff --git a/app/views/compliance_control_sets/index.html.slim b/app/views/compliance_control_sets/index.html.slim index 95833a01c..aee1595ef 100644 --- a/app/views/compliance_control_sets/index.html.slim +++ b/app/views/compliance_control_sets/index.html.slim @@ -24,7 +24,7 @@ key: :name, \ attribute: 'name', \ link_to: lambda do |compliance_control_set| \ - compliance_control_set_path(@compliance_control_sets, compliance_control_set) \ + compliance_control_set_path(compliance_control_set) \ end \ ), \ TableBuilderHelper::Column.new( \ @@ -45,6 +45,7 @@ ) \ ], sortable: true, + links: [:show], cls: 'table has-filter has-search' - unless @compliance_control_sets.any? .row.mt-xs diff --git a/app/views/routes/_form.html.slim b/app/views/routes/_form.html.slim index 244b427dc..24c0d3c4a 100644 --- a/app/views/routes/_form.html.slim +++ b/app/views/routes/_form.html.slim @@ -26,7 +26,8 @@ // Get JSON data for route stop points = javascript_tag do - | window.itinerary_stop = "#{URI.escape(route_json_for_edit(@route))}" + | window.itinerary_stop = "#{URI.escape(route_json_for_edit(@route))}"; + | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe}; / StopPoints Reactux component = javascript_include_tag 'es6_browserified/itineraries/index.js' diff --git a/app/views/time_tables/edit.html.slim b/app/views/time_tables/edit.html.slim index ed55dc4e3..cc6f31489 100644 --- a/app/views/time_tables/edit.html.slim +++ b/app/views/time_tables/edit.html.slim @@ -11,6 +11,6 @@ = javascript_tag do | window.actionType = "#{raw params[:action]}"; - | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe}; + | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe}; = javascript_include_tag 'es6_browserified/time_tables/index.js' diff --git a/config/locales/actions.en.yml b/config/locales/actions.en.yml index 8dea51ca3..c34462d0d 100644 --- a/config/locales/actions.en.yml +++ b/config/locales/actions.en.yml @@ -20,8 +20,10 @@ en: filter: 'Filter' erase: 'Erase' create_api_key: "Create an API key" + select: Select or: "or" cancel: "Cancel" + back: "Go Back" search_hint: "Type in a search term" no_result_text: "No Results" searching_term: "Searching..." diff --git a/config/locales/actions.fr.yml b/config/locales/actions.fr.yml index 01fc06326..df16d2aab 100644 --- a/config/locales/actions.fr.yml +++ b/config/locales/actions.fr.yml @@ -20,8 +20,10 @@ fr: filter: 'Filtrer' erase: 'Effacer' create_api_key: "Créer une clé d'API" + select: Sélectionner or: "ou" cancel: "Annuler" + back: "Retour" search_hint: "Entrez un texte à rechercher" no_result_text: "Aucun résultat" searching_term: "Recherche en cours..." diff --git a/config/locales/compliance_control_sets.en.yml b/config/locales/compliance_control_sets.en.yml new file mode 100644 index 000000000..497ca50c3 --- /dev/null +++ b/config/locales/compliance_control_sets.en.yml @@ -0,0 +1,24 @@ +fr: + compliance_control_sets: + index: + title: Control games + new: Creating a control set + edit: Editing a Control Game + actions: + new: Add + edit: Edit + destroy: Delete + destroy_confirm: Are you sure to remove the control games ? + filters: + name: Specify a control game name... + search_no_results: No control game matches your search + activerecord: + models: + compliance_control_set: Calendar + attributes: + compliance_control_set: + name: Name + assignment: Affectation + owner_jdc: Owner of the control game + control_numbers: Nb contrôle + updated_at: Update
\ No newline at end of file diff --git a/config/locales/compliance_control_sets.fr.yml b/config/locales/compliance_control_sets.fr.yml index fedfeede7..f5bb7c67b 100644 --- a/config/locales/compliance_control_sets.fr.yml +++ b/config/locales/compliance_control_sets.fr.yml @@ -10,8 +10,8 @@ fr: destroy: Supprimer destroy_confirm: Etes vous sûr de supprimer ce jeux de contrôle ? filters: - name: 'Indiquez un nom de jeux de contrôle...' - search_no_results: 'Aucun jeu de contrôle ne correspond à votre recherche' + name: Indiquez un nom de jeux de contrôle... + search_no_results: Aucun jeu de contrôle ne correspond à votre recherche activerecord: models: compliance_control_set: Calendrier diff --git a/config/locales/routes.en.yml b/config/locales/routes.en.yml index 63d7b198c..3b1fd85cc 100644 --- a/config/locales/routes.en.yml +++ b/config/locales/routes.en.yml @@ -17,6 +17,25 @@ en: title: "Add a new route" edit: title: "Update route %{route}" + select2: + placeholder: "Select a stop point..." + map: + stop_point_type: Stop point type + short_name: Short name + coordinates: Coordinates + proj: Proj + lat: Lat + lon: Lon + postal_code: Zip Code + city: City + comment: Comment + stop_point: + boarding: + normal: Normal boarding + forbidden: Forbidden boarding + alighting: + normal: Normal alighting + forbidden: Forbidden alighting show: title: "Route %{route}" stop_points: "Stop point on route list" diff --git a/config/locales/routes.fr.yml b/config/locales/routes.fr.yml index a494e60ec..43c40645d 100644 --- a/config/locales/routes.fr.yml +++ b/config/locales/routes.fr.yml @@ -17,6 +17,25 @@ fr: title: "Ajouter un itinéraire" edit: title: "Editer l'itinéraire %{route}" + select2: + placeholder: "Sélectionnez un arrêt existant..." + map: + stop_point_type: Type d'arrêt + short_name: Nom court + coordinates: Coordonnées + proj: Proj + lat: Lat + lon: Lon + postal_code: Code Postal + city: Commune + comment: Commentaire + stop_point: + boarding: + normal: Montée autorisée + forbidden: Montée interdite + alighting: + normal: Descente autorisée + forbidden: Descente interdite show: title: "Itinéraire %{route} de la ligne %{line}" stop_points: "Liste des arrêts de l'itinéraire" diff --git a/config/locales/stop_points.en.yml b/config/locales/stop_points.en.yml index 1ef8002d0..d22d85731 100644 --- a/config/locales/stop_points.en.yml +++ b/config/locales/stop_points.en.yml @@ -52,5 +52,6 @@ en: simple_form: labels: stop_point: + name: Stop Point for_boarding: "Pickup" for_alighting: "Drop off" diff --git a/config/locales/stop_points.fr.yml b/config/locales/stop_points.fr.yml index d90041945..d3c873442 100644 --- a/config/locales/stop_points.fr.yml +++ b/config/locales/stop_points.fr.yml @@ -52,5 +52,6 @@ fr: simple_form: labels: stop_point: + name: Arrêt for_boarding: "Montée" for_alighting: "Descente" diff --git a/config/locales/time_tables.en.yml b/config/locales/time_tables.en.yml index d67e30edb..e68836f99 100644 --- a/config/locales/time_tables.en.yml +++ b/config/locales/time_tables.en.yml @@ -29,10 +29,39 @@ en: title: "Duplicate timetable" edit: title: "Update timetable %{time_table}" + day_types: Day types + periods: Periods + exceptions: Exceptions + synthesis: Synthesis error_modal: title: "Error" withoutPeriodsWithDaysTypes: "A timetable can't have day type(s) without period(s)." - withPeriodsWithoutDayTypes: "A tiemetable can't have period(s) swithout day type(s)." + withPeriodsWithoutDayTypes: "A tiemetable can't have period(s) swithout day type(s)." + error_submit: + periods_overlaps: "Periods cannot overlap in a timetable" + dates_overlaps: "A period cannot overlap a date in a timetable" + confirm_modal: + title: "Confirm" + message: "You are about to change pages. Do you want to validate your changes before this?" + metas: + name: Name + calendar: Associated calendar + no_calendar: None + day_types: Periods day tpes + days: + 1: Su + 2: Mo + 3: Tu + 4: We + 5: Th + 6: Fr + 7: Sa + select2: + tag: + placeholder: Add or search a tag... + period_form: + begin: Period start + end: Period end show: title: "Timetable %{time_table}" dates: "Application dates" diff --git a/config/locales/time_tables.fr.yml b/config/locales/time_tables.fr.yml index 06d1d59e8..b85f7ca33 100644 --- a/config/locales/time_tables.fr.yml +++ b/config/locales/time_tables.fr.yml @@ -29,10 +29,39 @@ fr: title: "Dupliquer un calendrier" edit: title: "Editer le calendrier %{time_table}" + day_types: Journées d'application + periods: Périodes + exceptions: Exceptions + synthesis: Synthèse error_modal: title: "Erreur" withoutPeriodsWithDaysTypes: "Un calendrier d'application ne peut pas avoir de journée(s) d'application sans période(s)." - withPeriodsWithoutDayTypes: "Un calendrier d'application ne peut pas avoir de période(s) sans journée(s) d'application." + withPeriodsWithoutDayTypes: "Un calendrier d'application ne peut pas avoir de période(s) sans journée(s) d'application." + error_submit: + periods_overlaps: "Les périodes ne peuvent pas se chevaucher" + dates_overlaps: "Une période ne peut chevaucher une date dans un calendrier" + confirm_modal: + title: "Confirmation" + message: "Vous vous apprêtez à changer de page. Voulez-vous valider vos modifications avant cela ?" + metas: + name: Nom + calendar: Modèle de calendrier associée + no_calendar: Aucun + day_types: Journées d'applications pour les périodes ci-dessous + days: + 1: Di + 2: Lu + 3: Ma + 4: Me + 5: Je + 6: Ve + 7: Sa + select2: + tag: + placeholder: Ajoutez ou cherchez une étiquette... + period_form: + begin: Début de période + end: Fin de période show: title: Calendrier %{time_table} dates: "Dates d'application" diff --git a/config/locales/vehicle_journey_at_stops.en.yml b/config/locales/vehicle_journey_at_stops.en.yml index a96effa2b..8a3bd9394 100644 --- a/config/locales/vehicle_journey_at_stops.en.yml +++ b/config/locales/vehicle_journey_at_stops.en.yml @@ -1,4 +1,4 @@ en: vehicle_journey_at_stops: errors: - day_offset_must_not_exceed_max: "The vehicle journey with ID %{local_id} cannot have times exceeding %{max} days" + day_offset_must_not_exceed_max: "The vehicle journey with ID %{short_id} cannot have times exceeding %{max} days" diff --git a/config/locales/vehicle_journey_at_stops.fr.yml b/config/locales/vehicle_journey_at_stops.fr.yml index 3eff79cf4..f5139fb79 100644 --- a/config/locales/vehicle_journey_at_stops.fr.yml +++ b/config/locales/vehicle_journey_at_stops.fr.yml @@ -1,4 +1,4 @@ fr: vehicle_journey_at_stops: errors: - day_offset_must_not_exceed_max: "La course avec l'identifiant %{local_id} ne peut pas avoir des horaires sur plus de %{max} jours" + day_offset_must_not_exceed_max: "La course avec l'identifiant %{short_id} ne peut pas avoir des horaires sur plus de %{max} jours" diff --git a/db/migrate/20170922161352_create_referential_suites.rb b/db/migrate/20170922161352_create_referential_suites.rb new file mode 100644 index 000000000..5a8693d01 --- /dev/null +++ b/db/migrate/20170922161352_create_referential_suites.rb @@ -0,0 +1,10 @@ +class CreateReferentialSuites < ActiveRecord::Migration + def change + create_table :referential_suites do |t| + t.bigint :new_id, index: true + t.bigint :current_id, index: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170922165315_add_referential_suite_to_referentials.rb b/db/migrate/20170922165315_add_referential_suite_to_referentials.rb new file mode 100644 index 000000000..a01ba4d40 --- /dev/null +++ b/db/migrate/20170922165315_add_referential_suite_to_referentials.rb @@ -0,0 +1,8 @@ +class AddReferentialSuiteToReferentials < ActiveRecord::Migration + def change + add_reference :referentials, :referential_suite, + index: true, + foreign_key: true, + type: :bigint + end +end diff --git a/db/schema.rb b/db/schema.rb index 2b62fa7f1..89f002aee 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170918103913) do +ActiveRecord::Schema.define(version: 20170922165315) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -602,6 +602,16 @@ ActiveRecord::Schema.define(version: 20170918103913) do add_index "referential_metadata", ["referential_id"], name: "index_referential_metadata_on_referential_id", using: :btree add_index "referential_metadata", ["referential_source_id"], name: "index_referential_metadata_on_referential_source_id", using: :btree + create_table "referential_suites", id: :bigserial, force: :cascade do |t| + t.integer "new_id", limit: 8 + t.integer "current_id", limit: 8 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "referential_suites", ["current_id"], name: "index_referential_suites_on_current_id", using: :btree + add_index "referential_suites", ["new_id"], name: "index_referential_suites_on_new_id", using: :btree + create_table "referentials", id: :bigserial, force: :cascade do |t| t.string "name" t.string "slug" @@ -622,9 +632,11 @@ ActiveRecord::Schema.define(version: 20170918103913) do t.datetime "archived_at" t.integer "created_from_id", limit: 8 t.boolean "ready", default: false + t.integer "referential_suite_id", limit: 8 end add_index "referentials", ["created_from_id"], name: "index_referentials_on_created_from_id", using: :btree + add_index "referentials", ["referential_suite_id"], name: "index_referentials_on_referential_suite_id", using: :btree create_table "route_sections", id: :bigserial, force: :cascade do |t| t.integer "departure_id", limit: 8 @@ -987,6 +999,7 @@ ActiveRecord::Schema.define(version: 20170918103913) do add_foreign_key "journey_patterns", "stop_points", column: "departure_stop_point_id", name: "departure_point_fkey", on_delete: :nullify add_foreign_key "journey_patterns_stop_points", "journey_patterns", name: "jpsp_jp_fkey", on_delete: :cascade add_foreign_key "journey_patterns_stop_points", "stop_points", name: "jpsp_stoppoint_fkey", on_delete: :cascade + add_foreign_key "referentials", "referential_suites" add_foreign_key "route_sections", "stop_areas", column: "arrival_id" add_foreign_key "route_sections", "stop_areas", column: "departure_id" add_foreign_key "routes", "routes", column: "opposite_route_id", name: "route_opposite_route_fkey" diff --git a/lib/model_attribute.rb b/lib/model_attribute.rb new file mode 100644 index 000000000..4d246853a --- /dev/null +++ b/lib/model_attribute.rb @@ -0,0 +1,101 @@ +class ModelAttribute + attr_reader :klass, :name, :data_type + + def self.all + @__all__ ||= [] + end + + def self.define(klass, name, data_type) + all << new(klass, name, data_type) + end + + def self.classes + all + .map(&:klass) + .uniq + .map(&:to_s) + .map(&:camelize) + end + + def self.group_by_class + all.group_by(&:klass) + end + + def self.from_code(code) + klass, name = code.split('#').map(&:to_sym) + + methods_by_class(klass).select do |model_attr| + model_attr.name == name + end.first + end + + def self.methods_by_class(klass) + all.select do |model_attr| + model_attr.klass == klass + end + end + + def self.methods_by_class_and_type(klass, type) + methods_by_class(klass).select do |model_attr| + model_attr.data_type == type + end + end + + def initialize(klass, name, data_type) + @klass = klass + @name = name + @data_type = data_type + end + + # Chouette::Route + define :route, :name, :string + define :route, :published_name, :string + define :route, :comment, :string + define :route, :number, :string + define :route, :direction, :string + define :route, :wayback, :string + + # Chouette::JourneyPattern + define :journey_pattern, :name, :string + define :journey_pattern, :published_name, :string + define :journey_pattern, :comment, :string + define :journey_pattern, :registration_number, :string + define :journey_pattern, :section_status, :integer + + # Chouette::VehicleJourney + define :vehicle_journey, :comment, :string + define :vehicle_journey, :status_value, :string + define :vehicle_journey, :transport_mode, :string + define :vehicle_journey, :facility, :string + define :vehicle_journey, :published_journey_name, :string + define :vehicle_journey, :published_journey_identifier, :string + define :vehicle_journey, :vehicle_type_identifier, :string + define :vehicle_journey, :number, :integer + define :vehicle_journey, :mobility_restricted_suitability, :boolean + define :vehicle_journey, :flexible_service, :boolean + + # Chouette::Footnote + define :footnote, :code, :string + define :footnote, :label, :string + + # Chouette::TimeTable + define :time_table, :version, :string + define :time_table, :comment, :string + define :time_table, :start_date, :date + define :time_table, :end_date, :date + define :time_table, :color, :string + + # Chouette::RoutingConstraintZone + define :routing_constraint_zone, :name, :string + + def code + "#{@klass}##{@name}" + end + + def ==(other) + self.class === other && + klass == other.klass && + name == other.name && + data_type == other.data_type + end +end diff --git a/lib/stif/permission_translator.rb b/lib/stif/permission_translator.rb index 2bc565968..15d5ffc89 100644 --- a/lib/stif/permission_translator.rb +++ b/lib/stif/permission_translator.rb @@ -19,6 +19,7 @@ module Stif access_points connection_links calendars footnotes + imports journey_patterns referentials routes routing_constraint_zones time_tables @@ -29,7 +30,6 @@ module Stif end def destructive_permissions_for(models) - @__destructive_permissions_for__ ||= models.product( %w{create destroy update} ).map{ |model_action| model_action.join('.') } end diff --git a/spec/controllers/imports_controller_spec.rb b/spec/controllers/imports_controller_spec.rb index f07190496..22be9f6ed 100644 --- a/spec/controllers/imports_controller_spec.rb +++ b/spec/controllers/imports_controller_spec.rb @@ -5,10 +5,16 @@ RSpec.describe ImportsController, :type => :controller do let(:import) { create :import, workbench: workbench } describe 'GET #new' do - it 'should be successful' do + it 'should be successful if authorized' do get :new, workbench_id: workbench.id expect(response).to be_success end + + it 'should be unsuccessful unless authorized' do + remove_permissions('imports.create', from_user: @user, save: true) + get :new, workbench_id: workbench.id + expect(response).not_to be_success + end end describe 'GET #download' do @@ -18,4 +24,5 @@ RSpec.describe ImportsController, :type => :controller do expect( response.body ).to eq(import.file.read) end end + end diff --git a/spec/javascripts/time_table/actions_spec.js b/spec/javascripts/time_table/actions_spec.js index f052aeece..a7344586d 100644 --- a/spec/javascripts/time_table/actions_spec.js +++ b/spec/javascripts/time_table/actions_spec.js @@ -171,28 +171,52 @@ describe('actions', () => { expect(actions.validatePeriodForm(modalProps, timeTablePeriods, metas, timetableInDates, error)).toEqual(expectedAction) }) - it('should create an action to include date in period', () => { + it('should create an action to add an included date', () => { let index = 1 let date = actions.formatDate(new Date) const expectedAction = { - type: 'INCLUDE_DATE_IN_PERIOD', + type: 'ADD_INCLUDED_DATE', index, dayTypes, date } - expect(actions.includeDateInPeriod(index, dayTypes, date)).toEqual(expectedAction) + expect(actions.addIncludedDate(index, dayTypes, date)).toEqual(expectedAction) }) - it('should create an action to exclude date from period', () => { + it('should create an action to remove an included dat', () => { let index = 1 let date = actions.formatDate(new Date) const expectedAction = { - type: 'EXCLUDE_DATE_FROM_PERIOD', + type: 'REMOVE_INCLUDED_DATE', index, dayTypes, date } - expect(actions.excludeDateFromPeriod(index, dayTypes, date)).toEqual(expectedAction) + expect(actions.removeIncludedDate(index, dayTypes, date)).toEqual(expectedAction) + }) + + it('should create an action to add an excluded date in period', () => { + let index = 1 + let date = actions.formatDate(new Date) + const expectedAction = { + type: 'ADD_EXCLUDED_DATE', + index, + dayTypes, + date + } + expect(actions.addExcludedDate(index, dayTypes, date)).toEqual(expectedAction) + }) + + it('should create an action to remove an excluded date from period', () => { + let index = 1 + let date = actions.formatDate(new Date) + const expectedAction = { + type: 'REMOVE_EXCLUDED_DATE', + index, + dayTypes, + date + } + expect(actions.removeExcludedDate(index, dayTypes, date)).toEqual(expectedAction) }) it('should create an action to open confirm modal', () => { diff --git a/spec/javascripts/time_table/reducers/modal_spec.js b/spec/javascripts/time_table/reducers/modal_spec.js index 570eb85ed..05d58a138 100644 --- a/spec/javascripts/time_table/reducers/modal_spec.js +++ b/spec/javascripts/time_table/reducers/modal_spec.js @@ -171,12 +171,14 @@ describe('modal reducer', () => { let ttperiods = [] let ttdates = [] + let metas = [] expect( modalReducer(state, { type: 'VALIDATE_PERIOD_FORM', modalProps : modProps, timeTablePeriods: ttperiods, + metas: metas, timetableInDates: ttdates, error: 'La date de départ doit être antérieure à la date de fin' }) @@ -289,9 +291,12 @@ describe('modal reducer', () => { index: false, error: '' } - let ttperiods3 = [] + let ttperiods3 = [] let ttdates3 = [{date: "2017-08-04", include_date: true}] + let metas = { + day_types: [true,true,true,true,true,true,true] + } let newModalProps3 = { active: true, @@ -315,6 +320,7 @@ describe('modal reducer', () => { modalProps : modProps3, timeTablePeriods: ttperiods3, timetableInDates: ttdates3, + metas: metas, error: "Une période ne peut chevaucher une date dans un calendrier" }) ).toEqual(Object.assign({}, state3, {modalProps: newModalProps3})) diff --git a/spec/javascripts/time_table/reducers/pagination_spec.js b/spec/javascripts/time_table/reducers/pagination_spec.js index 5da58427e..3c1edb9c5 100644 --- a/spec/javascripts/time_table/reducers/pagination_spec.js +++ b/spec/javascripts/time_table/reducers/pagination_spec.js @@ -76,20 +76,38 @@ describe('pagination reducer', () => { ).toEqual(Object.assign({}, state, {currentPage : page, stateChanged: false})) }) - it('should handle INCLUDE_DATE_IN_PERIOD', () => { + it('should handle ADD_INCLUDED_DATE', () => { expect( paginationReducer(state, { - type: 'INCLUDE_DATE_IN_PERIOD' + type: 'ADD_INCLUDED_DATE' }) ).toEqual(Object.assign({}, state, {stateChanged: true})) }) - it('should handle EXCLUDE_DATE_FROM_PERIOD', () => { + + it('should handle REMOVE_INCLUDED_DATE', () => { + expect( + paginationReducer(state, { + type: 'REMOVE_INCLUDED_DATE' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) + + it('should handle ADD_EXCLUDED_DATE', () => { expect( paginationReducer(state, { - type: 'EXCLUDE_DATE_FROM_PERIOD' + type: 'ADD_EXCLUDED_DATE' }) ).toEqual(Object.assign({}, state, {stateChanged: true})) }) + + it('should handle REMOVE_EXCLUDED_DATE', () => { + expect( + paginationReducer(state, { + type: 'REMOVE_EXCLUDED_DATE' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) + it('should handle DELETE_PERIOD', () => { expect( paginationReducer(state, { diff --git a/spec/javascripts/time_table/reducers/timetable_spec.js b/spec/javascripts/time_table/reducers/timetable_spec.js index 6585a78a0..21f6d236d 100644 --- a/spec/javascripts/time_table/reducers/timetable_spec.js +++ b/spec/javascripts/time_table/reducers/timetable_spec.js @@ -6,14 +6,13 @@ const dispatch = function(){} let arrDayTypes = [true, true, true, true, true, true, true] let strDayTypes = 'LuMaMeJeVeSaDi' let time_table_periods = [{"id":261,"period_start":"2017-02-23","period_end":"2017-03-05"},{"id":262,"period_start":"2017-03-15","period_end":"2017-03-25"},{"id":263,"period_start":"2017-04-04","period_end":"2017-04-14"},{"id":264,"period_start":"2017-04-24","period_end":"2017-05-04"},{"id":265,"period_start":"2017-05-14","period_end":"2017-05-24"}] +let time_table_dates = [] let current_periode_range = "2017-05-01" let periode_range = ["2014-05-01","2014-06-01","2014-07-01","2014-08-01","2014-09-01","2014-10-01","2014-11-01","2014-12-01","2015-01-01","2015-02-01","2015-03-01","2015-04-01","2015-05-01","2015-06-01","2015-07-01","2015-08-01","2015-09-01","2015-10-01","2015-11-01","2015-12-01","2016-01-01","2016-02-01","2016-03-01","2016-04-01","2016-05-01","2016-06-01","2016-07-01","2016-08-01","2016-09-01","2016-10-01","2016-11-01","2016-12-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2018-08-01","2018-09-01","2018-10-01","2018-11-01","2018-12-01","2019-01-01","2019-02-01","2019-03-01","2019-04-01","2019-05-01","2019-06-01","2019-07-01","2019-08-01","2019-09-01","2019-10-01","2019-11-01","2019-12-01","2020-01-01","2020-02-01","2020-03-01","2020-04-01","2020-05-01"] let current_month = [{"day":"lundi","date":"2017-05-01","wday":1,"wnumber":"18","mday":1,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-02","wday":2,"wnumber":"18","mday":2,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-03","wday":3,"wnumber":"18","mday":3,"include_date":false,"excluded_date":false},{"day":"jeudi","date":"2017-05-04","wday":4,"wnumber":"18","mday":4,"include_date":false,"excluded_date":false},{"day":"vendredi","date":"2017-05-05","wday":5,"wnumber":"18","mday":5,"include_date":false,"excluded_date":false},{"day":"samedi","date":"2017-05-06","wday":6,"wnumber":"18","mday":6,"include_date":false,"excluded_date":false},{"day":"dimanche","date":"2017-05-07","wday":0,"wnumber":"18","mday":7,"include_date":false,"excluded_date":false},{"day":"lundi","date":"2017-05-08","wday":1,"wnumber":"19","mday":8,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-09","wday":2,"wnumber":"19","mday":9,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-10","wday":3,"wnumber":"19","mday":10,"include_date":false,"excluded_date":false},{"day":"jeudi","date":"2017-05-11","wday":4,"wnumber":"19","mday":11,"include_date":false,"excluded_date":false},{"day":"vendredi","date":"2017-05-12","wday":5,"wnumber":"19","mday":12,"include_date":false,"excluded_date":false},{"day":"samedi","date":"2017-05-13","wday":6,"wnumber":"19","mday":13,"include_date":false,"excluded_date":false},{"day":"dimanche","date":"2017-05-14","wday":0,"wnumber":"19","mday":14,"include_date":false,"excluded_date":false},{"day":"lundi","date":"2017-05-15","wday":1,"wnumber":"20","mday":15,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-16","wday":2,"wnumber":"20","mday":16,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-17","wday":3,"wnumber":"20","mday":17,"include_date":false,"excluded_date":false},{"day":"jeudi","date":"2017-05-18","wday":4,"wnumber":"20","mday":18,"include_date":false,"excluded_date":false},{"day":"vendredi","date":"2017-05-19","wday":5,"wnumber":"20","mday":19,"include_date":false,"excluded_date":false},{"day":"samedi","date":"2017-05-20","wday":6,"wnumber":"20","mday":20,"include_date":false,"excluded_date":false},{"day":"dimanche","date":"2017-05-21","wday":0,"wnumber":"20","mday":21,"include_date":false,"excluded_date":false},{"day":"lundi","date":"2017-05-22","wday":1,"wnumber":"21","mday":22,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-23","wday":2,"wnumber":"21","mday":23,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-24","wday":3,"wnumber":"21","mday":24,"include_date":false,"excluded_date":false},{"day":"jeudi","date":"2017-05-25","wday":4,"wnumber":"21","mday":25,"include_date":false,"excluded_date":false},{"day":"vendredi","date":"2017-05-26","wday":5,"wnumber":"21","mday":26,"include_date":false,"excluded_date":false},{"day":"samedi","date":"2017-05-27","wday":6,"wnumber":"21","mday":27,"include_date":false,"excluded_date":false},{"day":"dimanche","date":"2017-05-28","wday":0,"wnumber":"21","mday":28,"include_date":false,"excluded_date":false},{"day":"lundi","date":"2017-05-29","wday":1,"wnumber":"22","mday":29,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-30","wday":2,"wnumber":"22","mday":30,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-31","wday":3,"wnumber":"22","mday":31,"include_date":false,"excluded_date":false}] let newCurrentMonth = [{"day":"lundi","date":"2017-05-01","wday":1,"wnumber":"18","mday":1,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-02","wday":2,"wnumber":"18","mday":2,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-03","wday":3,"wnumber":"18","mday":3,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-04","wday":4,"wnumber":"18","mday":4,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"vendredi","date":"2017-05-05","wday":5,"wnumber":"18","mday":5,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-06","wday":6,"wnumber":"18","mday":6,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-07","wday":0,"wnumber":"18","mday":7,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"lundi","date":"2017-05-08","wday":1,"wnumber":"19","mday":8,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mardi","date":"2017-05-09","wday":2,"wnumber":"19","mday":9,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mercredi","date":"2017-05-10","wday":3,"wnumber":"19","mday":10,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"jeudi","date":"2017-05-11","wday":4,"wnumber":"19","mday":11,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"vendredi","date":"2017-05-12","wday":5,"wnumber":"19","mday":12,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-13","wday":6,"wnumber":"19","mday":13,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-14","wday":0,"wnumber":"19","mday":14,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"lundi","date":"2017-05-15","wday":1,"wnumber":"20","mday":15,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-16","wday":2,"wnumber":"20","mday":16,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-17","wday":3,"wnumber":"20","mday":17,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-18","wday":4,"wnumber":"20","mday":18,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"vendredi","date":"2017-05-19","wday":5,"wnumber":"20","mday":19,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"samedi","date":"2017-05-20","wday":6,"wnumber":"20","mday":20,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"dimanche","date":"2017-05-21","wday":0,"wnumber":"20","mday":21,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"lundi","date":"2017-05-22","wday":1,"wnumber":"21","mday":22,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-23","wday":2,"wnumber":"21","mday":23,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-24","wday":3,"wnumber":"21","mday":24,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-25","wday":4,"wnumber":"21","mday":25,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"vendredi","date":"2017-05-26","wday":5,"wnumber":"21","mday":26,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-27","wday":6,"wnumber":"21","mday":27,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-28","wday":0,"wnumber":"21","mday":28,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"lundi","date":"2017-05-29","wday":1,"wnumber":"22","mday":29,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mardi","date":"2017-05-30","wday":2,"wnumber":"22","mday":30,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mercredi","date":"2017-05-31","wday":3,"wnumber":"22","mday":31,"include_date":false,"excluded_date":false,"in_periods":false}] -let time_table_dates = [] - let json = { current_month: current_month, current_periode_range: current_periode_range, @@ -46,7 +45,7 @@ describe('timetable reducer with empty state', () => { current_periode_range: current_periode_range, periode_range: periode_range, time_table_periods: time_table_periods, - time_table_dates: time_table_dates + time_table_dates: [] } expect( timetableReducer(state, { @@ -125,24 +124,46 @@ describe('timetable reducer with filled state', () => { ).toEqual(Object.assign({}, state, {current_periode_range: newPage})) }) - it('should handle DELETE_PERIOD', () => { - state.time_table_periods[0].deleted = true + it('should handle DELETE_PERIOD and remove excluded days that were in period', () => { + state.time_table_dates.push({date: "2017-05-01", in_out: false}) + state.current_month[0].excluded_date = true + state.time_table_periods[3].deleted = true + + let begin = new Date(state.time_table_periods[3].period_start) + let end = new Date(state.time_table_periods[3].period_end) + + let newState = Object.assign({}, state, { + time_table_dates: [], + current_month: state.current_month.map((d, i) => { + if (new Date(d.date) >= begin && new Date(d.date) <= end) { + d.excluded_date = false + d.in_periods = false + } + return d + }) + }) expect( timetableReducer(state, { type: 'DELETE_PERIOD', - index: 0, + index: 3, dayTypes: arrDayTypes }) - ).toEqual(state) + ).toEqual(newState) }) - it('should handle INCLUDE_DATE_IN_PERIOD and add in_day if TT doesnt have it', () => { + it('should handle ADD_INCLUDED_DATE', () => { let newDates = state.time_table_dates.concat({date: "2017-05-05", in_out: true}) - let newState = Object.assign({}, state, {time_table_dates: newDates}) - state.current_month[4].include_date = true + + let newCM = newCurrentMonth.map((d,i) => { + if (i == 4) d.include_date = true + return d + }) + + let newState = Object.assign({}, state, {time_table_dates: newDates, current_month: newCM}) + expect( timetableReducer(state, { - type: 'INCLUDE_DATE_IN_PERIOD', + type: 'ADD_INCLUDED_DATE', index: 4, dayTypes: arrDayTypes, date: "2017-05-05" @@ -150,13 +171,20 @@ describe('timetable reducer with filled state', () => { ).toEqual(newState) }) - it('should handle INCLUDE_DATE_IN_PERIOD and remove in_day if TT has it', () => { + it('should handle REMOVE_INCLUDED_DATE', () => { state.current_month[4].include_date = true state.time_table_dates.push({date: "2017-05-05", in_out: true}) - let newState = Object.assign({}, state, {time_table_dates: []}) + + let newCM = newCurrentMonth.map((d,i) => { + if (i == 4) d.include_date = false + return d + }) + + let newDates = state.time_table_dates.filter(d => d.date != "2017-05-05" && d.in_out != true ) + let newState = Object.assign({}, state, {time_table_dates: newDates, current_month: newCM}) expect( timetableReducer(state, { - type: 'INCLUDE_DATE_IN_PERIOD', + type: 'REMOVE_INCLUDED_DATE', index: 4, dayTypes: arrDayTypes, date: "2017-05-05" @@ -164,13 +192,22 @@ describe('timetable reducer with filled state', () => { ).toEqual(newState) }) - it('should handle EXCLUDE_DATE_FROM_PERIOD and add out_day if TT doesnt have it', () => { + it('should handle ADD_EXCLUDED_DATE', () => { let newDates = state.time_table_dates.concat({date: "2017-05-01", in_out: false}) - let newState = Object.assign({}, state, {time_table_dates: newDates}) - state.current_month[0].excluded_date = true + + let newCM = newCurrentMonth.map((d,i) => { + if (i == 0){ + d.include_date = false + d.excluded_date = true + } + return d + }) + + let newState = Object.assign({}, state, {time_table_dates: newDates, current_month: newCM}) + expect( timetableReducer(state, { - type: 'EXCLUDE_DATE_FROM_PERIOD', + type: 'ADD_EXCLUDED_DATE', index: 0, dayTypes: arrDayTypes, date: "2017-05-01" @@ -178,13 +215,13 @@ describe('timetable reducer with filled state', () => { ).toEqual(newState) }) - it('should handle EXCLUDE_DATE_FROM_PERIOD and remove out_day if TT has it', () => { + it('should handle REMOVE_EXCLUDED_DATE', () => { state.time_table_dates = [{date: "2017-05-01", in_out: false}] - state.current_month[0].excluded_date = true + state.current_month[0].excluded_date = false let newState = Object.assign({}, state, {time_table_dates: []}) expect( timetableReducer(state, { - type: 'EXCLUDE_DATE_FROM_PERIOD', + type: 'REMOVE_EXCLUDED_DATE', index: 0, dayTypes: arrDayTypes, date: "2017-05-01" @@ -194,9 +231,10 @@ describe('timetable reducer with filled state', () => { it('should handle UPDATE_DAY_TYPES and remove out_day that are out of day types', () => { state.time_table_dates = [{date: "2017-05-01", in_out: false}] - let newArrDayTypes = arrDayTypes.slice(0) - newArrDayTypes[1] = false - let newState = Object.assign({}, state, {time_table_dates: []}) + let newArrDayTypes = Array.from(arrDayTypes, (dt, i) => { + if (i == 1) dt = false + return dt + }) expect( timetableReducer(state, { type: 'UPDATE_DAY_TYPES', @@ -205,9 +243,19 @@ describe('timetable reducer with filled state', () => { ).toEqual([]) }) + it('should handle UPDATE_DAY_TYPES and remove in_day that are in day types and in period', () => { + state.time_table_dates = [{ date: "2017-05-16", in_out: true }] + expect( + timetableReducer(state, { + type: 'UPDATE_DAY_TYPES', + dayTypes: arrDayTypes + }).time_table_dates + ).toEqual([]) + }) + it('should handle VALIDATE_PERIOD_FORM and add period if modalProps index = false', () => { let newPeriods = state.time_table_periods.concat({"period_start": "2018-05-15", "period_end": "2018-05-24"}) - let newState = Object.assign({}, state, {time_table_periods: newPeriods}) + let newState = Object.assign({}, state, {time_table_periods: newPeriods, time_table_dates: []}) let modalProps = { active: false, begin: { @@ -236,4 +284,53 @@ describe('timetable reducer with filled state', () => { }) ).toEqual(newState) }) + + it('should handle VALIDATE_PERIOD_FORM and update period if modalProps index != false', () => { + + let begin = new Date(state.time_table_periods[0].period_start) + let end = new Date(state.time_table_periods[0].period_end) + let newCM = newCurrentMonth.map((d) => { + if (new Date (d.date) >= begin && new Date(d.date) <= end) { + d.in_periods = false + d.excluded_date = false + } + return d + }) + + let newPeriods = state.time_table_periods.map( (p,i) => { + if (i == 0) { + p.period_start = "2018-05-15" + p.period_end = "2018-05-24" + } + return p + }) + let newState = Object.assign({}, state, {time_table_periods: newPeriods}) + + let modalProps = { + active: false, + begin: { + day: '15', + month: '05', + year: '2018' + }, + end: { + day: '24', + month: '05', + year: '2018' + }, + error: '', + index: 0 + } + expect( + timetableReducer(state, { + type: 'VALIDATE_PERIOD_FORM', + modalProps: modalProps, + timeTablePeriods: state.time_table_periods, + metas: { + day_types: arrDayTypes + }, + timetableInDates: state.time_table_dates.filter(d => d.in_out == true) + }) + ).toEqual(newState) + }) }) diff --git a/spec/lib/model_attribute_spec.rb b/spec/lib/model_attribute_spec.rb new file mode 100644 index 000000000..cdba87a90 --- /dev/null +++ b/spec/lib/model_attribute_spec.rb @@ -0,0 +1,111 @@ +RSpec.describe ModelAttribute do + before(:each) do + ModelAttribute.instance_variable_set(:@__all__, []) + end + + describe ".define" do + it "adds a new instance of ModelAttribute to .all" do + expect do + ModelAttribute.define(:route, :name, :string) + end.to change { ModelAttribute.all.length }.by(1) + + model_attr = ModelAttribute.all.last + + expect(model_attr).to be_an_instance_of(ModelAttribute) + expect(model_attr.klass).to eq(:route) + expect(model_attr.name).to eq(:name) + expect(model_attr.data_type).to eq(:string) + end + end + + describe ".classes" do + it "returns the list of classes of ModelAttributes in .all" do + ModelAttribute.define(:route, :name, :string) + ModelAttribute.define(:journey_pattern, :name, :string) + ModelAttribute.define(:time_table, :start_date, :date) + + expect(ModelAttribute.classes).to match_array([ + 'Route', + 'JourneyPattern', + 'TimeTable' + ]) + end + end + + describe ".from_code" do + it "returns a ModelAttribute from a given code" do + ModelAttribute.define(:journey_pattern, :name, :string) + + expect(ModelAttribute.from_code('journey_pattern#name')).to eq( + ModelAttribute.new(:journey_pattern, :name, :string) + ) + end + end + + describe ".group_by_class" do + it "returns all ModelAttributes grouped by klass" do + ModelAttribute.define(:route, :name, :string) + ModelAttribute.define(:route, :published_name, :string) + ModelAttribute.define(:journey_pattern, :name, :string) + ModelAttribute.define(:vehicle_journey, :number, :integer) + + expect(ModelAttribute.group_by_class).to eq({ + route: [ + ModelAttribute.new(:route, :name, :string), + ModelAttribute.new(:route, :published_name, :string), + ], + journey_pattern: [ + ModelAttribute.new(:journey_pattern, :name, :string), + ], + vehicle_journey: [ + ModelAttribute.new(:vehicle_journey, :number, :integer) + ] + }) + end + end + + describe ".methods_by_class" do + it "returns all ModelAttributes for a given class" do + ModelAttribute.define(:route, :name, :string) + ModelAttribute.define(:route, :published_name, :string) + ModelAttribute.define(:route, :direction, :string) + ModelAttribute.define(:journey_pattern, :name, :string) + + expect(ModelAttribute.methods_by_class(:route)).to match_array([ + ModelAttribute.new(:route, :name, :string), + ModelAttribute.new(:route, :published_name, :string), + ModelAttribute.new(:route, :direction, :string) + ]) + end + end + + describe ".methods_by_class_and_type" do + it "returns ModelAttributes of a certain class and type" do + ModelAttribute.define(:route, :name, :string) + ModelAttribute.define(:route, :checked_at, :date) + ModelAttribute.define(:journey_pattern, :name, :string) + ModelAttribute.define(:journey_pattern, :section_status, :integer) + + expect(ModelAttribute.methods_by_class_and_type(:route, :string)).to match_array([ + ModelAttribute.new(:route, :name, :string) + ]) + end + end + + describe "#code" do + it "returns a string representation of the attribute" do + model_attr = ModelAttribute.new(:route, :name, :string) + + expect(model_attr.code).to eq('route#name') + end + end + + describe "#==" do + it "returns true when :klass, :name, and :data_type attributes match" do + route_name = ModelAttribute.new(:route, :name, :string) + other_route_name = ModelAttribute.new(:route, :name, :string) + + expect(route_name == other_route_name).to be true + end + end +end diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb index 4f9d12730..03e6fcb7d 100644 --- a/spec/models/chouette/vehicle_journey_at_stop_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do ) error_message = I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', - local_id: at_stop.vehicle_journey.objectid.local_id, + short_id: at_stop.vehicle_journey.objectid.short_id, max: bad_offset ) diff --git a/spec/models/referential_spec.rb b/spec/models/referential_spec.rb index f9ace08cc..bb8fabb2e 100644 --- a/spec/models/referential_spec.rb +++ b/spec/models/referential_spec.rb @@ -10,6 +10,7 @@ describe Referential, :type => :model do it { should have_many(:metadatas) } it { should belong_to(:workbench) } + it { should belong_to(:referential_suite) } context ".referential_ids_in_periode" do it 'should retrieve referential id in periode range' do diff --git a/spec/models/referential_suite_spec.rb b/spec/models/referential_suite_spec.rb new file mode 100644 index 000000000..771187b55 --- /dev/null +++ b/spec/models/referential_suite_spec.rb @@ -0,0 +1,5 @@ +RSpec.describe ReferentialSuite, type: :model do + it { should belong_to(:new).class_name('Referential') } + it { should belong_to(:current).class_name('Referential') } + it { should have_many(:referentials) } +end diff --git a/spec/policies/api_key_policy_spec.rb b/spec/policies/api_key_policy_spec.rb index f0242978e..3638a05b2 100644 --- a/spec/policies/api_key_policy_spec.rb +++ b/spec/policies/api_key_policy_spec.rb @@ -21,7 +21,7 @@ RSpec.describe ApiKeyPolicy do end context 'permission present → ' do it 'allows a user with a different organisation' do - add_permissions('api_keys.create', for_user: user) + add_permissions('api_keys.create', to_user: user) expect_it.to permit(user_context, record) end end @@ -40,7 +40,7 @@ RSpec.describe ApiKeyPolicy do context 'permission present → ' do before do - add_permissions('api_keys.update', for_user: user) + add_permissions('api_keys.update', to_user: user) end it 'denies a user with a different organisation' do diff --git a/spec/policies/import_policy_spec.rb b/spec/policies/import_policy_spec.rb new file mode 100644 index 000000000..fd9f3172c --- /dev/null +++ b/spec/policies/import_policy_spec.rb @@ -0,0 +1,41 @@ +RSpec.describe ImportPolicy, type: :policy do + + let( :record ){ build_stubbed :import } + before { stub_policy_scope(record) } + + # + # Non Destructive + # --------------- + + context 'Non Destructive actions →' do + permissions :index? do + it_behaves_like 'always allowed', 'anything', archived: true + end + permissions :show? do + it_behaves_like 'always allowed', 'anything', archived: true + end + end + + + # + # Destructive + # ----------- + + context 'Destructive actions →' do + permissions :create? do + it_behaves_like 'permitted policy', 'imports.create', archived: true + end + permissions :destroy? do + it_behaves_like 'permitted policy', 'imports.destroy', archived: true + end + permissions :edit? do + it_behaves_like 'permitted policy', 'imports.update', archived: true + end + permissions :new? do + it_behaves_like 'permitted policy', 'imports.create', archived: true + end + permissions :update? do + it_behaves_like 'permitted policy', 'imports.update', archived: true + end + end +end diff --git a/spec/policies/referential_policy_spec.rb b/spec/policies/referential_policy_spec.rb index 69d0eb17b..d00415fc6 100644 --- a/spec/policies/referential_policy_spec.rb +++ b/spec/policies/referential_policy_spec.rb @@ -9,7 +9,7 @@ RSpec.describe ReferentialPolicy, type: :policy do permissions :create? do it 'permissions present → allowed' do - add_permissions('referentials.create', for_user: user) + add_permissions('referentials.create', to_user: user) expect_it.to permit(user_context, record) end it 'permissions absent → forbidden' do @@ -19,7 +19,7 @@ RSpec.describe ReferentialPolicy, type: :policy do permissions :new? do it 'permissions present → allowed' do - add_permissions('referentials.create', for_user: user) + add_permissions('referentials.create', to_user: user) expect_it.to permit(user_context, record) end it 'permissions absent → forbidden' do @@ -53,7 +53,7 @@ RSpec.describe ReferentialPolicy, type: :policy do context 'permission present →' do before do - add_permissions('referentials.update', for_user: user) + add_permissions('referentials.update', to_user: user) end context 'same organisation →' do @@ -108,7 +108,7 @@ RSpec.describe ReferentialPolicy, type: :policy do context 'permission present →' do before do - add_permissions('referentials.update', for_user: user) + add_permissions('referentials.update', to_user: user) end context 'same organisation →' do diff --git a/spec/support/permissions.rb b/spec/support/permissions.rb index 467c07a32..13666aca3 100644 --- a/spec/support/permissions.rb +++ b/spec/support/permissions.rb @@ -18,6 +18,7 @@ module Support connection_links calendars footnotes + imports journey_patterns referentials routes diff --git a/spec/support/pundit/policies.rb b/spec/support/pundit/policies.rb index d5bb63243..a3489d9db 100644 --- a/spec/support/pundit/policies.rb +++ b/spec/support/pundit/policies.rb @@ -3,18 +3,18 @@ require 'pundit/rspec' module Support module Pundit module Policies - def add_permissions(*permissions, for_user:) - for_user.permissions ||= [] - for_user.permissions += permissions.flatten + def add_permissions(*permissions, to_user:) + to_user.permissions ||= [] + to_user.permissions += permissions.flatten end def create_user_context(user:, referential:) UserContext.new(user, referential: referential) end - def add_permissions(*permissions, for_user:) - for_user.permissions ||= [] - for_user.permissions += permissions.flatten + def remove_permissions(*permissions, from_user:, save: false) + from_user.permissions -= permissions.flatten + from_user.save! if save end end @@ -30,7 +30,7 @@ module Support end def with_user_permission(permission, &blk) it "with user permission #{permission.inspect}" do - add_permissions(permission, for_user: user) + add_permissions(permission, to_user: user) blk.() end end @@ -41,7 +41,7 @@ module Support perms, options = permissions.partition{|x| String === x} context "with permissions #{perms.inspect}...", *options do before do - add_permissions(*permissions, for_user: @user) + add_permissions(*permissions, to_user: @user) end instance_eval(&blk) end @@ -51,6 +51,7 @@ module Support end RSpec.configure do | c | + c.include Support::Pundit::Policies, type: :controller c.include Support::Pundit::Policies, type: :policy c.extend Support::Pundit::PoliciesMacros, type: :policy c.include Support::Pundit::Policies, type: :feature diff --git a/spec/support/pundit/shared_examples.rb b/spec/support/pundit/shared_examples.rb index 63a106759..49c6845da 100644 --- a/spec/support/pundit/shared_examples.rb +++ b/spec/support/pundit/shared_examples.rb @@ -18,7 +18,7 @@ RSpec.shared_examples 'always allowed' do context 'different organisations →' do before do - add_permissions(permission, for_user: user) + add_permissions(permission, to_user: user) end it "allows a user with a different organisation" do expect_it.to permit(user_context, record) @@ -51,7 +51,7 @@ RSpec.shared_examples 'always forbidden' do context 'different organisations →' do before do - add_permissions(permission, for_user: user) + add_permissions(permission, to_user: user) end it "denies a user with a different organisation" do expect_it.not_to permit(user_context, record) @@ -80,7 +80,7 @@ RSpec.shared_examples 'permitted policy and same organisation' do context 'permission present → ' do before do - add_permissions(permission, for_user: user) + add_permissions(permission, to_user: user) end it 'denies a user with a different organisation' do @@ -113,7 +113,7 @@ RSpec.shared_examples 'permitted policy' do context 'permission present → ' do before do - add_permissions(permission, for_user: user) + add_permissions(permission, to_user: user) end it 'allows user' do |
