diff options
19 files changed, 351 insertions, 44 deletions
| diff --git a/app/assets/stylesheets/base/_config.sass b/app/assets/stylesheets/base/_config.sass index ec1c43e7f..2c226357f 100644 --- a/app/assets/stylesheets/base/_config.sass +++ b/app/assets/stylesheets/base/_config.sass @@ -16,7 +16,7 @@ $blue: #007fbb  $darkgrey: #4b4b4b  $grey: #a4a4a4 -$lightgrey: rgba($grey, 0.15) +$lightgrey: lighten($grey, 25)  $green: #70b12b  $red: #da2f36 diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass index 47faf19b1..5143d2ba4 100644 --- a/app/assets/stylesheets/components/_forms.sass +++ b/app/assets/stylesheets/components/_forms.sass @@ -284,6 +284,7 @@ table, .table    height: $cbx-size    width: $cbx-size    margin: 0 auto +  transition: transform 0.2s, background-color 0.2s    > input[type='checkbox']      &:not(:checked), &:checked diff --git a/app/assets/stylesheets/modules/_jp_collection.sass b/app/assets/stylesheets/modules/_jp_collection.sass index f579cf87b..22edb9599 100644 --- a/app/assets/stylesheets/modules/_jp_collection.sass +++ b/app/assets/stylesheets/modules/_jp_collection.sass @@ -103,6 +103,7 @@    .table-2entries .t2e-item-list      .t2e-item        position: relative +      overflow: hidden        .th .vj_tt          display: inline-block @@ -111,6 +112,116 @@          + .vj_tt            margin-left: 5px +      &.with-costs + +        & > div +          position: relative + +        .has_radio +          margin-right: 150px + +        .costs +          background: $lightgrey +          padding: 5px +          color: white +          position: absolute +          left: 80px +          top: -1px +          transform: translateY(-50%) +          font-size: 0.75em +          transition: background 0.1s +          border: 1px solid white + +          &:hover +            background: $blue +            &:after +              background: $blue +          &:after +            content: "" +            height: 2px +            position: absolute +            left: -30px +            background: $lightgrey +            right: 100% +            top: 50% +            transition: background 0.1s + +          p +            display: block +            border: none +            margin-bottom: 0 +            i +              margin-right: 3px +              width: 12px +            & + p +              position: relative +              z-index: 2 +              padding-right: 0 +              margin: 0 +              border-right: none + +            input +              display: inline-block +              width: 40px +              border: none +              margin-right: 5px +              color: black + +      .edit-mode .costs +        p +          margin-bottom: 5px +          & + p +            margin-bottom: 0 + +        background: $blue +        &:after +          background: $blue + + +      .with-headline + .costs +        top: 25% + +      .deactivated .costs +        display: none + +      $link-size: 10px +      .link +        position: absolute +        left: 40px +        width: 10px +        top: -7px +        bottom: -7px +        background: $blue +        z-index: 3 +        &:after +          content: "" +          width: $link-size +          height: $link-size +          position: absolute +          top: 50% +          bottom: 50% +          margin-top: -$link-size/2 - 3px +          border-top: $link-size/2 solid transparent +          border-left: $link-size/2 solid transparent +          border-right: $link-size/2 solid $blue +          border-bottom: $link-size/2 solid $blue +          transform: rotate(135deg) +          left: 0% +          opacity: 0 +          transition: left 0.2s, opacity 0.2s + +      .activated .link +        &:after +          left: -50% +          opacity: 1 + +      .headlined +        .link +          top: 0 +          &:after +            top: 75% +            margin-top: -$link-size/2 - 1px +        &.has-error          &:before            content: '' diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb index 5fe78766c..6b661da0c 100644 --- a/app/controllers/journey_patterns_collections_controller.rb +++ b/app/controllers/journey_patterns_collections_controller.rb @@ -17,35 +17,45 @@ class JourneyPatternsCollectionsController < ChouetteController    alias_method :vehicle_journey, :resource    def show -    @q = route.journey_patterns.search(params[:q]).result(distinct: true).includes(:stop_points) +    @q = route.journey_patterns +    if params[:q].present? +      ids = @q.search(params[:q]).result(distinct: true).pluck(:id) +      @q = @q.where(id: ids) +    end +    @q = @q.includes(:stop_points) +    # @q = route.journey_patterns.search(params[:q]).result().includes(:stop_points)      @ppage = 10      @journey_patterns ||= @q.paginate(page: params[:page], per_page: @ppage).order(:name) - -    @stop_points_list = [] -    route.stop_points.each do |sp| -      @stop_points_list << { -        :id => sp.stop_area.id, -        :route_id => sp.try(:route_id), -        :object_id => sp.try(:objectid), -        :position => sp.try(:position), -        :for_boarding => sp.try(:for_boarding), -        :for_alighting => sp.try(:for_alighting), -        :name => sp.stop_area.try(:name), -        :zip_code => sp.stop_area.try(:zip_code), -        :city_name => sp.stop_area.try(:city_name), -        :comment => sp.stop_area.try(:comment), -        :area_type => sp.stop_area.try(:area_type), -        :registration_number => sp.stop_area.try(:registration_number), -        :nearest_topic_name => sp.stop_area.try(:nearest_topic_name), -        :fare_code => sp.stop_area.try(:fare_code), -        :longitude => sp.stop_area.try(:longitude), -        :latitude => sp.stop_area.try(:latitude), -        :long_lat_type => sp.stop_area.try(:long_lat_type), -        :country_code => sp.stop_area.try(:country_code), -        :street_name => sp.stop_area.try(:street_name) -      } +    respond_to do |format| +      format.json +      format.html do +        @stop_points_list = [] +        route.stop_points.each do |sp| +          @stop_points_list << { +            :id => sp.stop_area.id, +            :route_id => sp.try(:route_id), +            :object_id => sp.try(:objectid), +            :position => sp.try(:position), +            :for_boarding => sp.try(:for_boarding), +            :for_alighting => sp.try(:for_alighting), +            :name => sp.stop_area.try(:name), +            :zip_code => sp.stop_area.try(:zip_code), +            :city_name => sp.stop_area.try(:city_name), +            :comment => sp.stop_area.try(:comment), +            :area_type => sp.stop_area.try(:area_type), +            :registration_number => sp.stop_area.try(:registration_number), +            :nearest_topic_name => sp.stop_area.try(:nearest_topic_name), +            :fare_code => sp.stop_area.try(:fare_code), +            :longitude => sp.stop_area.try(:longitude), +            :latitude => sp.stop_area.try(:latitude), +            :long_lat_type => sp.stop_area.try(:long_lat_type), +            :country_code => sp.stop_area.try(:country_code), +            :street_name => sp.stop_area.try(:street_name) +          } +        end +        @stop_points_list = @stop_points_list.sort_by {|a| a[:position] } +      end      end -    @stop_points_list = @stop_points_list.sort_by {|a| a[:position] }    end    def user_permissions diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js index 1c2eb68b2..09e2001c1 100644 --- a/app/javascript/journey_patterns/actions/index.js +++ b/app/javascript/journey_patterns/actions/index.js @@ -64,6 +64,11 @@ const actions = {      type : 'DELETE_JOURNEYPATTERN',      index,    }), +  updateJourneyPatternCosts : (index, costs) => ({ +    type : 'UPDATE_JOURNEYPATTERN_COSTS', +    index, +    costs +  }),    closeModal : () => ({      type : 'CLOSE_MODAL'    }), @@ -194,6 +199,7 @@ const actions = {                    }                  })                } +<<<<<<< HEAD                journeyPatterns.push({                  name: val.name,                  object_id: val.object_id, @@ -204,6 +210,14 @@ const actions = {                  stop_points: val.route_short_description.stop_points,                  deletable: false                }) +======= +              journeyPatterns.push( +                _.assign({}, val, { +                  stop_points: val.route_short_description.stop_points, +                  deletable: false +                }) +              ) +>>>>>>> Refs #5455 @6h; Add time and distance between stops in Journey Patterns              }            }            window.currentItemsLength = journeyPatterns.length diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js index d4c9816ec..40a6899e2 100644 --- a/app/javascript/journey_patterns/components/JourneyPattern.js +++ b/app/javascript/journey_patterns/components/JourneyPattern.js @@ -5,6 +5,17 @@ export default class JourneyPattern extends Component{    constructor(props){      super(props)      this.previousCity = undefined +    this.previousSpId = undefined +    this.updateCosts = this.updateCosts.bind(this) +  } + +  updateCosts(e) { +    let costs = { +      [e.target.dataset.costsKey]: { +        [e.target.name]: parseInt(e.target.value) +      } +    } +    this.props.onUpdateJourneyPatternCosts(costs)    }    vehicleJourneyURL(jpOid) { @@ -16,16 +27,26 @@ export default class JourneyPattern extends Component{      )    } +  hasFeature(key) { +    return this.props.status.features[key] +  } +    cityNameChecker(sp) {      let bool = false +      if(sp.city_name != this.previousCity){        bool = true        this.previousCity = sp.city_name      } +    return bool +  } + +  spNode(sp, headlined){      return (        <div -        className={(bool) ? 'headlined' : ''} +        className={(headlined) ? 'headlined' : ''}        > +        <div className={'link '}></div>          <span className='has_radio'>            <input              onChange = {(e) => this.props.onCheckboxChange(e)} @@ -61,9 +82,9 @@ export default class JourneyPattern extends Component{    render() {      this.previousCity = undefined - +    this.previousSpId = undefined      return ( -      <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '')}> +      <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '') + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}>          {/* Errors */}          {/* this.props.value.errors ? this.getErrors(this.props.value.errors) : '' */} @@ -112,9 +133,49 @@ export default class JourneyPattern extends Component{            </div>            {this.props.value.stop_points.map((stopPoint, i) =>{ +            let costs = null +            let costsKey = null +            let time = null +            let distance = null +            let time_in_words = null +            if(this.previousSpId && stopPoint.checked){ +              costsKey = this.previousSpId + "-" + stopPoint.id +              costs = this.props.value.costs[costsKey] || {distance: 0, time: 0} +              time = costs['time'] || 0 +              distance = costs['distance'] || 0 +              if(time < 60){ +                time_in_words = time + " min" +              } +              else{ +                let hours = parseInt(time/60) +                time_in_words = hours + " h " + (time - 60*hours) + " min" +              } +            } +            if(stopPoint.checked){ +              this.previousSpId = stopPoint.id +            } +            let headlined = this.cityNameChecker(stopPoint)              return ( -              <div key={i} className='td'> -                {this.cityNameChecker(stopPoint)} +              <div key={i} className={(stopPoint.checked ? 'activated' : 'deactivated') + (this.props.editMode ? ' edit-mode' : '')}> +                <div className={'td' + (headlined ? ' with-headline' : '')}> +                  {this.spNode(stopPoint, headlined)} +                </div> +                {this.hasFeature('costs_in_journey_patterns') && costs && <div className='costs' id={'costs-' + this.props.value.id + '-' + costsKey }> +                  {this.props.editMode && <div> +                    <p> +                      <input type="number" value={costs['distance'] || 0} min='0' name="distance" onChange={this.updateCosts} data-costs-key={costsKey}/> +                      <span>km</span> +                    </p> +                    <p> +                      <input type="number" value={costs['time'] || 0} min='0' name="time" onChange={this.updateCosts} data-costs-key={costsKey}/> +                      <span>min</span> +                    </p> +                  </div>} +                  {!this.props.editMode && <div> +                    <p><i className="fa fa-arrows-h"></i>{(costs['distance'] || 0) + " km"}</p> +                    <p><i className="fa fa-clock-o"></i>{time_in_words}</p> +                  </div>} +                </div>}                </div>              )            })} @@ -129,4 +190,4 @@ JourneyPattern.propTypes = {    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 index 4b2badabb..69024050f 100644 --- a/app/javascript/journey_patterns/components/JourneyPatterns.js +++ b/app/javascript/journey_patterns/components/JourneyPatterns.js @@ -131,6 +131,7 @@ export default class JourneyPatterns extends Component {                        onCheckboxChange= {(e) => this.props.onCheckboxChange(e, index)}                        onOpenEditModal= {() => this.props.onOpenEditModal(index, journeyPattern)}                        onDeleteJourneyPattern={() => this.props.onDeleteJourneyPattern(index)} +                      onUpdateJourneyPatternCosts={(costs) => this.props.onUpdateJourneyPatternCosts(index, costs)}                        status= {this.props.status}                        editMode= {this.props.editMode}                        /> @@ -152,4 +153,4 @@ JourneyPatterns.propTypes = {    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/containers/JourneyPatternList.js b/app/javascript/journey_patterns/containers/JourneyPatternList.js index d98734407..d338345f2 100644 --- a/app/javascript/journey_patterns/containers/JourneyPatternList.js +++ b/app/javascript/journey_patterns/containers/JourneyPatternList.js @@ -25,7 +25,10 @@ const mapDispatchToProps = (dispatch) => {      },      onDeleteJourneyPattern: (index) =>{        dispatch(actions.deleteJourneyPattern(index)) -    } +    }, +    onUpdateJourneyPatternCosts: (index, costs) =>{ +      dispatch(actions.updateJourneyPatternCosts(index, costs)) +    },    }  } diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js index 0bbcba976..1ce069522 100644 --- a/app/javascript/journey_patterns/reducers/journeyPatterns.js +++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js @@ -17,6 +17,7 @@ const journeyPattern = (state = {}, action) =>{          published_name: action.data.published_name.value,          registration_number: action.data.registration_number.value,          stop_points: stopPoints, +        costs: {},          deletable: false        }      case 'UPDATE_CHECKBOX_VALUE': @@ -67,6 +68,19 @@ export default function journeyPatterns (state = [], action)  {            return j          }        }) +    case 'UPDATE_JOURNEYPATTERN_COSTS': +      return state.map((j, i) =>{ +        if(i == action.index) { +          const new_costs = Object.assign({}, j.costs) +          Object.keys(action.costs).map((key) => { +            let new_costs_for_key = Object.assign({}, j.costs[key] || {}, action.costs[key]) +            new_costs[key] = new_costs_for_key +          }) +          return _.assign({}, j, {costs: new_costs}) +        } else { +          return j +        } +      })      case 'ADD_JOURNEYPATTERN':        return [          journeyPattern(state, action), @@ -87,4 +101,4 @@ export default function journeyPatterns (state = [], action)  {      default:        return state    } -}
\ No newline at end of file +} diff --git a/app/javascript/packs/journey_patterns/index.js b/app/javascript/packs/journey_patterns/index.js index fde28b45d..367a8830f 100644 --- a/app/javascript/packs/journey_patterns/index.js +++ b/app/javascript/packs/journey_patterns/index.js @@ -16,6 +16,7 @@ var initialState = {    editMode: false,    status: {      policy: window.perms, +    features: window.features,      fetchSuccess: true,      isFetching: false    }, diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index 366fde188..1ddf7c9fb 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -58,14 +58,14 @@ module Chouette        {          name: item['name'],          published_name: item['published_name'], -        registration_number: item['registration_number'] +        registration_number: item['registration_number'], +        costs: item['costs']        }      end      def self.state_create_instance route, item        # Flag new record, so we can unset object_id if transaction rollback        jp = route.journey_patterns.create(state_permited_attributes(item)) -        # FIXME        # DefaultAttributesSupport will trigger some weird validation on after save        # wich will call to valid?, wich will populate errors @@ -146,5 +146,9 @@ module Chouette          vjas.destroy        end      end + +    def costs +      read_attribute(:costs) || {} +    end    end  end diff --git a/app/views/api/v1/journey_patterns/show.rabl b/app/views/api/v1/journey_patterns/show.rabl index 86876f3fb..67d483147 100644 --- a/app/views/api/v1/journey_patterns/show.rabl +++ b/app/views/api/v1/journey_patterns/show.rabl @@ -5,6 +5,10 @@ extends "api/v1/trident_objects/show"    attributes attr, :unless => lambda { |m| m.send( attr).nil?}  end +if has_feature? :costs_in_journey_patterns +  attribute :costs +end +  node(:route_short_description) do |journey_pattern|    partial("api/v1/routes/short_description", :object => journey_pattern.route)  end diff --git a/db/migrate/20180103084612_add_costs_to_journey_patterns.rb b/db/migrate/20180103084612_add_costs_to_journey_patterns.rb new file mode 100644 index 000000000..6795e3671 --- /dev/null +++ b/db/migrate/20180103084612_add_costs_to_journey_patterns.rb @@ -0,0 +1,5 @@ +class AddCostsToJourneyPatterns < ActiveRecord::Migration +  def change +    add_column :journey_patterns, :costs, :json +  end +end diff --git a/db/schema.rb b/db/schema.rb index 667b95c84..df8243cfd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@  #  # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171227113809) do +ActiveRecord::Schema.define(version: 20180103084612) do    # These are extensions that must be enabled in order to support this database    enable_extension "plpgsql" @@ -442,6 +442,7 @@ ActiveRecord::Schema.define(version: 20171227113809) do      t.string   "checksum"      t.text     "checksum_source"      t.string   "data_source_ref" +    t.json     "costs"    end    add_index "journey_patterns", ["objectid"], name: "journey_patterns_objectid_key", unique: true, using: :btree diff --git a/spec/controllers/journey_patterns_collections_controller_spec.rb b/spec/controllers/journey_patterns_collections_controller_spec.rb index a3efbc23f..e2adc59f4 100644 --- a/spec/controllers/journey_patterns_collections_controller_spec.rb +++ b/spec/controllers/journey_patterns_collections_controller_spec.rb @@ -25,4 +25,20 @@ RSpec.describe JourneyPatternsCollectionsController, :type => :controller do                                                     'journey_patterns.update'  => true }.to_json)      end    end + +  context "get show" do +    login_user + +    let( :referential ){ Referential.first } +    let( :line ){ create(:line) } +    let( :route ){ create(:route, line: line) } + +    let(:request){ +      get :show, referential_id: referential.id, line_id: line.id, route_id: route.id, format: :json +    } +    it 'should be successful' do +      request +      expect(response).to be_success +    end +  end  end diff --git a/spec/factories/chouette_journey_pattern.rb b/spec/factories/chouette_journey_pattern.rb index 05d8d536a..d96302e7f 100644 --- a/spec/factories/chouette_journey_pattern.rb +++ b/spec/factories/chouette_journey_pattern.rb @@ -13,6 +13,12 @@ FactoryGirl.define do          j.stop_point_ids = j.route.stop_points.map(&:id)          j.departure_stop_point_id = j.route.stop_points.first.id          j.arrival_stop_point_id = j.route.stop_points.last.id +        j.costs = { +          "1-2": { +            distance: 10, +            time: 10 +          } +        }        end      end @@ -35,5 +41,3 @@ FactoryGirl.define do    end  end - - diff --git a/spec/javascript/journey_patterns/actions_spec.js b/spec/javascript/journey_patterns/actions_spec.js index 2542fa2f4..60d6d88bb 100644 --- a/spec/javascript/journey_patterns/actions_spec.js +++ b/spec/javascript/journey_patterns/actions_spec.js @@ -112,6 +112,22 @@ describe('when clicking on a journey pattern delete button', () => {      expect(actions.deleteJourneyPattern(index)).toEqual(expectedAction)    })  }) +describe('when changing on a journey pattern costs', () => { +  it('should create an action to update journey pattern', () => { +    const index = 1 +    const costs = { +      "1-2": { +        distance: 1 +      } +    } +    const expectedAction = { +      type: 'UPDATE_JOURNEYPATTERN_COSTS', +      index, +      costs +    } +    expect(actions.updateJourneyPatternCosts(index, costs)).toEqual(expectedAction) +  }) +})  describe('when clicking on validate button inside edit modal', () => {    it('should create an action to save journey pattern modifications', () => {      const index = 1 diff --git a/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js b/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js index 24780ab5a..bfa87d24a 100644 --- a/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js +++ b/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js @@ -47,7 +47,10 @@ describe('journeyPatterns reducer', () => {          object_id : 'o1',          published_name: 'M1',          registration_number: '', -        stop_points: fakeStopPoints +        stop_points: fakeStopPoints, +        costs: { + +        }        },        {          deletable: false, @@ -55,7 +58,13 @@ describe('journeyPatterns reducer', () => {          object_id : 'o2',          published_name: 'M2',          registration_number: '', -        stop_points: fakeStopPoints +        stop_points: fakeStopPoints, +        costs: { +          "1-2": { +            distance: 0, +            time: 10, +          } +        }        }      ]    }) @@ -83,7 +92,8 @@ describe('journeyPatterns reducer', () => {        published_name: 'M3',        registration_number: '',        deletable: false, -      stop_points: stopPoints +      stop_points: stopPoints, +      costs: {}      }, ...state])    }) @@ -100,6 +110,28 @@ describe('journeyPatterns reducer', () => {      ).toEqual([newState, state[1]])    }) +  it('should handle UPDATE_JOURNEYPATTERN_COSTS', () => { +    const costs = { +      "1-2": { +        distance: 1 +      } +    } +    const new_costs = { +      "1-2": { +        distance: 1, +        time: 10, +      } +    } +    const new_state = Object.assign({}, state[1], {costs: new_costs}) +    expect( +      jpReducer(state, { +        type: 'UPDATE_JOURNEYPATTERN_COSTS', +        index: 1, +        costs +      }) +    ).toEqual([state[0], new_state]) +  }) +    it('should handle DELETE_JOURNEYPATTERN', () => {      expect(        jpReducer(state, { diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index ea7c2a2e9..077c85e85 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -39,6 +39,7 @@ describe Chouette::JourneyPattern, :type => :model do          item['stop_points'] = jp.stop_points.map do |sp|            { 'id' => sp.stop_area_id }          end +        item['costs'] = jp.costs        end      end @@ -72,6 +73,14 @@ describe Chouette::JourneyPattern, :type => :model do        expect(new_state['new_record']).to be_truthy      end +    it 'should create journey_pattern with state_update' do +      new_state = journey_pattern_to_state(build(:journey_pattern, objectid: nil, route: route)) +      collection = [new_state] +      expect { +        Chouette::JourneyPattern.state_update route, collection +      }.to change{Chouette::JourneyPattern.count}.by(1) +    end +      it 'should delete journey_pattern' do        state['deletable'] = true        collection = [state] | 
