diff options
| author | cedricnjanga | 2017-10-06 10:17:17 +0200 | 
|---|---|---|
| committer | cedricnjanga | 2017-10-06 10:17:17 +0200 | 
| commit | b6f08e58fae35d5dd8a610af31c2950b37746695 (patch) | |
| tree | 989843dd674c41ff73eb75bd630ce4cc91fff91b /app/javascript/journey_patterns | |
| parent | 08517c27551a2dd8b227f6662f4c41574a36d81e (diff) | |
| download | chouette-core-b6f08e58fae35d5dd8a610af31c2950b37746695.tar.bz2 | |
Add webpacker gem and migrate the React apps
Diffstat (limited to 'app/javascript/journey_patterns')
22 files changed, 1289 insertions, 0 deletions
| diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js new file mode 100644 index 000000000..0c1cb5f5c --- /dev/null +++ b/app/javascript/journey_patterns/actions/index.js @@ -0,0 +1,220 @@ +import Promise from 'promise-polyfill' + +// To add to window +if (!window.Promise) { +  window.Promise = Promise; +} + +const actions = { +  enterEditMode: () => ({ +    type: "ENTER_EDIT_MODE" +  }), +  exitEditMode: () => ({ +    type: "EXIT_EDIT_MODE" +  }), +  receiveJourneyPatterns : (json) => ({ +    type: "RECEIVE_JOURNEY_PATTERNS", +    json +  }), +  receiveErrors : (json) => ({ +    type: "RECEIVE_ERRORS", +    json +  }), +  unavailableServer : () => ({ +    type: 'UNAVAILABLE_SERVER' +  }), +  goToPreviousPage : (dispatch, pagination) => ({ +    type: 'GO_TO_PREVIOUS_PAGE', +    dispatch, +    pagination, +    nextPage : false +  }), +  goToNextPage : (dispatch, pagination) => ({ +    type: 'GO_TO_NEXT_PAGE', +    dispatch, +    pagination, +    nextPage : true +  }), +  updateCheckboxValue : (e, index) => ({ +    type : 'UPDATE_CHECKBOX_VALUE', +    id : e.currentTarget.id, +    index +  }), +  checkConfirmModal : (event, callback, stateChanged,dispatch) => { +    if(stateChanged === true){ +      return actions.openConfirmModal(callback) +    }else{ +      dispatch(actions.fetchingApi()) +      return callback +    } +  }, +  openConfirmModal : (callback) => ({ +    type : 'OPEN_CONFIRM_MODAL', +    callback +  }), +  openEditModal : (index, journeyPattern) => ({ +    type : 'EDIT_JOURNEYPATTERN_MODAL', +    index, +    journeyPattern +  }), +  openCreateModal : () => ({ +    type : 'CREATE_JOURNEYPATTERN_MODAL' +  }), +  deleteJourneyPattern : (index) => ({ +    type : 'DELETE_JOURNEYPATTERN', +    index, +  }), +  closeModal : () => ({ +    type : 'CLOSE_MODAL' +  }), +  saveModal : (index, data) => ({ +    type: 'SAVE_MODAL', +    data, +    index +  }), +  addJourneyPattern : (data) => ({ +    type: 'ADD_JOURNEYPATTERN', +    data, +  }), +  savePage : (dispatch, currentPage) => ({ +    type: 'SAVE_PAGE', +    dispatch +  }), +  updateTotalCount: (diff) => ({ +    type: 'UPDATE_TOTAL_COUNT', +    diff +  }), +  fetchingApi: () =>({ +      type: 'FETCH_API' +  }), +  resetValidation: (target) => { +    $(target).parent().removeClass('has-error').children('.help-block').remove() +  }, +  humanOID : (oid) => oid.split(':')[2].split("-").pop(), +  validateFields : (fields) => { +    const test = [] + +    Object.keys(fields).map(function(key) { +      test.push(fields[key].validity.valid) +    }) +    if(test.indexOf(false) >= 0) { +      // Form is invalid +      test.map(function(item, i) { +        if(item == false) { +          const k = Object.keys(fields)[i] +          $(fields[k]).parent().addClass('has-error').children('.help-block').remove() +          $(fields[k]).parent().append("<span class='small help-block'>" + fields[k].validationMessage + "</span>") +        } +      }) +      return false +    } else { +      // Form is valid +      return true +    } +  }, +  submitJourneyPattern : (dispatch, state, next) => { +    dispatch(actions.fetchingApi()) +    let urlJSON = window.location.pathname + ".json" +    let hasError = false +    fetch(urlJSON, { +      credentials: 'same-origin', +      method: 'PATCH', +      contentType: 'application/json; charset=utf-8', +      Accept: 'application/json', +      body: JSON.stringify(state), +      headers: { +        'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') +      } +    }).then(response => { +        if(!response.ok) { +          hasError = true +        } +        return response.json() +      }).then((json) => { +        if(hasError == true) { +          dispatch(actions.receiveErrors(json)) +        } else { +          if(next) { +            dispatch(next) +          } else { +            if(json.length != window.currentItemsLength){ +              dispatch(actions.updateTotalCount(window.currentItemsLength - json.length)) +            } +            window.currentItemsLength = json.length +            dispatch(actions.exitEditMode()) +            dispatch(actions.receiveJourneyPatterns(json)) +          } +        } +      }) +  }, +  fetchJourneyPatterns : (dispatch, currentPage, nextPage) => { +    if(currentPage == undefined){ +      currentPage = 1 +    } +    let journeyPatterns = [] +    let page + +    switch (nextPage) { +      case true: +        page = currentPage + 1 +        break +      case false: +        if(currentPage > 1){ +          page = currentPage - 1 +        } +        break +      default: +        page = currentPage +        break +    } +    let str = ".json" +    if(page > 1){ +      str = '.json?page=' + page.toString() +    } +    let urlJSON = window.location.pathname + str +    let hasError = false +    fetch(urlJSON, { +      credentials: 'same-origin', +    }).then(response => { +        if(response.status == 500) { +          hasError = true +        } +        return response.json() +      }).then((json) => { +        if(hasError == true) { +          dispatch(actions.unavailableServer()) +        } else { +          if(json.length != 0){ +            let val +            for (val of json){ +              for (let stop_point of val.route_short_description.stop_points){ +                stop_point.checked = false +                val.stop_area_short_descriptions.map((element) => { +                  if(element.stop_area_short_description.id === stop_point.id){ +                    stop_point.checked = true +                  } +                }) +              } +              journeyPatterns.push({ +                name: val.name, +                object_id: val.object_id, +                published_name: val.published_name, +                registration_number: val.registration_number, +                stop_points: val.route_short_description.stop_points, +                deletable: false +              }) +            } +          } +          window.currentItemsLength = journeyPatterns.length +          dispatch(actions.receiveJourneyPatterns(journeyPatterns)) +        } +      }) +  }, +  getChecked : (jp) => { +    return jp.filter((obj) => { +      return obj.checked +    }) +  } +} + +export default actions
\ No newline at end of file diff --git a/app/javascript/journey_patterns/components/App.js b/app/javascript/journey_patterns/components/App.js new file mode 100644 index 000000000..ac6214cc1 --- /dev/null +++ b/app/javascript/journey_patterns/components/App.js @@ -0,0 +1,21 @@ +import React from 'react' +import AddJourneyPattern from '../containers/AddJourneyPattern' +import Navigate from '../containers/Navigate' +import Modal from '../containers/Modal' +import ConfirmModal from '../containers/ConfirmModal' +import SaveJourneyPattern from '../containers/SaveJourneyPattern' +import JourneyPatternList from '../containers/JourneyPatternList' + +const App = () => ( +  <div> +    <Navigate /> +    <JourneyPatternList /> +    <Navigate /> +    <AddJourneyPattern /> +    <SaveJourneyPattern /> +    <ConfirmModal /> +    <Modal/> +  </div> +) + +export default App diff --git a/app/javascript/journey_patterns/components/ConfirmModal.js b/app/javascript/journey_patterns/components/ConfirmModal.js new file mode 100644 index 000000000..2cc1bef44 --- /dev/null +++ b/app/javascript/journey_patterns/components/ConfirmModal.js @@ -0,0 +1,46 @@ +import React, { PropTypes } from 'react' + +export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, journeyPatterns}) { +  return ( +    <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> +            </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> +              </div> +            </div> +            <div className='modal-footer'> +              <button +                className='btn btn-link' +                data-dismiss='modal' +                type='button' +                onClick={() => { onModalCancel(modal.confirmModal.callback) }} +              > +                Ne pas valider +            </button> +              <button +                className='btn btn-primary' +                data-dismiss='modal' +                type='button' +                onClick={() => { onModalAccept(modal.confirmModal.callback, journeyPatterns) }} +              > +                Valider +            </button> +            </div> +          </div> +        </div> +      </div> +    </div> +  ) +} + +ConfirmModal.propTypes = { +  modal: PropTypes.object.isRequired, +  onModalAccept: PropTypes.func.isRequired, +  onModalCancel: PropTypes.func.isRequired +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/components/CreateModal.js b/app/javascript/journey_patterns/components/CreateModal.js new file mode 100644 index 000000000..d0eff6e57 --- /dev/null +++ b/app/javascript/journey_patterns/components/CreateModal.js @@ -0,0 +1,122 @@ +import React, { PropTypes, Component } from 'react' +import actions from '../actions' + +export default class CreateModal extends Component { +  constructor(props) { +    super(props) +  } + +  handleSubmit() { +    if(actions.validateFields(this.refs) == true) { +      this.props.onAddJourneyPattern(this.refs) +      this.props.onModalClose() +      $('#NewJourneyPatternModal').modal('hide') +    } +  } + +  render() { +    if(this.props.status.isFetching == true || this.props.status.policy['journey_patterns.create'] == false || this.props.editMode == false) { +      return false +    } +    if(this.props.status.fetchSuccess == true) { +      return ( +        <div className="select_toolbox"> +          <ul> +            <li className='st_action'> +              <button +                type='button' +                data-toggle='modal' +                data-target='#NewJourneyPatternModal' +                onClick={this.props.onOpenCreateModal} +                > +                <span className="fa fa-plus"></span> +              </button> + +              <div className={ 'modal fade ' + ((this.props.modal.type == 'create') ? 'in' : '') } id='NewJourneyPatternModal'> +                <div className='modal-container'> +                  <div className='modal-dialog'> +                    <div className='modal-content'> +                      <div className='modal-header'> +                        <h4 className='modal-title'>Ajouter une mission</h4> +                      </div> + +                      {(this.props.modal.type == 'create') && ( +                        <form> +                          <div className='modal-body'> +                            <div className='form-group'> +                              <label className='control-label is-required'>Nom</label> +                              <input +                                type='text' +                                ref='name' +                                className='form-control' +                                onKeyDown={(e) => actions.resetValidation(e.currentTarget)} +                                required +                                /> +                            </div> +                            <div className='row'> +                              <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'> +                                <div className='form-group'> +                                  <label className='control-label is-required'>Nom public</label> +                                  <input +                                    type='text' +                                    ref='published_name' +                                    className='form-control' +                                    onKeyDown={(e) => actions.resetValidation(e.currentTarget)} +                                    required +                                    /> +                                </div> +                              </div> +                              <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'> +                                <div className='form-group'> +                                  <label className='control-label'>Code mission</label> +                                  <input +                                    type='text' +                                    ref='registration_number' +                                    className='form-control' +                                    onKeyDown={(e) => actions.resetValidation(e.currentTarget)} +                                    /> +                                </div> +                              </div> +                            </div> +                          </div> +                          <div className='modal-footer'> +                            <button +                              className='btn btn-link' +                              data-dismiss='modal' +                              type='button' +                              onClick={this.props.onModalClose} +                              > +                              Annuler +                            </button> +                            <button +                              className='btn btn-primary' +                              type='button' +                              onClick={this.handleSubmit.bind(this)} +                              > +                              Valider +                            </button> +                          </div> +                        </form> +                      )} +                    </div> +                  </div> +                </div> +              </div> +            </li> +          </ul> +        </div> +      ) +    } else { +      return false +    } +  } +} + +CreateModal.propTypes = { +  index: PropTypes.number, +  modal: PropTypes.object.isRequired, +  status: PropTypes.object.isRequired, +  onOpenCreateModal: PropTypes.func.isRequired, +  onModalClose: PropTypes.func.isRequired, +  onAddJourneyPattern: PropTypes.func.isRequired +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/components/EditModal.js b/app/javascript/journey_patterns/components/EditModal.js new file mode 100644 index 000000000..699f89b85 --- /dev/null +++ b/app/javascript/journey_patterns/components/EditModal.js @@ -0,0 +1,110 @@ +import React, { PropTypes, Component } from 'react' +import actions from '../actions' + +export default class EditModal extends Component { +  constructor(props) { +    super(props) +  } + +  handleSubmit() { +    if(actions.validateFields(this.refs) == true) { +      this.props.saveModal(this.props.modal.modalProps.index, this.refs) +      $('#JourneyPatternModal').modal('hide') +    } +  } + +  render() { +    return ( +      <div className={ 'modal fade ' + ((this.props.modal.type == 'edit') ? 'in' : '') } id='JourneyPatternModal'> +        <div className='modal-container'> +          <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> +              </div> + +              {(this.props.modal.type == 'edit') && ( +                <form> +                  <div className='modal-body'> +                    <div className='form-group'> +                      <label className='control-label is-required'>Nom</label> +                      <input +                        type='text' +                        ref='name' +                        className='form-control' +                        id={this.props.modal.modalProps.index} +                        defaultValue={this.props.modal.modalProps.journeyPattern.name} +                        onKeyDown={(e) => actions.resetValidation(e.currentTarget)} +                        required +                        /> +                    </div> + +                    <div className='row'> +                      <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'> +                        <div className='form-group'> +                          <label className='control-label is-required'>Nom public</label> +                          <input +                            type='text' +                            ref='published_name' +                            className='form-control' +                            id={this.props.modal.modalProps.index} +                            defaultValue={this.props.modal.modalProps.journeyPattern.published_name} +                            onKeyDown={(e) => actions.resetValidation(e.currentTarget)} +                            required +                            /> +                        </div> +                      </div> +                      <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'> +                        <div className='form-group'> +                          <label className='control-label'>Code mission</label> +                          <input +                            type='text' +                            ref='registration_number' +                            className='form-control' +                            id={this.props.modal.modalProps.index} +                            defaultValue={this.props.modal.modalProps.journeyPattern.registration_number} +                            onKeyDown={(e) => actions.resetValidation(e.currentTarget)} +                            /> +                        </div> +                      </div> +                    </div> +                  </div> + +                  <div className='modal-footer'> +                    <button +                      className='btn btn-link' +                      data-dismiss='modal' +                      type='button' +                      onClick={this.props.onModalClose} +                      > +                      Annuler +                    </button> +                    <button +                      className='btn btn-primary' +                      type='button' +                      onClick={this.handleSubmit.bind(this)} +                      > +                      Valider +                    </button> +                  </div> +                </form> +              )} +            </div> +          </div> +        </div> +      </div> +    ) +  } +} + +EditModal.propTypes = { +  index: PropTypes.number, +  modal: PropTypes.object, +  onModalClose: PropTypes.func.isRequired, +  saveModal: PropTypes.func.isRequired +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js new file mode 100644 index 000000000..dde73a957 --- /dev/null +++ b/app/javascript/journey_patterns/components/JourneyPattern.js @@ -0,0 +1,131 @@ +import React, { PropTypes, Component } from 'react' +import actions from '../actions' + +export default class JourneyPattern extends Component{ +  constructor(props){ +    super(props) +    this.previousCity = undefined +  } + +  vehicleJourneyURL(jpOid) { +    let routeURL = window.location.pathname.split('/', 7).join('/') +    let vjURL = routeURL + '/vehicle_journeys?jp=' + jpOid + +    return ( +      <a href={vjURL}>Horaires des courses</a> +    ) +  } + +  cityNameChecker(sp) { +    let bool = false +    if(sp.city_name != this.previousCity){ +      bool = true +      this.previousCity = sp.city_name +    } +    return ( +      <div +        className={(bool) ? 'headlined' : ''} +      > +        <span className='has_radio'> +          <input +            onChange = {(e) => this.props.onCheckboxChange(e)} +            type='checkbox' +            id={sp.id} +            checked={sp.checked} +            disabled={(this.props.value.deletable || this.props.status.policy['journey_patterns.update'] == false || this.props.editMode == false) ? 'disabled' : ''} +            > +          </input> +          <span className='radio-label'></span> +        </span> +      </div> +    ) +  } + +  getErrors(errors) { +    let err = Object.keys(errors).map((key, index) => { +      return ( +        <li key={index} style={{listStyleType: 'disc'}}> +          <strong>{key}</strong> { errors[key] } +        </li> +      ) +    }) + +    return ( +      <ul className="alert alert-danger">{err}</ul> +    ) +  } + +  isDisabled(action) { +    return !this.props.status.policy[`journey_patterns.${action}`] && !this.props.editMode +  } + +  render() { +    this.previousCity = undefined + +    return ( +      <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '')}> +        {/* Errors */} +        {/* this.props.value.errors ? this.getErrors(this.props.value.errors) : '' */} + +        <div className='th'> +          <div className='strong mb-xs'>{this.props.value.object_id ? actions.humanOID(this.props.value.object_id) : '-'}</div> +          <div>{this.props.value.registration_number}</div> +          <div>{actions.getChecked(this.props.value.stop_points).length} arrêt(s)</div> + +          <div className={this.props.value.deletable ? 'btn-group disabled' : 'btn-group'}> +            <div +              className={this.props.value.deletable ? 'btn dropdown-toggle disabled' : 'btn dropdown-toggle'} +              data-toggle='dropdown' +              > +              <span className='fa fa-cog'></span> +            </div> +            <ul className='dropdown-menu'> +              <li className={this.isDisabled('update') ? 'disabled' : ''}> +                <button +                  type='button' +                  disabled={this.isDisabled('update')} +                  onClick={this.props.onOpenEditModal} +                  data-toggle='modal' +                  data-target='#JourneyPatternModal' +                  > +                  Editer +                </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' : '')}> +                <button +                  type='button' +                  disabled={this.isDisabled('destroy') ? 'disabled' : ''} +                  onClick={(e) => { +                    e.preventDefault() +                    this.props.onDeleteJourneyPattern(this.props.index)} +                  } +                  > +                    <span className='fa fa-trash'></span>Supprimer +                  </button> +                </li> +              </ul> +            </div> +          </div> + +          {this.props.value.stop_points.map((stopPoint, i) =>{ +            return ( +              <div key={i} className='td'> +                {this.cityNameChecker(stopPoint)} +              </div> +            ) +          })} +        </div> +      ) +  } +} + +JourneyPattern.propTypes = { +  value: PropTypes.object, +  index: PropTypes.number, +  onCheckboxChange: PropTypes.func.isRequired, +  onOpenEditModal: PropTypes.func.isRequired, +  onDeleteJourneyPattern: PropTypes.func.isRequired +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/components/JourneyPatterns.js b/app/javascript/journey_patterns/components/JourneyPatterns.js new file mode 100644 index 000000000..4b2badabb --- /dev/null +++ b/app/javascript/journey_patterns/components/JourneyPatterns.js @@ -0,0 +1,155 @@ +import React, { PropTypes, Component } from 'react' +import _ from 'lodash' +import JourneyPattern from './JourneyPattern' + + +export default class JourneyPatterns extends Component { +  constructor(props){ +    super(props) +    this.previousCity = undefined +  } +  componentDidMount() { +    this.props.onLoadFirstPage() +  } +  componentDidUpdate(prevProps, prevState) { +    if(this.props.status.isFetching == false){ +      $('.table-2entries').each(function() { +        var refH = [] +        var refCol = [] + +        $(this).find('.t2e-head').children('div').each(function() { +          var h = $(this).outerHeight(); +          refH.push(h) +        }); + +        var i = 0 +        $(this).find('.t2e-item').children('div').each(function() { +          var h = $(this).outerHeight(); +          if(refCol.length < refH.length){ +            refCol.push(h) +          } else { +            if(h > refCol[i]) { +              refCol[i] = h +            } +          } +          if(i == (refH.length - 1)){ +            i = 0 +          } else { +            i++ +          } +        }); + +        for(var n = 0; n < refH.length; n++) { +          if(refCol[n] < refH[n]) { +            refCol[n] = refH[n] +          } +        } + +        $(this).find('.th').css('height', refCol[0]); + +        for(var nth = 1; nth < refH.length; nth++) { +          $(this).find('.td:nth-child('+ (nth + 1) +')').css('height', refCol[nth]); +        } +      }); +    } +  } + +  cityNameChecker(sp) { +    let bool = false +    if(sp.city_name != this.previousCity){ +      bool = true +      this.previousCity = sp.city_name +    } +    return ( +      <div +        className={(bool) ? 'headlined' : ''} +        data-headline={(bool) ? sp.city_name : ''} +        title={sp.city_name + ' (' + sp.zip_code +')'} +      > +        <span><span>{sp.name}</span></span> +      </div> +    ) +  } + +  render() { +    this.previousCity = undefined + +    if(this.props.status.isFetching == true) { +      return ( +        <div className="isLoading" style={{marginTop: 80, marginBottom: 80}}> +          <div className="loader"></div> +        </div> +      ) +    } else { +      return ( +        <div className='row'> +          <div className='col-lg-12'> +            {(this.props.status.fetchSuccess == false) && ( +              <div className="alert alert-danger mt-sm"> +                <strong>Erreur : </strong> +                la récupération des missions a rencontré un problème. Rechargez la page pour tenter de corriger le problème +              </div> +            )} + +            { _.some(this.props.journeyPatterns, 'errors') && ( +              <div className="alert alert-danger mt-sm"> +                <strong>Erreur : </strong> +                {this.props.journeyPatterns.map((jp, index) => +                  jp.errors && jp.errors.map((err, i) => { +                    return ( +                      <ul key={i}> +                        <li>{err}</li> +                      </ul> +                    ) +                  }) +                )} +              </div> +            )} + +            <div className={'table table-2entries mt-sm mb-sm' + ((this.props.journeyPatterns.length > 0) ? '' : ' no_result')}> +              <div className='t2e-head w20'> +                <div className='th'> +                  <div className='strong mb-xs'>ID Mission</div> +                  <div>Code mission</div> +                  <div>Nb arrêts</div> +                </div> +                {this.props.stopPointsList.map((sp, i) =>{ +                  return ( +                    <div key={i} className='td'> +                      {this.cityNameChecker(sp)} +                    </div> +                  ) +                })} +              </div> + +              <div className='t2e-item-list w80'> +                <div> +                  {this.props.journeyPatterns.map((journeyPattern, index) => +                    <JourneyPattern +                      value={ journeyPattern } +                      key={ index } +                      onCheckboxChange= {(e) => this.props.onCheckboxChange(e, index)} +                      onOpenEditModal= {() => this.props.onOpenEditModal(index, journeyPattern)} +                      onDeleteJourneyPattern={() => this.props.onDeleteJourneyPattern(index)} +                      status= {this.props.status} +                      editMode= {this.props.editMode} +                      /> +                  )} +                </div> +              </div> +            </div> +          </div> +        </div> +      ) +    } +  } +} + +JourneyPatterns.propTypes = { +  journeyPatterns: PropTypes.array.isRequired, +  stopPointsList: PropTypes.array.isRequired, +  status: PropTypes.object.isRequired, +  onCheckboxChange: PropTypes.func.isRequired, +  onLoadFirstPage: PropTypes.func.isRequired, +  onOpenEditModal: PropTypes.func.isRequired +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/components/Navigate.js b/app/javascript/journey_patterns/components/Navigate.js new file mode 100644 index 000000000..f2fdd668f --- /dev/null +++ b/app/javascript/journey_patterns/components/Navigate.js @@ -0,0 +1,62 @@ +import React, { PropTypes, Component } from 'react' +import actions from '../actions' + +export default function Navigate({ dispatch, journeyPatterns, pagination, status }) { +  let firstPage = 1 +  let lastPage = Math.ceil(pagination.totalCount / window.journeyPatternsPerPage) + +  let firstItemOnPage = firstPage + (pagination.perPage * (pagination.page - firstPage)) +  let lastItemOnPage = firstItemOnPage + (pagination.perPage - firstPage) + +  if(status.isFetching == true) { +    return false +  } +  if(status.fetchSuccess == true) { +    return ( +      <div className='row'> +        <div className='col-lg-12 text-right'> +          <div className='pagination'> +            Liste des missions {firstItemOnPage} à {(lastItemOnPage < pagination.totalCount) ? lastItemOnPage : pagination.totalCount} sur {pagination.totalCount} +            <form className='page_links' onSubmit={e => { +                e.preventDefault() +              }}> +              <button +                onClick={e => { +                  e.preventDefault() +                  dispatch(actions.checkConfirmModal(e, actions.goToPreviousPage(dispatch, pagination), pagination.stateChanged, dispatch)) +                }} +                type='button' +                data-toggle='' +                data-target='#ConfirmModal' +                className={'previous_page' + (pagination.page == firstPage ? ' disabled' : '')} +                disabled={(pagination.page == firstPage ? ' disabled' : '')} +              > +              </button> +              <button +                onClick={e => { +                  e.preventDefault() +                  dispatch(actions.checkConfirmModal(e, actions.goToNextPage(dispatch, pagination), pagination.stateChanged, dispatch)) +                }} +                type='button' +                data-toggle='' +                data-target='#ConfirmModal' +                className={'next_page' + (pagination.page == lastPage ? ' disabled' : '')} +                disabled={(pagination.page == lastPage ? 'disabled' : '')} +              > +              </button> +            </form> +          </div> +        </div> +      </div> +    ) +  } else { +    return false +  } +} + +Navigate.propTypes = { +  journeyPatterns: PropTypes.array.isRequired, +  status: PropTypes.object.isRequired, +  pagination: PropTypes.object.isRequired, +  dispatch: PropTypes.func.isRequired +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/components/SaveJourneyPattern.js b/app/javascript/journey_patterns/components/SaveJourneyPattern.js new file mode 100644 index 000000000..d071fa542 --- /dev/null +++ b/app/javascript/journey_patterns/components/SaveJourneyPattern.js @@ -0,0 +1,39 @@ +import React, { PropTypes, Component } from 'react' +import actions from '../actions' + +export default class SaveJourneyPattern extends Component { +  constructor(props){ +    super(props) +  } + +  render() { +    if(this.props.status.policy['journey_patterns.update'] == false) { +      return false +    }else{ +      return ( +        <div className='row mt-md'> +          <div className='col-lg-12 text-right'> +            <form className='jp_collection formSubmitr ml-xs' onSubmit={e => {e.preventDefault()}}> +              <button +                className='btn btn-default' +                type='button' +                onClick={e => { +                  e.preventDefault() +                  this.props.editMode ? this.props.onSubmitJourneyPattern(this.props.dispatch, this.props.journeyPatterns) : this.props.onEnterEditMode() +                }} +                > +                {this.props.editMode ? "Valider" : "Editer"} +              </button> +            </form> +          </div> +        </div> +      ) +    } +  } +} + +SaveJourneyPattern.propTypes = { +  journeyPatterns: PropTypes.array.isRequired, +  status: PropTypes.object.isRequired, +  page: PropTypes.number.isRequired +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/containers/AddJourneyPattern.js b/app/javascript/journey_patterns/containers/AddJourneyPattern.js new file mode 100644 index 000000000..b093fd111 --- /dev/null +++ b/app/javascript/journey_patterns/containers/AddJourneyPattern.js @@ -0,0 +1,30 @@ +import { connect } from 'react-redux' +import actions from '../actions' +import CreateModal from '../components/CreateModal' + +const mapStateToProps = (state) => { +  return { +    modal: state.modal, +    journeyPatterns: state.journeyPatterns, +    editMode: state.editMode, +    status: state.status +  } +} + +const mapDispatchToProps = (dispatch) => { +  return { +    onModalClose: () =>{ +      dispatch(actions.closeModal()) +    }, +    onAddJourneyPattern: (data) =>{ +      dispatch(actions.addJourneyPattern(data)) +    }, +    onOpenCreateModal: () =>{ +      dispatch(actions.openCreateModal()) +    } +  } +} + +const AddJourneyPattern = connect(mapStateToProps, mapDispatchToProps)(CreateModal) + +export default AddJourneyPattern diff --git a/app/javascript/journey_patterns/containers/ConfirmModal.js b/app/javascript/journey_patterns/containers/ConfirmModal.js new file mode 100644 index 000000000..92ce09f33 --- /dev/null +++ b/app/javascript/journey_patterns/containers/ConfirmModal.js @@ -0,0 +1,30 @@ +import { connect } from 'react-redux' +import actions from '../actions' +import ConfirmModal from '../components/ConfirmModal' + +const mapStateToProps = (state) => { +  return { +    modal: state.modal, +    journeyPatterns: state.journeyPatterns +  } +} + +const mapDispatchToProps = (dispatch) => { +  return { +    onModalAccept: (next, state) =>{ +      dispatch(actions.fetchingApi()) +      actions.submitJourneyPattern(dispatch, state, next) +    }, +    onModalCancel: (next) =>{ +      dispatch(actions.fetchingApi()) +      dispatch(next) +    }, +    onModalClose: () =>{ +      dispatch(actions.closeModal()) +    } +  } +} + +const ConfirmModalContainer = connect(mapStateToProps, mapDispatchToProps)(ConfirmModal) + +export default ConfirmModalContainer diff --git a/app/javascript/journey_patterns/containers/JourneyPatternList.js b/app/javascript/journey_patterns/containers/JourneyPatternList.js new file mode 100644 index 000000000..d98734407 --- /dev/null +++ b/app/javascript/journey_patterns/containers/JourneyPatternList.js @@ -0,0 +1,34 @@ +import { connect } from 'react-redux' +import actions from '../actions' +import JourneyPatterns from '../components/JourneyPatterns' + +const mapStateToProps = (state) => { +  return { +    journeyPatterns: state.journeyPatterns, +    status: state.status, +    editMode: state.editMode, +    stopPointsList: state.stopPointsList +  } +} + +const mapDispatchToProps = (dispatch) => { +  return { +    onLoadFirstPage: () =>{ +      dispatch(actions.fetchingApi()) +      actions.fetchJourneyPatterns(dispatch) +    }, +    onCheckboxChange: (e, index) =>{ +      dispatch(actions.updateCheckboxValue(e, index)) +    }, +    onOpenEditModal: (index, journeyPattern) =>{ +      dispatch(actions.openEditModal(index, journeyPattern)) +    }, +    onDeleteJourneyPattern: (index) =>{ +      dispatch(actions.deleteJourneyPattern(index)) +    } +  } +} + +const JourneyPatternList = connect(mapStateToProps, mapDispatchToProps)(JourneyPatterns) + +export default JourneyPatternList diff --git a/app/javascript/journey_patterns/containers/Modal.js b/app/javascript/journey_patterns/containers/Modal.js new file mode 100644 index 000000000..ace71a857 --- /dev/null +++ b/app/javascript/journey_patterns/containers/Modal.js @@ -0,0 +1,26 @@ +import { connect } from 'react-redux' +import actions from '../actions' +import EditModal from '../components/EditModal' +import CreateModal from '../components/CreateModal' + +const mapStateToProps = (state) => { +  return { +    modal: state.modal, +    journeyPattern: state.journeyPattern +  } +} + +const mapDispatchToProps = (dispatch) => { +  return { +    onModalClose: () =>{ +      dispatch(actions.closeModal()) +    }, +    saveModal: (index, data) =>{ +      dispatch(actions.saveModal(index, data)) +    } +  } +} + +const ModalContainer = connect(mapStateToProps, mapDispatchToProps)(EditModal, CreateModal) + +export default ModalContainer diff --git a/app/javascript/journey_patterns/containers/Navigate.js b/app/javascript/journey_patterns/containers/Navigate.js new file mode 100644 index 000000000..d34e0b4c5 --- /dev/null +++ b/app/javascript/journey_patterns/containers/Navigate.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux' +import actions from '../actions' +import NavigateComponent from '../components/Navigate' + +const mapStateToProps = (state) => { +  return { +    journeyPatterns: state.journeyPatterns, +    status: state.status, +    pagination: state.pagination +  } +} + +const Navigate = connect(mapStateToProps)(NavigateComponent) + +export default Navigate
\ No newline at end of file diff --git a/app/javascript/journey_patterns/containers/SaveJourneyPattern.js b/app/javascript/journey_patterns/containers/SaveJourneyPattern.js new file mode 100644 index 000000000..b630c121c --- /dev/null +++ b/app/javascript/journey_patterns/containers/SaveJourneyPattern.js @@ -0,0 +1,27 @@ +import { connect } from 'react-redux' +import actions from '../actions' +import SaveJourneyPatternComponent from '../components/SaveJourneyPattern' + +const mapStateToProps = (state) => { +  return { +    journeyPatterns: state.journeyPatterns, +    editMode: state.editMode, +    page: state.pagination.page, +    status: state.status +  } +} + +const mapDispatchToProps = (dispatch) => { +  return { +    onEnterEditMode: () => { +      dispatch(actions.enterEditMode()) +    }, +    onSubmitJourneyPattern: (next, state) => { +      actions.submitJourneyPattern(dispatch, state, next) +    } +  } +} + +const SaveJourneyPattern = connect(mapStateToProps, mapDispatchToProps)(SaveJourneyPatternComponent) + +export default SaveJourneyPattern diff --git a/app/javascript/journey_patterns/reducers/editMode.js b/app/javascript/journey_patterns/reducers/editMode.js new file mode 100644 index 000000000..bff976804 --- /dev/null +++ b/app/javascript/journey_patterns/reducers/editMode.js @@ -0,0 +1,10 @@ +export default function editMode(state = {}, action ) { +  switch (action.type) { +    case "ENTER_EDIT_MODE": +      return true +    case "EXIT_EDIT_MODE": +      return false +    default: +      return state +  } +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/reducers/index.js b/app/javascript/journey_patterns/reducers/index.js new file mode 100644 index 000000000..2ffaf86d4 --- /dev/null +++ b/app/javascript/journey_patterns/reducers/index.js @@ -0,0 +1,18 @@ +import { combineReducers } from 'redux' +import editMode from './editMode' +import status from './status' +import journeyPatterns from './journeyPatterns' +import pagination from './pagination' +import modal from './modal' +import stopPointsList from './stopPointsList' + +const journeyPatternsApp = combineReducers({ +  editMode, +  status, +  journeyPatterns, +  pagination, +  stopPointsList, +  modal +}) + +export default journeyPatternsApp diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js new file mode 100644 index 000000000..7702e21bc --- /dev/null +++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js @@ -0,0 +1,90 @@ +import _ from 'lodash' +import actions from "../actions" + +export default function journeyPattern(state = {}, action) { +  switch (action.type) { +    case 'ADD_JOURNEYPATTERN': +      let stopPoints = window.stopPoints + +      if(stopPoints != undefined) { +        stopPoints.map((s)=>{ +          s.checked = false +          return s +        }) +      } +      return { +        name: action.data.name.value, +        published_name: action.data.published_name.value, +        registration_number: action.data.registration_number.value, +        stop_points: stopPoints, +        deletable: false +      } +    case 'UPDATE_CHECKBOX_VALUE': +      var updatedStopPoints = state.stop_points.map((s) => { +        if (String(s.id) == action.id) { +          return _.assign({}, s, {checked : !s.checked}) +        }else { +          return s +        } +      }) +      return _.assign({}, state, {stop_points: updatedStopPoints}) +    default: +      return state +  } +} + +const journeyPatterns = (state = [], action) => { +  switch (action.type) { +    case 'RECEIVE_JOURNEY_PATTERNS': +      return [...action.json] +    case 'RECEIVE_ERRORS': +      return [...action.json] +    case 'GO_TO_PREVIOUS_PAGE': +      $('#ConfirmModal').modal('hide') +      if(action.pagination.page > 1){ +        actions.fetchJourneyPatterns(action.dispatch, action.pagination.page, action.nextPage) +      } +      return state +    case 'GO_TO_NEXT_PAGE': +      $('#ConfirmModal').modal('hide') +      if (action.pagination.totalCount - (action.pagination.page * action.pagination.perPage) > 0){ +        actions.fetchJourneyPatterns(action.dispatch, action.pagination.page, action.nextPage) +      } +      return state +    case 'UPDATE_CHECKBOX_VALUE': +      return state.map((j, i) =>{ +        if(i == action.index) { +          return journeyPattern(j, action) +        } else { +          return j +        } +      }) +    case 'DELETE_JOURNEYPATTERN': +      return state.map((j, i) =>{ +        if(i == action.index) { +          return _.assign({}, j, {deletable: true}) +        } else { +          return j +        } +      }) +    case 'ADD_JOURNEYPATTERN': +      return [ +        journeyPattern(state, action), +        ...state +      ] +    case 'SAVE_MODAL': +      return state.map((j, i) =>{ +        if(i == action.index) { +          return _.assign({}, j, { +            name: action.data.name.value, +            published_name: action.data.published_name.value, +            registration_number: action.data.registration_number.value +          }) +        } else { +          return j +        } +      }) +    default: +      return state +  } +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/reducers/modal.js b/app/javascript/journey_patterns/reducers/modal.js new file mode 100644 index 000000000..0a96f1679 --- /dev/null +++ b/app/javascript/journey_patterns/reducers/modal.js @@ -0,0 +1,41 @@ +import _ from 'lodash' + +export default function modal(state = {}, action) { +  switch (action.type) { +    case 'OPEN_CONFIRM_MODAL': +      $('#ConfirmModal').modal('show') +      return _.assign({}, state, { +        type: 'confirm', +        confirmModal: { +          callback: action.callback, +        } +      }) +    case 'EDIT_JOURNEYPATTERN_MODAL': +      return { +        type: 'edit', +        modalProps: { +          index: action.index, +          journeyPattern: action.journeyPattern +        }, +        confirmModal: {} +      } +    case 'CREATE_JOURNEYPATTERN_MODAL': +      return { +        type: 'create', +        modalProps: {}, +        confirmModal: {} +      } +    case 'DELETE_JOURNEYPATTERN': +      return _.assign({}, state, { type: '' }) +    case 'SAVE_MODAL': +      return _.assign({}, state, { type: '' }) +    case 'CLOSE_MODAL': +      return { +        type: '', +        modalProps: {}, +        confirmModal: {} +      } +    default: +      return state +  } +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/reducers/pagination.js b/app/javascript/journey_patterns/reducers/pagination.js new file mode 100644 index 000000000..01fdf21d4 --- /dev/null +++ b/app/javascript/journey_patterns/reducers/pagination.js @@ -0,0 +1,35 @@ +import _ from 'lodash' + +export default function pagination (state = {}, action) { +  switch (action.type) { +    case 'RECEIVE_JOURNEY_PATTERNS': +      return _.assign({}, state, {stateChanged: false}) +    case 'GO_TO_PREVIOUS_PAGE': +      if (action.pagination.page > 1){ +        toggleOnConfirmModal() +        return _.assign({}, state, {page : action.pagination.page - 1, stateChanged: false}) +      } +      return state +    case 'GO_TO_NEXT_PAGE': +      if (state.totalCount - (action.pagination.page * action.pagination.perPage) > 0){ +        toggleOnConfirmModal() +        return _.assign({}, state, {page : action.pagination.page + 1, stateChanged: false}) +      } +      return state +    case 'UPDATE_CHECKBOX_VALUE': +    case 'ADD_JOURNEYPATTERN': +    case 'SAVE_MODAL': +      toggleOnConfirmModal('modal') +      return _.assign({}, state, {stateChanged: true}) +    case 'UPDATE_TOTAL_COUNT': +      return _.assign({}, state, {totalCount : state.totalCount - action.diff }) +    default: +      return state +  } +} + +const toggleOnConfirmModal = (arg = '') =>{ +  $('.confirm').each(function(){ +    $(this).data('toggle','') +  }) +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/reducers/status.js b/app/javascript/journey_patterns/reducers/status.js new file mode 100644 index 000000000..88c75966d --- /dev/null +++ b/app/javascript/journey_patterns/reducers/status.js @@ -0,0 +1,21 @@ +import _ from 'lodash' +import actions from '../actions' + +export default function status (state = {}, action) { +  switch (action.type) { +    case 'UNAVAILABLE_SERVER': +      return _.assign({}, state, {fetchSuccess: false}) +    case 'FETCH_API': +      return _.assign({}, state, {isFetching: true}) +    case 'RECEIVE_JOURNEY_PATTERNS': +      return _.assign({}, state, {fetchSuccess: true, isFetching: false}) +    case 'RECEIVE_ERRORS': +      return _.assign({}, state, {isFetching: false}) +    case 'ENTER_EDIT_MODE': +      return _.assign({}, state, {editMode: true}) +    case 'EXIT_EDIT_MODE': +      return _.assign({}, state, {editMode: false}) +    default: +      return state +  } +}
\ No newline at end of file diff --git a/app/javascript/journey_patterns/reducers/stopPointsList.js b/app/javascript/journey_patterns/reducers/stopPointsList.js new file mode 100644 index 000000000..ee5eb1a80 --- /dev/null +++ b/app/javascript/journey_patterns/reducers/stopPointsList.js @@ -0,0 +1,6 @@ +export default function stopPointsList (state = [], action) { +  switch (action.type) { +    default: +      return state +  } +}
\ No newline at end of file | 
