diff options
Diffstat (limited to 'app')
39 files changed, 418 insertions, 258 deletions
diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass index 7a5323011..2b715d669 100644 --- a/app/assets/stylesheets/components/_forms.sass +++ b/app/assets/stylesheets/components/_forms.sass @@ -53,6 +53,8 @@ input border-right: none &:last-child border-left: none + &[readonly] + background-color: white + span display: table-cell @@ -61,7 +63,7 @@ input border-bottom: 1px solid #ccc box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075) - &.disabled > .form-control + span + &[disabled], &.disabled > .form-control + span background-color: #eee // Validations diff --git a/app/assets/stylesheets/components/_modals.sass b/app/assets/stylesheets/components/_modals.sass index 2db4fe955..e52a2e125 100644 --- a/app/assets/stylesheets/components/_modals.sass +++ b/app/assets/stylesheets/components/_modals.sass @@ -38,6 +38,11 @@ $modalW: 600px .modal-title font-size: $h2-size + display: inline-block + + .modal-close + text-align: right + display: inline-block .modal-body padding: 15px 30px diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass index 8fe7be374..178ec2f36 100644 --- a/app/assets/stylesheets/components/_tables.sass +++ b/app/assets/stylesheets/components/_tables.sass @@ -247,6 +247,16 @@ width: 35px height: 35px margin: 5px + &.with_text + width: initial + > a, > button + border-radius: 4% + text-decoration: none + span + &.fa + padding-left: 10px + span + padding: 0 10px 0 0 > a, > button display: block diff --git a/app/controllers/api/v1/imports_controller.rb b/app/controllers/api/v1/imports_controller.rb index 6050418d8..3d7f4ca79 100644 --- a/app/controllers/api/v1/imports_controller.rb +++ b/app/controllers/api/v1/imports_controller.rb @@ -5,7 +5,11 @@ class Api::V1::ImportsController < Api::V1::IbooController def create args = workbench_import_params.merge(creator: 'Webservice') @import = parent.workbench_imports.create(args) - create! + if @import.valid? + create! + else + render json: { status: "error", messages: @import.errors.full_messages } + end end private diff --git a/app/controllers/compliance_control_sets_controller.rb b/app/controllers/compliance_control_sets_controller.rb index a1c4f19f0..570204065 100644 --- a/app/controllers/compliance_control_sets_controller.rb +++ b/app/controllers/compliance_control_sets_controller.rb @@ -23,6 +23,12 @@ class ComplianceControlSetsController < InheritedResources::Base end end + def clone + ComplianceControlSetCloner.new.copy(params[:id], current_organisation.id) + flash[:notice] = I18n.t("compliance_control_sets.errors.operation_in_progress") + redirect_to(compliance_control_sets_path) + end + protected def begin_of_association_chain @@ -48,4 +54,4 @@ class ComplianceControlSetsController < InheritedResources::Base def compliance_control_set_params params.require(:compliance_control_set).permit(:name, :id) end -end +end
\ No newline at end of file diff --git a/app/controllers/compliance_controls_controller.rb b/app/controllers/compliance_controls_controller.rb index 33eb9cc97..bd4a33ff4 100644 --- a/app/controllers/compliance_controls_controller.rb +++ b/app/controllers/compliance_controls_controller.rb @@ -1,6 +1,7 @@ class ComplianceControlsController < InheritedResources::Base defaults resource_class: ComplianceControl belongs_to :compliance_control_set + actions :all, :except => [:show, :index] def select_type @sti_subclasses = ComplianceControl.subclasses @@ -15,7 +16,6 @@ class ComplianceControlsController < InheritedResources::Base end def create - puts build_resource.inspect create! do |success, failure| success.html { redirect_to compliance_control_set_path(parent) } failure.html { render( :action => 'new' ) } diff --git a/app/decorators/compliance_control_set_decorator.rb b/app/decorators/compliance_control_set_decorator.rb index f4aa607e1..7515316ce 100644 --- a/app/decorators/compliance_control_set_decorator.rb +++ b/app/decorators/compliance_control_set_decorator.rb @@ -4,6 +4,13 @@ class ComplianceControlSetDecorator < Draper::Decorator def action_links links = [] + # if policy.clone? + links << Link.new( + content: h.t('actions.clone'), + href: h.clone_compliance_control_set_path(object.id) + ) + # end + # if h.policy(object).destroy? links << Link.new( content: h.destroy_link_content, diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js index 0c1cb5f5c..8bea5a990 100644 --- a/app/javascript/journey_patterns/actions/index.js +++ b/app/javascript/journey_patterns/actions/index.js @@ -90,7 +90,10 @@ const actions = { resetValidation: (target) => { $(target).parent().removeClass('has-error').children('.help-block').remove() }, - humanOID : (oid) => oid.split(':')[2].split("-").pop(), + humanOID : (oid) => { + let shortOId = oid.split(':')[2].split("-").pop() + return shortOId.length > 10 ? `${shortOId.slice(0, 10)}...` : shortOId + }, validateFields : (fields) => { const test = [] diff --git a/app/javascript/journey_patterns/components/EditModal.js b/app/javascript/journey_patterns/components/EditModal.js index 699f89b85..e7ce24aa1 100644 --- a/app/javascript/journey_patterns/components/EditModal.js +++ b/app/javascript/journey_patterns/components/EditModal.js @@ -13,6 +13,19 @@ export default class EditModal extends Component { } } + renderModalTitle() { + if (this.props.editMode) { + return ( + <h4 className='modal-title'> + Editer la mission + {this.props.modal.type == 'edit' && <em> "{this.props.modal.modalProps.journeyPattern.name}"</em>} + </h4> + ) + } else { + return <h4 className='modal-title'> Informations </h4> + } + } + render() { return ( <div className={ 'modal fade ' + ((this.props.modal.type == 'edit') ? 'in' : '') } id='JourneyPatternModal'> @@ -20,12 +33,8 @@ export default class EditModal extends Component { <div className='modal-dialog'> <div className='modal-content'> <div className='modal-header'> - <h4 className='modal-title'> - Editer la mission - {(this.props.modal.type == 'edit') && ( - <em> "{this.props.modal.modalProps.journeyPattern.name}"</em> - )} - </h4> + {this.renderModalTitle()} + <span type="button" className="close modal-close" data-dismiss="modal">×</span> </div> {(this.props.modal.type == 'edit') && ( @@ -37,6 +46,7 @@ export default class EditModal extends Component { type='text' ref='name' className='form-control' + disabled={!this.props.editMode} id={this.props.modal.modalProps.index} defaultValue={this.props.modal.modalProps.journeyPattern.name} onKeyDown={(e) => actions.resetValidation(e.currentTarget)} @@ -52,6 +62,7 @@ export default class EditModal extends Component { type='text' ref='published_name' className='form-control' + disabled={!this.props.editMode} id={this.props.modal.modalProps.index} defaultValue={this.props.modal.modalProps.journeyPattern.published_name} onKeyDown={(e) => actions.resetValidation(e.currentTarget)} @@ -66,6 +77,7 @@ export default class EditModal extends Component { type='text' ref='registration_number' className='form-control' + disabled={!this.props.editMode} id={this.props.modal.modalProps.index} defaultValue={this.props.modal.modalProps.journeyPattern.registration_number} onKeyDown={(e) => actions.resetValidation(e.currentTarget)} @@ -74,24 +86,26 @@ export default class EditModal extends Component { </div> </div> </div> - - <div className='modal-footer'> - <button - className='btn btn-link' - data-dismiss='modal' - type='button' - onClick={this.props.onModalClose} + { + this.props.editMode && + <div className='modal-footer'> + <button + className='btn btn-link' + data-dismiss='modal' + type='button' + onClick={this.props.onModalClose} > - Annuler - </button> - <button - className='btn btn-primary' - type='button' - onClick={this.handleSubmit.bind(this)} + Annuler + </button> + <button + className='btn btn-primary' + type='button' + onClick={this.handleSubmit.bind(this)} > - Valider - </button> - </div> + Valider + </button> + </div> + } </form> )} </div> diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js index dde73a957..34d102c5d 100644 --- a/app/javascript/journey_patterns/components/JourneyPattern.js +++ b/app/javascript/journey_patterns/components/JourneyPattern.js @@ -56,7 +56,7 @@ export default class JourneyPattern extends Component{ } isDisabled(action) { - return !this.props.status.policy[`journey_patterns.${action}`] && !this.props.editMode + return !this.props.status.policy[`journey_patterns.${action}`] } render() { @@ -88,16 +88,17 @@ export default class JourneyPattern extends Component{ data-toggle='modal' data-target='#JourneyPatternModal' > - Editer + {this.props.editMode ? 'Editer' : 'Consulter'} </button> </li> <li className={this.props.value.object_id ? '' : 'disabled'}> {this.vehicleJourneyURL(this.props.value.object_id)} </li> - <li className={'delete-action' + (this.isDisabled('destroy') ? ' disabled' : '')}> + <li className={'delete-action' + (this.isDisabled('destroy') || !this.props.editMode ? ' disabled' : '')}> <button type='button' - disabled={this.isDisabled('destroy') ? 'disabled' : ''} + className="disabled" + disabled={this.isDisabled('destroy') || !this.props.editMode} onClick={(e) => { e.preventDefault() this.props.onDeleteJourneyPattern(this.props.index)} diff --git a/app/javascript/journey_patterns/containers/Modal.js b/app/javascript/journey_patterns/containers/Modal.js index ace71a857..33ee8583c 100644 --- a/app/javascript/journey_patterns/containers/Modal.js +++ b/app/javascript/journey_patterns/containers/Modal.js @@ -5,6 +5,7 @@ import CreateModal from '../components/CreateModal' const mapStateToProps = (state) => { return { + editMode: state.editMode, modal: state.modal, journeyPattern: state.journeyPattern } diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index 4272c7915..95c739893 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -269,7 +269,10 @@ const actions = { type: 'RECEIVE_TOTAL_COUNT', total }), - humanOID: (oid) => oid.split(':')[2].split("-").pop(), + humanOID: (oid) => { + let shortOId = oid.split(':')[2].split("-").pop() + return shortOId.length > 10 ? `${shortOId.slice(0, 10)}...` : shortOId + }, fetchVehicleJourneys : (dispatch, currentPage, nextPage, queryString) => { if(currentPage == undefined){ currentPage = 1 @@ -458,6 +461,10 @@ const actions = { } } }, + escapeWildcardCharacters(search) { + let newSearch = search.replace(/^_/, "\\_") + return newSearch.replace(/^%/, "\\%") + } } export default actions diff --git a/app/javascript/vehicle_journeys/components/Tools.js b/app/javascript/vehicle_journeys/components/Tools.js index a717408b9..7621dfc10 100644 --- a/app/javascript/vehicle_journeys/components/Tools.js +++ b/app/javascript/vehicle_journeys/components/Tools.js @@ -1,4 +1,4 @@ -import React, { PropTypes } from 'react' +import React, { PropTypes, Component } from 'react' import actions from '../actions' import AddVehicleJourney from '../containers/tools/AddVehicleJourney' import DeleteVehicleJourneys from '../containers/tools/DeleteVehicleJourneys' @@ -8,28 +8,37 @@ import EditVehicleJourney from '../containers/tools/EditVehicleJourney' import NotesEditVehicleJourney from '../containers/tools/NotesEditVehicleJourney' import TimetablesEditVehicleJourney from '../containers/tools/TimetablesEditVehicleJourney' -export default function Tools({vehicleJourneys, onCancelSelection, filters: {policy}, editMode}) { - return ( - <div> - { - (policy['vehicle_journeys.create'] && policy['vehicle_journeys.update'] && policy['vehicle_journeys.destroy'] && editMode) && - <div className='select_toolbox'> - <ul> - <AddVehicleJourney /> - <DuplicateVehicleJourney /> - <ShiftVehicleJourney /> - <EditVehicleJourney /> - <TimetablesEditVehicleJourney /> - <NotesEditVehicleJourney /> - <DeleteVehicleJourneys /> - </ul> - <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span> - <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>Annuler la sélection</button> - </div> - } - </div> - ) +export default class Tools extends Component { + constructor(props) { + super(props) + this.hasPolicy = this.hasPolicy.bind(this) + } + + hasPolicy(key) { + // Check if the user has the policy to disable or not the action + return this.props.filters.policy[`vehicle_journeys.${key}`] + } + + render() { + let { vehicleJourneys, onCancelSelection, editMode } = this.props + return ( + <div className='select_toolbox'> + <ul> + <AddVehicleJourney disabled={this.hasPolicy("create") && !editMode} /> + <DuplicateVehicleJourney disabled={this.hasPolicy("create") && this.hasPolicy("update") && !editMode}/> + <ShiftVehicleJourney disabled={this.hasPolicy("update") && !editMode}/> + <EditVehicleJourney disabled={!this.hasPolicy("update")}/> + <TimetablesEditVehicleJourney disabled={!this.hasPolicy("update")}/> + <NotesEditVehicleJourney disabled={!this.hasPolicy("update")}/> + <DeleteVehicleJourneys disabled={this.hasPolicy("destroy") && !editMode}/> + </ul> + + <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span> + <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>Annuler la sélection</button> + </div> + ) + } } Tools.propTypes = { diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index cb5407f81..13f8eced2 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -44,6 +44,7 @@ export default class VehicleJourney extends Component { render() { this.previousCity = undefined + let {time_tables} = this.props.value return ( <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.errors ? ' has-error': '')}> @@ -51,39 +52,37 @@ export default class VehicleJourney extends Component { <div className='strong mb-xs'>{this.props.value.objectid ? actions.humanOID(this.props.value.objectid) : '-'}</div> <div>{actions.humanOID(this.props.value.journey_pattern.objectid)}</div> <div> - {this.props.value.time_tables.map((tt, i)=> + {time_tables.slice(0,3).map((tt, i)=> <span key={i} className='vj_tt'>{this.timeTableURL(tt)}</span> )} + {time_tables.length > 3 && <span className='vj_tt'> + {time_tables.length - 3}</span>} + </div> + <div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}> + <input + id={this.props.index} + name={this.props.index} + style={{display: 'none'}} + onChange={(e) => this.props.onSelectVehicleJourney(this.props.index)} + type='checkbox' + disabled={this.props.value.deletable} + checked={this.props.value.selected} + ></input> + <label htmlFor={this.props.index}></label> </div> - - {(this.props.filters.policy['vehicle_journeys.update'] == true && this.props.editMode) && - <div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}> - <input - id={this.props.index} - name={this.props.index} - style={{display: 'none'}} - onChange={(e) => this.props.onSelectVehicleJourney(this.props.index)} - type='checkbox' - disabled={this.props.value.deletable} - checked={this.props.value.selected} - ></input> - <label htmlFor={this.props.index}></label> - </div> - } - </div> {this.props.value.vehicle_journey_at_stops.map((vj, i) => <div key={i} className='td text-center'> <div className={'cellwrap' + (this.cityNameChecker(vj) ? ' headlined' : '')}> {this.props.filters.toggleArrivals && <div data-headline='Arrivée à'> - <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false) ? 'disabled ' : '') + 'input-group time'}> + <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}> <input type='number' min='00' max='23' className='form-control' - disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false)} + disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} + readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}} value={vj.arrival_time['hour']} /> @@ -93,7 +92,8 @@ export default class VehicleJourney extends Component { min='00' max='59' className='form-control' - disabled={((this.isDisabled(this.props.value.deletable), vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false)} + disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} + readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false)}} value={vj.arrival_time['minute']} /> @@ -106,13 +106,14 @@ export default class VehicleJourney extends Component { } </div> <div data-headline='Départ à'> - <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false) ? 'disabled ' : '') + 'input-group time'}> + <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}> <input type='number' min='00' max='23' className='form-control' - disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false)} + disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} + readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals)}} value={vj.departure_time['hour']} /> @@ -122,7 +123,8 @@ export default class VehicleJourney extends Component { min='00' max='59' className='form-control' - disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false)} + disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} + readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals)}} value={vj.departure_time['minute']} /> diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js index 5b5e2f849..2bffebdf6 100644 --- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js +++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js @@ -25,7 +25,7 @@ export default class CreateModal extends Component { <li className='st_action'> <button type='button' - disabled={((this.props.filters.policy['vehicle_journeys.update'] == true) ? '' : 'disabled')} + disabled={(this.props.disabled) } data-toggle='modal' data-target='#NewVehicleJourneyModal' onClick={this.props.onOpenCreateModal} @@ -39,6 +39,7 @@ export default class CreateModal extends Component { <div className='modal-content'> <div className='modal-header'> <h4 className='modal-title'>Ajouter une course</h4> + <span type="button" className="close modal-close" data-dismiss="modal">×</span> </div> {(this.props.modal.type == 'create') && ( @@ -127,5 +128,6 @@ CreateModal.propTypes = { onOpenCreateModal: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired, onAddVehicleJourney: PropTypes.func.isRequired, - onSelect2JourneyPattern: PropTypes.func.isRequired + onSelect2JourneyPattern: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired }
\ No newline at end of file diff --git a/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js b/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js index 0a1dedd3c..fc13ae964 100644 --- a/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js @@ -1,12 +1,12 @@ import React, { PropTypes } from 'react' import actions from '../../actions' -export default function DeleteVehicleJourneys({onDeleteVehicleJourneys, vehicleJourneys, filters}) { +export default function DeleteVehicleJourneys({onDeleteVehicleJourneys, vehicleJourneys, disabled}) { return ( <li className='st_action'> <button type='button' - disabled={(actions.getSelected(vehicleJourneys).length > 0 && filters.policy['vehicle_journeys.destroy']) ? '' : 'disabled'} + disabled={(actions.getSelected(vehicleJourneys).length == 0 || disabled)} onClick={e => { e.preventDefault() onDeleteVehicleJourneys() @@ -22,5 +22,5 @@ export default function DeleteVehicleJourneys({onDeleteVehicleJourneys, vehicleJ DeleteVehicleJourneys.propTypes = { onDeleteVehicleJourneys: PropTypes.func.isRequired, vehicleJourneys: PropTypes.array.isRequired, - filters: PropTypes.object.isRequired + disabled: PropTypes.bool.isRequired }
\ No newline at end of file diff --git a/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js index 0c1c81114..8083defb9 100644 --- a/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js @@ -8,6 +8,7 @@ export default class DuplicateVehicleJourney extends Component { this.state = {} this.onFormChange = this.onFormChange.bind(this) this.handleSubmit = this.handleSubmit.bind(this) + this.disableValidateButton = this.disableValidateButton.bind(this) } componentWillReceiveProps() { @@ -58,16 +59,26 @@ export default class DuplicateVehicleJourney extends Component { return vjas.departure_time[type] } + disableValidateButton() { + /* We disable the button in two cases : + - if the additional_time_hh or additional_time_mm are above their input max value + - if if their is no change in the other inputs to avoid making a coping of the selected VJ + */ + let incorrectDT = isNaN(this.state.duplicate_time_hh) || isNaN(this.state.duplicate_time_mm) || this.state.duplicate_time_hh > 23 || this.state.duplicate_time_mm > 59 + let noInputChanges = this.state.additional_time == 0 && this.state.originalDT.hour == this.state.duplicate_time_hh && this.state.originalDT.minute == this.state.duplicate_time_mm + return incorrectDT || noInputChanges + } + render() { if(this.props.status.isFetching == true) { return false } - if(this.props.status.fetchSuccess == true && actions.getSelected(this.props.vehicleJourneys).length > 0) { + if(this.props.status.fetchSuccess == true) { return ( <li className='st_action'> <button type='button' - disabled={((actions.getSelected(this.props.vehicleJourneys).length >= 1 && this.props.filters.policy['vehicle_journeys.update']) ? '' : 'disabled')} + disabled={(actions.getSelected(this.props.vehicleJourneys).length == 0 || this.props.disabled)} data-toggle='modal' data-target='#DuplicateVehicleJourneyModal' onClick={this.props.onOpenDuplicateModal} @@ -83,6 +94,7 @@ export default class DuplicateVehicleJourney extends Component { <h4 className='modal-title'> Dupliquer { actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'plusieurs courses' : 'une course' } </h4> + <span type="button" className="close modal-close" data-dismiss="modal">×</span> </div> {(this.props.modal.type == 'duplicate') && ( @@ -171,6 +183,7 @@ export default class DuplicateVehicleJourney extends Component { className={'btn btn-primary ' + (this.state.additional_time == 0 && this.state.originalDT.hour == this.state.duplicate_time_hh && this.state.originalDT.minute == this.state.duplicate_time_mm ? 'disabled' : '')} type='button' onClick={this.handleSubmit} + disabled={this.disableValidateButton()} > Valider </button> @@ -192,5 +205,5 @@ export default class DuplicateVehicleJourney extends Component { DuplicateVehicleJourney.propTypes = { onOpenDuplicateModal: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired, - filters: PropTypes.object.isRequired + disabled: PropTypes.bool.isRequired }
\ No newline at end of file diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js index 3a4a57024..7ad3cf510 100644 --- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js @@ -32,7 +32,7 @@ export default class EditVehicleJourney extends Component { <li className='st_action'> <button type='button' - disabled={(actions.getSelected(this.props.vehicleJourneys).length == 1 && this.props.filters.policy['vehicle_journeys.update']) ? '' : 'disabled'} + disabled={(actions.getSelected(this.props.vehicleJourneys).length != 1 || this.props.disabled)} data-toggle='modal' data-target='#EditVehicleJourneyModal' onClick={() => this.props.onOpenEditModal(actions.getSelected(this.props.vehicleJourneys)[0])} @@ -46,6 +46,7 @@ export default class EditVehicleJourney extends Component { <div className='modal-content'> <div className='modal-header'> <h4 className='modal-title'>Informations</h4> + <span type="button" className="close modal-close" data-dismiss="modal">×</span> </div> {(this.props.modal.type == 'edit') && ( @@ -59,6 +60,7 @@ export default class EditVehicleJourney extends Component { type='text' ref='published_journey_name' className='form-control' + disabled={!this.props.editMode} defaultValue={this.props.modal.modalProps.vehicleJourney.published_journey_name} onKeyDown={(e) => actions.resetValidation(e.currentTarget)} /> @@ -85,6 +87,7 @@ export default class EditVehicleJourney extends Component { type='text' ref='published_journey_identifier' className='form-control' + disabled={!this.props.editMode} defaultValue={this.props.modal.modalProps.vehicleJourney.published_journey_identifier} onKeyDown={(e) => actions.resetValidation(e.currentTarget)} /> @@ -94,6 +97,7 @@ export default class EditVehicleJourney extends Component { <div className='form-group'> <label className='control-label'>Transporteur</label> <CompanySelect2 + editMode={this.props.editMode} company = {this.props.modal.modalProps.vehicleJourney.company} onSelect2Company = {(e) => this.props.onSelect2Company(e)} onUnselect2Company = {() => this.props.onUnselect2Company()} @@ -127,24 +131,26 @@ export default class EditVehicleJourney extends Component { </div> </div> </div> - - <div className='modal-footer'> - <button - className='btn btn-link' - data-dismiss='modal' - type='button' - onClick={this.props.onModalClose} + { + this.props.editMode && + <div className='modal-footer'> + <button + className='btn btn-link' + data-dismiss='modal' + type='button' + onClick={this.props.onModalClose} > - Annuler + Annuler </button> - <button - className='btn btn-primary' - type='button' - onClick={this.handleSubmit.bind(this)} + <button + className='btn btn-primary' + type='button' + onClick={this.handleSubmit.bind(this)} > - Valider + Valider </button> - </div> + </div> + } </form> )} @@ -163,5 +169,5 @@ export default class EditVehicleJourney extends Component { EditVehicleJourney.propTypes = { onOpenEditModal: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired, - filters: PropTypes.object.isRequired + disabled: PropTypes.bool.isRequired }
\ No newline at end of file diff --git a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js index 1958faf5f..de97bc403 100644 --- a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js @@ -13,21 +13,25 @@ export default class NotesEditVehicleJourney extends Component { $('#NotesEditVehicleJourneyModal').modal('hide') } - renderFootnoteButton(lf, vjArray){ - let footnote_id = undefined - vjArray.forEach((f) => { - if(f.id == lf.id){ - footnote_id = f.id - } - }) + footnotes() { + let { footnotes } = this.props.modal.modalProps.vehicleJourney + let fnIds = footnotes.map(fn => fn.id) + return { + associated: footnotes, + to_associate: window.line_footnotes.filter(fn => !fnIds.includes(fn.id)) + } + } + + renderFootnoteButton(lf) { + if (!this.props.editMode) return false - if(footnote_id){ + if (this.footnotes().associated.includes(lf)) { return <button type='button' className='btn btn-outline-danger btn-xs' onClick={() => this.props.onToggleFootnoteModal(lf, false)} ><span className="fa fa-trash"></span> Retirer</button> - }else{ + } else { return <button type='button' className='btn btn-outline-primary btn-xs' @@ -36,28 +40,64 @@ export default class NotesEditVehicleJourney extends Component { } } - filterFN() { - return _.filter(window.line_footnotes, (lf, i) => { - let bool = true - _.map(this.props.modal.modalProps.vehicleJourney.footnotes, (f, j) => { - if(lf.id === f.id) { - bool = false - } - }) - return bool - }) + renderAssociatedFN() { + if (this.footnotes().associated.length == 0) { + return <h3>Aucune note associée</h3> + } else { + return ( + <div> + <h3>Notes associées :</h3> + {this.footnotes().associated.map((lf, i) => + <div + key={i} + className='panel panel-default' + > + <div className='panel-heading'> + <h4 className='panel-title clearfix'> + <div className='pull-left' style={{ paddingTop: '3px' }}>{lf.code}</div> + <div className='pull-right'>{this.renderFootnoteButton(lf, this.props.modal.modalProps.vehicleJourney.footnotes)}</div> + </h4> + </div> + <div className='panel-body'><p>{lf.label}</p></div> + </div> + )} + </div> + ) + } + } + + renderToAssociateFN() { + if (window.line_footnotes.length == 0) return <h3>La ligne ne possède pas de notes</h3> + + if (this.footnotes().to_associate.length == 0) return false + + return ( + <div> + <h3 className='mt-lg'>Sélectionnez les notes à associer à cette course :</h3> + {this.footnotes().to_associate.map((lf, i) => + <div key={i} className='panel panel-default'> + <div className='panel-heading'> + <h4 className='panel-title clearfix'> + <div className='pull-left' style={{ paddingTop: '3px' }}>{lf.code}</div> + <div className='pull-right'>{this.renderFootnoteButton(lf)}</div> + </h4> + </div> + <div className='panel-body'><p>{lf.label}</p></div> + </div> + )} + </div> + ) } render() { - if(this.props.status.isFetching == true) { - return false - } - if(this.props.status.fetchSuccess == true) { + if (this.props.status.isFetching == true) return false + + if (this.props.status.fetchSuccess == true) { return ( <li className='st_action'> <button type='button' - disabled={(actions.getSelected(this.props.vehicleJourneys).length == 1 && this.props.filters.policy['vehicle_journeys.update']) ? '' : 'disabled'} + disabled={(actions.getSelected(this.props.vehicleJourneys).length != 1 || this.props.disabled)} data-toggle='modal' data-target='#NotesEditVehicleJourneyModal' onClick={() => this.props.onOpenNotesEditModal(actions.getSelected(this.props.vehicleJourneys)[0])} @@ -71,61 +111,35 @@ export default class NotesEditVehicleJourney extends Component { <div className='modal-content'> <div className='modal-header'> <h4 className='modal-title'>Notes</h4> + <span type="button" className="close modal-close" data-dismiss="modal">×</span> </div> {(this.props.modal.type == 'notes_edit') && ( <form> <div className='modal-body'> - <h3>Notes associées</h3> - {(this.props.modal.modalProps.vehicleJourney.footnotes).map((lf, i) => - <div - key={i} - className='panel panel-default' - > - <div className='panel-heading'> - <h4 className='panel-title clearfix'> - <div className='pull-left' style={{paddingTop: '3px'}}>{lf.code}</div> - <div className='pull-right'>{this.renderFootnoteButton(lf, this.props.modal.modalProps.vehicleJourney.footnotes)}</div> - </h4> - </div> - <div className='panel-body'><p>{lf.label}</p></div> - </div> - )} - - <h3 className='mt-lg'>Sélectionnez les notes à associer à cette course :</h3> - {this.filterFN().map((lf, i) => - <div - key={i} - className='panel panel-default' - > - <div className='panel-heading'> - <h4 className='panel-title clearfix'> - <div className='pull-left' style={{paddingTop: '3px'}}>{lf.code}</div> - <div className='pull-right'>{this.renderFootnoteButton(lf, this.props.modal.modalProps.vehicleJourney.footnotes)}</div> - </h4> - </div> - <div className='panel-body'><p>{lf.label}</p></div> - </div> - )} + {this.renderAssociatedFN()} + {this.props.editMode && this.renderToAssociateFN()} </div> - - <div className='modal-footer'> - <button - className='btn btn-link' - data-dismiss='modal' - type='button' - onClick={this.props.onModalClose} + { + this.props.editMode && + <div className='modal-footer'> + <button + className='btn btn-link' + data-dismiss='modal' + type='button' + onClick={this.props.onModalClose} > - Annuler + Annuler </button> - <button - className='btn btn-primary' - type='button' - onClick={this.handleSubmit.bind(this)} + <button + className='btn btn-primary' + type='button' + onClick={this.handleSubmit.bind(this)} > - Valider + Valider </button> - </div> + </div> + } </form> )} @@ -146,5 +160,5 @@ NotesEditVehicleJourney.propTypes = { onModalClose: PropTypes.func.isRequired, onToggleFootnoteModal: PropTypes.func.isRequired, onNotesEditVehicleJourney: PropTypes.func.isRequired, - filters: PropTypes.object.isRequired + disabled: PropTypes.bool.isRequired }
\ No newline at end of file diff --git a/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js index c1e2de779..175106ac5 100644 --- a/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js @@ -34,7 +34,7 @@ export default class ShiftVehicleJourney extends Component { <li className='st_action'> <button type='button' - disabled={(actions.getSelected(this.props.vehicleJourneys).length == 1 && this.props.filters.policy['vehicle_journeys.update']) ? '' : 'disabled'} + disabled={(actions.getSelected(this.props.vehicleJourneys).length > 1 || this.props.disabled)} data-toggle='modal' data-target='#ShiftVehicleJourneyModal' onClick={this.props.onOpenShiftModal} @@ -51,6 +51,7 @@ export default class ShiftVehicleJourney extends Component { {(this.props.modal.type == 'shift') && ( <em>Mettre à jour les horaires de la course {actions.humanOID(actions.getSelected(this.props.vehicleJourneys)[0].objectid)}</em> )} + <span type="button" className="close modal-close" data-dismiss="modal">×</span> </div> {(this.props.modal.type == 'shift') && ( @@ -110,5 +111,5 @@ export default class ShiftVehicleJourney extends Component { ShiftVehicleJourney.propTypes = { onOpenShiftModal: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired, - filters: PropTypes.object.isRequired + disabled: PropTypes.bool.isRequired }
\ No newline at end of file diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js index fd2304901..fef3cdcc9 100644 --- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js @@ -5,6 +5,8 @@ import TimetableSelect2 from './select2s/TimetableSelect2' export default class TimetablesEditVehicleJourney extends Component { constructor(props) { super(props) + this.handleSubmit = this.handleSubmit.bind(this) + this.timeTableURL = this.timeTableURL.bind(this) } handleSubmit() { @@ -13,6 +15,11 @@ export default class TimetablesEditVehicleJourney extends Component { $('#CalendarsEditVehicleJourneyModal').modal('hide') } + timeTableURL(tt) { + let refURL = window.location.pathname.split('/', 3).join('/') + return refURL + '/time_tables/' + tt.id + } + render() { if(this.props.status.isFetching == true) { return false @@ -22,7 +29,7 @@ export default class TimetablesEditVehicleJourney extends Component { <li className='st_action'> <button type='button' - disabled={(actions.getSelected(this.props.vehicleJourneys).length > 0 && this.props.filters.policy['vehicle_journeys.update']) ? '' : 'disabled'} + disabled={(actions.getSelected(this.props.vehicleJourneys).length != 1 || this.props.disabled)} data-toggle='modal' data-target='#CalendarsEditVehicleJourneyModal' onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))} @@ -36,6 +43,7 @@ export default class TimetablesEditVehicleJourney extends Component { <div className='modal-content'> <div className='modal-header'> <h4 className='modal-title'>Calendriers associés</h4> + <span type="button" className="close modal-close" data-dismiss="modal">×</span> </div> {(this.props.modal.type == 'calendars_edit') && ( @@ -57,55 +65,63 @@ export default class TimetablesEditVehicleJourney extends Component { {this.props.modal.modalProps.timetables.map((tt, i) => <div className='nested-fields' key={i}> <div className='wrapper'> - <div>{tt.comment}</div> - <div> - <a - href='#' - title='Supprimer' - className='fa fa-trash remove_fields' - style={{height: 'auto', lineHeight: 'normal'}} - onClick={(e) => { - e.preventDefault() - this.props.onDeleteCalendarModal(tt) - }} + <div> <a href={this.timeTableURL(tt)} target="_blank">{tt.comment}</a> </div> + { + this.props.editMode && + <div> + <a + href='#' + title='Supprimer' + className='fa fa-trash remove_fields' + style={{ height: 'auto', lineHeight: 'normal' }} + onClick={(e) => { + e.preventDefault() + this.props.onDeleteCalendarModal(tt) + }} ></a> - </div> + </div> + } </div> </div> )} - <div className='nested-fields'> - <div className='wrapper'> - <div> - <TimetableSelect2 - onSelect2Timetable={this.props.onSelect2Timetable} - chunkURL={'/autocomplete_time_tables.json'} - isFilter={false} - /> + { + this.props.editMode && + <div className='nested-fields'> + <div className='wrapper'> + <div> + <TimetableSelect2 + onSelect2Timetable={this.props.onSelect2Timetable} + chunkURL={'/autocomplete_time_tables.json'} + isFilter={false} + /> + </div> </div> </div> - </div> + } </div> </div> </div> </div> - - <div className='modal-footer'> - <button - className='btn btn-link' - data-dismiss='modal' - type='button' - onClick={this.props.onModalClose} + { + this.props.editMode && + <div className='modal-footer'> + <button + className='btn btn-link' + data-dismiss='modal' + type='button' + onClick={this.props.onModalClose} > - Annuler - </button> - <button - className='btn btn-primary' - type='button' - onClick={this.handleSubmit.bind(this)} + Annuler + </button> + <button + className='btn btn-primary' + type='button' + onClick={this.handleSubmit} > - Valider - </button> - </div> + Valider + </button> + </div> + } </form> )} @@ -127,5 +143,5 @@ TimetablesEditVehicleJourney.propTypes = { onTimetablesEditVehicleJourney: PropTypes.func.isRequired, onDeleteCalendarModal: PropTypes.func.isRequired, onSelect2Timetable: PropTypes.func.isRequired, - filters: PropTypes.object.isRequired + disabled: PropTypes.bool.isRequired }
\ No newline at end of file diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js index 9c259630d..0697e9141 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js @@ -1,6 +1,7 @@ import _ from 'lodash' import React, { PropTypes, Component } from 'react' import Select2 from 'react-select2' +import actions from '../../../actions' // get JSON full path let origin = window.location.origin @@ -20,10 +21,11 @@ export default class BSelect4 extends Component { value={(this.props.company) ? this.props.company.name : undefined} onSelect={(e) => this.props.onSelect2Company(e) } onUnselect={() => this.props.onUnselect2Company()} + disabled={!this.props.editMode} multiple={false} ref='company_id' options={{ - allowClear: true, + allowClear: this.props.editMode, theme: 'bootstrap', width: '100%', placeholder: 'Filtrer par transporteur...', @@ -34,7 +36,7 @@ export default class BSelect4 extends Component { delay: '500', data: function(params) { return { - q: {name_cont: params.term}, + q: { name_cont: actions.escapeWildcardCharacters(params.term)}, }; }, processResults: function(data, params) { diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js index e4abdd651..6069bf089 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js @@ -33,7 +33,7 @@ export default class BSelect4 extends Component { delay: '500', data: function(params) { return { - q: {published_name_or_objectid_or_registration_number_cont: params.term}, + q: { published_name_or_objectid_or_registration_number_cont: actions.escapeWildcardCharacters(params.term)}, }; }, processResults: function(data, params) { diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js index 606bf8511..60c3eab83 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js @@ -32,12 +32,9 @@ export default class BSelect4 extends Component { dataType: 'json', delay: '500', data: function(params) { - let newParmas = params.term.split(" ") return { q: { - objectid_cont_any: newParmas, - comment_cont_any: newParmas, - m: 'or' + comment_or_objectid_cont_any: actions.escapeWildcardCharacters(params.term) } }; }, diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js index e1af8816d..7cccbbc05 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js @@ -33,7 +33,7 @@ export default class BSelect4b extends Component { delay: '500', data: function(params) { return { - q: {objectid_cont: params.term}, + q: { objectid_cont: actions.escapeWildcardCharacters(params.term)}, }; }, processResults: function(data, params) { diff --git a/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js index b3f777448..5da0bd3e9 100644 --- a/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js +++ b/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js @@ -2,13 +2,13 @@ import actions from '../../actions' import { connect } from 'react-redux' import CreateModal from '../../components/tools/CreateModal' -const mapStateToProps = (state) => { +const mapStateToProps = (state, ownProps) => { return { + disabled: ownProps.disabled, modal: state.modal, vehicleJourneys: state.vehicleJourneys, status: state.status, stopPointsList: state.stopPointsList, - filters: state.filters } } diff --git a/app/javascript/vehicle_journeys/containers/tools/DeleteVehicleJourneys.js b/app/javascript/vehicle_journeys/containers/tools/DeleteVehicleJourneys.js index d7d315da4..95f2eb506 100644 --- a/app/javascript/vehicle_journeys/containers/tools/DeleteVehicleJourneys.js +++ b/app/javascript/vehicle_journeys/containers/tools/DeleteVehicleJourneys.js @@ -2,10 +2,10 @@ import actions from '../../actions' import { connect } from 'react-redux' import DeleteVJComponent from '../../components/tools/DeleteVehicleJourneys' -const mapStateToProps = (state) => { +const mapStateToProps = (state, ownProps) => { return { - vehicleJourneys: state.vehicleJourneys, - filters: state.filters + disabled: ownProps.disabled, + vehicleJourneys: state.vehicleJourneys } } diff --git a/app/javascript/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js index e9ca88040..7b23a06dc 100644 --- a/app/javascript/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js +++ b/app/javascript/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js @@ -2,8 +2,9 @@ import actions from '../../actions' import { connect } from 'react-redux' import DuplicateVJComponent from '../../components/tools/DuplicateVehicleJourney' -const mapStateToProps = (state) => { +const mapStateToProps = (state, ownProps) => { return { + disabled: ownProps.disabled, modal: state.modal, vehicleJourneys: state.vehicleJourneys, status: state.status, diff --git a/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js index 2d480aa0c..c2eabcc10 100644 --- a/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js @@ -2,12 +2,13 @@ import actions from '../../actions' import { connect } from 'react-redux' import EditComponent from '../../components/tools/EditVehicleJourney' -const mapStateToProps = (state) => { +const mapStateToProps = (state, ownProps) => { return { + editMode: state.editMode, + disabled: ownProps.disabled, modal: state.modal, vehicleJourneys: state.vehicleJourneys, - status: state.status, - filters: state.filters + status: state.status } } diff --git a/app/javascript/vehicle_journeys/containers/tools/NotesEditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/NotesEditVehicleJourney.js index 5a96ff273..6290ae3bf 100644 --- a/app/javascript/vehicle_journeys/containers/tools/NotesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/containers/tools/NotesEditVehicleJourney.js @@ -2,12 +2,13 @@ import actions from '../../actions' import { connect } from 'react-redux' import NotesEditComponent from '../../components/tools/NotesEditVehicleJourney' -const mapStateToProps = (state) => { +const mapStateToProps = (state, ownProps) => { return { + editMode: state.editMode, + disabled: ownProps.disabled, modal: state.modal, vehicleJourneys: state.vehicleJourneys, - status: state.status, - filters: state.filters + status: state.status } } diff --git a/app/javascript/vehicle_journeys/containers/tools/ShiftVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/ShiftVehicleJourney.js index a4b4fbe39..abd7dd145 100644 --- a/app/javascript/vehicle_journeys/containers/tools/ShiftVehicleJourney.js +++ b/app/javascript/vehicle_journeys/containers/tools/ShiftVehicleJourney.js @@ -2,12 +2,12 @@ import actions from '../../actions' import { connect } from 'react-redux' import ShiftVJComponent from '../../components/tools/ShiftVehicleJourney' -const mapStateToProps = (state) => { +const mapStateToProps = (state, ownProps) => { return { modal: state.modal, vehicleJourneys: state.vehicleJourneys, status: state.status, - filters: state.filters + disabled: ownProps.disabled } } diff --git a/app/javascript/vehicle_journeys/containers/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/TimetablesEditVehicleJourney.js index 62150a06e..b4ba9d068 100644 --- a/app/javascript/vehicle_journeys/containers/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/containers/tools/TimetablesEditVehicleJourney.js @@ -2,12 +2,13 @@ import actions from '../../actions' import { connect } from 'react-redux' import TimetablesEditComponent from '../../components/tools/TimetablesEditVehicleJourney' -const mapStateToProps = (state) => { +const mapStateToProps = (state, ownProps) => { return { + editMode: state.editMode, modal: state.modal, vehicleJourneys: state.vehicleJourneys, status: state.status, - filters: state.filters + disabled: ownProps.disabled } } diff --git a/app/models/import.rb b/app/models/import.rb index 64f713914..e0aae6ef1 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -18,6 +18,7 @@ class Import < ActiveRecord::Base validates :file, presence: true validates_presence_of :workbench, :creator + validates_format_of :file, with: %r{\.zip\z}i, message: I18n.t('activerecord.errors.models.imports.wrong_file_extension') before_create :initialize_fields @@ -34,7 +35,7 @@ class Import < ActiveRecord::Base end def self.finished_statuses - symbols_with_indifferent_access(%i(successful failed aborted canceled)) + symbols_with_indifferent_access(%i(successful failed warning aborted canceled)) end def notify_parent diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb index cab301b01..7a4bdad1b 100644 --- a/app/services/zip_service.rb +++ b/app/services/zip_service.rb @@ -1,10 +1,9 @@ class ZipService - # TODO: Remove me before merge https://github.com/rubyzip/rubyzip - class Subdir < Struct.new(:name, :stream) + class Subdir < Struct.new(:name, :stream, :spurious) end - attr_reader :current_key, :current_output, :yielder + attr_reader :current_key, :current_output, :current_spurious, :yielder def initialize data @zip_data = StringIO.new(data) @@ -36,6 +35,7 @@ class ZipService end def add_to_current_output entry + return if is_spurious! entry.name current_output.put_next_entry entry.name write_to_current_output entry.get_input_stream end @@ -51,7 +51,8 @@ class ZipService @yielder << Subdir.new( current_key, # Second part of the solution, yield the closed stream - current_output.close_buffer) + current_output.close_buffer, + current_spurious) end end @@ -59,10 +60,19 @@ class ZipService @current_key = entry_key # First piece of the solution, use internal way to create a Zip::OutputStream @current_output = Zip::OutputStream.new(StringIO.new(''), true, nil) + @current_spurious = [] end def entry_key entry # last dir name File.dirname.split("/").last entry.name.split('/', -1)[-2] end + + def is_spurious! entry_name + segments = entry_name.split('/', 3) + return false if segments.size < 3 + + current_spurious << segments.second + return true + end end diff --git a/app/views/compliance_control_sets/show.html.slim b/app/views/compliance_control_sets/show.html.slim index 4bb6b9c77..cf236feb8 100644 --- a/app/views/compliance_control_sets/show.html.slim +++ b/app/views/compliance_control_sets/show.html.slim @@ -97,9 +97,13 @@ cls: 'table has-filter has-search' .select_toolbox ul - li.st_action + li.st_action.with_text = link_to select_type_compliance_control_set_compliance_controls_path(@compliance_control_set.id) span.fa.fa-plus - li.st_action + span + = t('compliance_control_sets.actions.add_compliance_control') + li.st_action.with_text = link_to new_compliance_control_set_compliance_control_block_path(@compliance_control_set.id) - span.fa.fa-plus-square + span.fa.fa-plus + span + = t('compliance_control_sets.actions.add_compliance_control_block') diff --git a/app/views/compliance_controls/show.html.slim b/app/views/compliance_controls/show.html.slim index a123d1887..44d52a9f1 100644 --- a/app/views/compliance_controls/show.html.slim +++ b/app/views/compliance_controls/show.html.slim @@ -1,9 +1,9 @@ - breadcrumb :compliance_control, @compliance_control / PageHeader -- header_params = ['jeux-de-controle', += pageheader 'jeux-de-controle', t('compliance_controls.show.title'), - ''] -= pageheader(*header_params) do + '', + link_to(t('actions.edit'), edit_compliance_control_set_compliance_control_path(params[:compliance_control_set_id], params[:id]), class: 'btn btn-default') do / PageContent .page_content diff --git a/app/views/imports/_form.html.slim b/app/views/imports/_form.html.slim index 0fbf578be..95d97c534 100644 --- a/app/views/imports/_form.html.slim +++ b/app/views/imports/_form.html.slim @@ -9,6 +9,6 @@ .form-group = form.label :file, t('activerecord.attributes.import.resources'), class: 'control-label col-sm-4 col-xs-5' .col-sm-8.col-xs-7 - = form.input_field :file, label: false, class: 'form-control' + = form.input :file, label: false, class: 'form-control' = form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'wb_import_form' diff --git a/app/views/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim index 3142ecd5b..f3cd01f46 100644 --- a/app/views/stif/dashboards/_dashboard.html.slim +++ b/app/views/stif/dashboards/_dashboard.html.slim @@ -39,14 +39,14 @@ h3.panel-title.with_actions div = t('.referentials') - span.badge.ml-xs = @referentials.count if @referentials.present? + span.badge.ml-xs = @dashboard.referentials.count if @dashboard.referentials.present? div = link_to '', workbench_path(@dashboard.workbench), class: ' fa fa-chevron-right pull-right', title: t('.see') - - if @referentials.present? + - if @dashboard.referentials.present? .list-group - - @referentials.each_with_index do |referential, i| + - @dashboard.referentials.first(5).each_with_index do |referential, i| = link_to referential.name, referential_path(referential, workbench_id: referential.workbench_id, current_workbench_id: @dashboard.workbench.id), class: 'list-group-item' if i < 6 - else @@ -65,7 +65,7 @@ - if @dashboard.calendars.present? .list-group - - @dashboard.calendars.each_with_index do |calendar, i| + - @dashboard.calendars.first(5).each_with_index do |calendar, i| = link_to calendar.name, calendar_path(calendar), class: 'list-group-item' if i < 6 - else diff --git a/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb index 994493944..300fad9e2 100644 --- a/app/workers/workbench_import_worker.rb +++ b/app/workers/workbench_import_worker.rb @@ -14,11 +14,13 @@ class WorkbenchImportWorker zip_service = ZipService.new(downloaded) upload zip_service @workbench_import.update(ended_at: Time.now) + rescue Zip::Error + handle_corrupt_zip_file end def download logger.info "HTTP GET #{import_url}" - @zipfile_data = HTTPService.get_resource( + HTTPService.get_resource( host: import_host, path: import_path, params: {token: @workbench_import.token_download}).body @@ -32,6 +34,10 @@ class WorkbenchImportWorker params: params(eg_file, eg_name)) end + def handle_corrupt_zip_file + @workbench_import.messages.create(criticity: :error, message_key: 'corrupt_zip_file', message_attributes: {import_name: @workbench_import.name}) + end + def upload zip_service entry_group_streams = zip_service.subdirs @workbench_import.update total_steps: entry_group_streams.size @@ -42,11 +48,24 @@ class WorkbenchImportWorker raise end - def upload_entry_group entry_pair, element_count - @workbench_import.update( current_step: element_count.succ ) - # status = retry_service.execute(&upload_entry_group_proc(entry_pair)) - eg_name = entry_pair.name - eg_stream = entry_pair.stream + def update_object_state entry, count + @workbench_import.update( current_step: count ) + unless entry.spurious.empty? + @workbench_import.messages.create( + criticity: :warning, + message_key: 'inconsistent_zip_file', + message_attributes: { + 'import_name' => @workbench_import.name, + 'spurious_dirs' => entry.spurious.join(', ') + }) + end + end + + def upload_entry_group entry, element_count + update_object_state entry, element_count.succ + # status = retry_service.execute(&upload_entry_group_proc(entry)) + eg_name = entry.name + eg_stream = entry.stream FileUtils.mkdir_p(Rails.root.join('tmp', 'imports')) |
