diff options
Diffstat (limited to 'app')
15 files changed, 360 insertions, 78 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..14a6b9205 100644 --- a/app/assets/stylesheets/modules/_jp_collection.sass +++ b/app/assets/stylesheets/modules/_jp_collection.sass @@ -5,6 +5,7 @@ #journey_patterns .table-2entries .t2e-head + > .td position: relative padding-left: 25px @@ -99,32 +100,195 @@ top: 50% margin-top: -8px - // Errors - .table-2entries .t2e-item-list - .t2e-item - position: relative + .table-2entries + .t2e-item-list + & > div + overflow: visible + .td + overflow: hidden + + .t2e-item + position: relative - .th .vj_tt - display: inline-block - vertical-align: top + .th .vj_tt + display: inline-block + vertical-align: top - + .vj_tt - margin-left: 5px + + .vj_tt + margin-left: 5px - &.has-error - &:before - content: '' - position: absolute - top: 0 - left: 0 - right: 0 - bottom: 0 - border: 2px solid $red - - > .th - > div:first-child, > div:first-child + div - color: $red - - // Reset default behaviour - .form-control - border-color: #ccc + &.with-costs + .td + padding: 15px 8px + + $link-size: 10px + .link + position: absolute + left: 50px + width: 10px + top: -15px + bottom: -15px + background: $blue + z-index: 3 + opacity: 0.5 + &:after + content: "" + width: $link-size + height: $link-size + position: absolute + top: 50% + bottom: 50% + margin-top: -$link-size/2 + 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 + + .headlined .link + top: 0 + bottom: -15px + + &:after + top: 75% + margin-top: -$link-size/2 - 1px + + .activated .link + &:after + left: -50% + opacity: 1 + + & > div + position: relative + + .link + left: 35px + + .has_radio + margin-right: 150px + + .costs + background: $blue + opacity: 0.5 + padding: 5px + color: white + position: absolute + cursor: not-allowed + left: 75px + top: -1px + transform: translateY(-50%) + font-size: 0.75em + transition: background 0.1s + border: 1px solid white + + &:hover + opacity: 1 + &:after + opacity: 1 + + &:after + opacity: 0.5 + content: "" + height: 2px + position: absolute + left: -23px + background: $blue + 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 + cursor: pointer + p + margin-bottom: 5px + & + p + margin-bottom: 0 + + opacity: 1 + &:after + opacity: 1 + + .link + opacity: 1 + + .with-headline + .costs + top: 25% + + .deactivated .costs + display: none + + &.has-error + &:before + content: '' + position: absolute + top: 0 + left: 0 + right: 0 + bottom: 0 + border: 2px solid $red + + > .th + > div:first-child, > div:first-child + div + color: $red + + // Reset default behaviour + .form-control + border-color: #ccc + + .t2e-head + .td.with-costs + & > div + &:not(.headlined) + height: calc(100% + 6px) + & > span + &:after + top: -15px + bottom: -9px + + div.headlined + &:before + margin-bottom: 0 + & > span + height: calc(100% - (1.4em + 15px)) + &:after + top: calc((1.4em + 30px) * -1) + bottom: 0 + + .td.with-costs, .with-costs .td + padding-top: 15px + padding-bottom: 15px + + & > div + height: calc(100% + 15px) + &.headlined + &:before + padding-top: 15px + padding-bottom: 15px + height: calc(1.4em + 30px) + margin-top: -15px + margin-bottom: 15px diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb index 5fe78766c..4c698bb89 100644 --- a/app/controllers/journey_patterns_collections_controller.rb +++ b/app/controllers/journey_patterns_collections_controller.rb @@ -17,35 +17,46 @@ 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) @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 do + @journey_patterns = @journey_patterns.includes(stop_points: {stop_area: :stop_area_referential}) + end + format.html do + @stop_points_list = [] + route.stop_points.includes(:stop_area).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/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb index ad08699a5..8445fc423 100644 --- a/app/controllers/referential_vehicle_journeys_controller.rb +++ b/app/controllers/referential_vehicle_journeys_controller.rb @@ -12,6 +12,7 @@ class ReferentialVehicleJourneysController < ChouetteController def collection @q ||= end_of_association_chain.ransack(params[:q]) @vehicle_journeys ||= @q.result.includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: 10 + @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct end end diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js index 1c2eb68b2..a70a2e6f2 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,16 +199,13 @@ const actions = { } }) } - journeyPatterns.push({ - name: val.name, - object_id: val.object_id, - short_id: val.short_id, - checksum: val.checksum, - published_name: val.published_name, - registration_number: val.registration_number, - stop_points: val.route_short_description.stop_points, - deletable: false - }) + journeyPatterns.push( + _.assign({}, val, { + stop_points: val.route_short_description.stop_points, + costs: val.costs || {}, + deletable: false + }) + ) } } window.currentItemsLength = journeyPatterns.length diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js index d4c9816ec..69eff978e 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]: parseFloat(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" step="0.01" 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..1e391b0c2 100644 --- a/app/javascript/journey_patterns/components/JourneyPatterns.js +++ b/app/javascript/journey_patterns/components/JourneyPatterns.js @@ -54,6 +54,10 @@ export default class JourneyPatterns extends Component { } } + hasFeature(key) { + return this.props.status.features[key] + } + cityNameChecker(sp) { let bool = false if(sp.city_name != this.previousCity){ @@ -115,7 +119,7 @@ export default class JourneyPatterns extends Component { </div> {this.props.stopPointsList.map((sp, i) =>{ return ( - <div key={i} className='td'> + <div key={i} className={'td' + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}> {this.cityNameChecker(sp)} </div> ) @@ -131,6 +135,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 +157,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/models/referential.rb b/app/models/referential.rb index a5d5acbf9..4cddd502e 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -156,7 +156,7 @@ class Referential < ActiveRecord::Base def stop_points Chouette::StopPoint.all end - + def compliance_check_sets ComplianceCheckSet.all end @@ -356,7 +356,13 @@ class Referential < ActiveRecord::Base end Rails.logger.info("Schema create benchmark: '#{slug}'\t#{report}") - Rails.logger.error( "Schema migrations count for Referential #{slug} " + Referential.connection.select_value("select count(*) from #{slug}.schema_migrations;").to_s ) + Rails.logger.info("Schema migrations count for Referential #{slug}: #{migration_count || '-'}") + end + end + + def migration_count + if self.class.connection.table_exists?("#{slug}.schema_migrations") + self.class.connection.select_value("select count(*) from #{slug}.schema_migrations;") end end diff --git a/app/views/api/v1/journey_patterns/show.rabl b/app/views/api/v1/journey_patterns/show.rabl index 86876f3fb..815b1cf0b 100644 --- a/app/views/api/v1/journey_patterns/show.rabl +++ b/app/views/api/v1/journey_patterns/show.rabl @@ -5,16 +5,21 @@ 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 node(:vehicle_journey_object_ids) do |journey_pattern| - journey_pattern.vehicle_journeys.map(&:objectid) + journey_pattern.vehicle_journeys.pluck(:objectid) end unless root_object.vehicle_journeys.empty? child :stop_points => :stop_area_short_descriptions do |stop_points| node do |stop_point| + cache stop_point.stop_area_id partial("api/v1/stop_areas/short_description", :object => stop_point.stop_area) end end diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim index 963da8cea..6d22e1378 100644 --- a/app/views/referential_vehicle_journeys/_filters.html.slim +++ b/app/views/referential_vehicle_journeys/_filters.html.slim @@ -5,6 +5,10 @@ span.input-group-btn button.btn.btn-default#search-btn type='submit' span.fa.fa-search + .ffg-row + .form-group.togglable + = f.label Chouette::VehicleJourney.human_attribute_name(:company_id), required: false, class: 'control-label' + = f.input :company_id_eq_any, collection: @all_companies.select(:id, :name).order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} .actions = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link' |
