diff options
| author | Luc Donnet | 2018-01-03 11:57:42 +0100 |
|---|---|---|
| committer | Luc Donnet | 2018-01-03 11:57:42 +0100 |
| commit | 7cce4762c11e7d1e78433f6f88d2e12928c398dc (patch) | |
| tree | 3084d95692a70f9c5d5a842aae6f4ec0ea07a1c3 /app | |
| parent | 6497b23e18385121974f6cbf56d48caf897e69b1 (diff) | |
| parent | 414d0f6c4dd992696354757c4ae700952a7e4dd9 (diff) | |
| download | chouette-core-7cce4762c11e7d1e78433f6f88d2e12928c398dc.tar.bz2 | |
Merge branch 'master' into 5024-prevent-duplicate-referentials-from-being-created-during-parallel-db-transactions--rb201711271659
Diffstat (limited to 'app')
162 files changed, 2466 insertions, 947 deletions
diff --git a/app/assets/javascripts/forms.coffee b/app/assets/javascripts/forms.coffee index 12d82fef1..b7ae3c6ca 100644 --- a/app/assets/javascripts/forms.coffee +++ b/app/assets/javascripts/forms.coffee @@ -32,14 +32,25 @@ isEdge = !isIE && !!window.StyleMedia @colorSelector = -> $('.form-group .dropdown.color_selector').each -> - selectedStatus = $(this).children('.dropdown-toggle').children('.fa-circle') - + selectedStatusColor = $(this).children('.dropdown-toggle').children('.fa-circle') + selectedStatusLabel = $(this).children('.dropdown-toggle') + self = this $(this).on 'click', "input[type='radio']", (e) -> selectedValue = e.currentTarget.value + selectedText = $(e.currentTarget).parent()[0].textContent + if e.currentTarget.getAttribute("data-for") + hidden = $("[name=\"#{e.currentTarget.getAttribute("data-for")}\"]") + if selectedValue == '' - $(selectedStatus).css('color', 'transparent') + $(selectedStatusColor).css('color', 'transparent') + $(selectedStatusLabel).contents().filter( -> this.nodeType == 3 ).filter(':first').text = "" + hidden?.val "" else - $(selectedStatus).css('color', selectedValue) + $(selectedStatusColor).css('color', selectedValue) + $(selectedStatusLabel).contents().filter( -> this.nodeType == 3 ).first().replaceWith selectedText + hidden?.val selectedValue + + $(self).find('.dropdown-toggle').click() $ -> togglableFilter() diff --git a/app/assets/javascripts/main_menu.coffee b/app/assets/javascripts/main_menu.coffee index a12c47576..e943f448a 100644 --- a/app/assets/javascripts/main_menu.coffee +++ b/app/assets/javascripts/main_menu.coffee @@ -1,12 +1,11 @@ $ -> - link = [] + stickyActions = [] ptitleCont = "" $(document).on 'page:before-change', -> - link = [] + stickyActions = [] ptitleCont = "" - $el = $('#main_nav') # Opening/closing left-side menu $el.find('.openMenu').on 'click', (e) -> @@ -24,34 +23,45 @@ $ -> limit = 51 if $(window).scrollTop() >= limit - if ($('.page-action .small').length > 0) - data = $('.page-action .small')[0].innerHTML + if stickyActions.length == 0 + if ($('.page-action .small').length > 0) + stickyActions.push + content: [ + $('.page-action .small'), + $('.page-action .small').first().next() + ] + originalParent: $('.page-action .small').parent() + + for action in $(".sticky-action, .sticky-actions") + stickyActions.push + class: "small", + content: [$(action)] + originalParent: $(action).parent() - if ($(".page-title").length > 0) + if $(".page-title").length > 0 ptitleCont = $(".page-title").html() - stickyContent = '<div class="sticky-content">' - stickyContent += '<div class="sticky-ptitle">' + ptitleCont + '</div>' - stickyContent += '<div class="sticky-paction"><div class="small">' + data + '</div></div>' - stickyContent += '</div>' + stickyContent = $('<div class="sticky-content"></div>') + stickyContent.append $("<div class='sticky-ptitle'>#{ptitleCont}</div>") + stickyContent.append $('<div class="sticky-paction"></div>') $('#main_nav').addClass 'sticky' if $('#menu_top').find('.sticky-content').length == 0 if ptitleCont.length > 0 $('#menu_top').children('.menu-content').after(stickyContent) - if link.length == 0 - link = $('.page-action .small').next() - - $('.sticky-paction .small').after(link) + for item in stickyActions + for child in item.content + child.appendTo $('.sticky-paction') else $('#main_nav').removeClass 'sticky' if $('#menu_top').find('.sticky-content').length > 0 - if !$('.page-action').find('.formSubmitr').length - $('.page-action .small').after(link) + for item in stickyActions + for child in item.content + child.appendTo item.originalParent $('.sticky-content').remove() - sticker(); + sticker() # Sticky behavior $(document).on 'scroll', sticker diff --git a/app/assets/javascripts/select2.coffee b/app/assets/javascripts/select2.coffee index 2e3884d7f..4cf5f42d0 100644 --- a/app/assets/javascripts/select2.coffee +++ b/app/assets/javascripts/select2.coffee @@ -9,24 +9,27 @@ bind_select2 = (el, cfg = {}) -> target.select2 $.extend({}, default_cfg, cfg) bind_select2_ajax = (el, cfg = {}) -> - target = $(el) + _this = $(el) cfg = ajax: data: (params) -> - q: - "#{target.data('term')}": params.term - url: target.data('url'), + if _this.data('term') + { q: "#{_this.data('term')}": params.term } + else + { q: params.term } + url: _this.data('url'), dataType: 'json', delay: 125, processResults: (data, params) -> results: data - minimumInputLength: 1 - placeholder: target.data('select2ed-placeholder') templateResult: (item) -> item.text templateSelection: (item) -> item.text escapeMarkup: (markup) -> markup + initSelection : (item, callback) -> + if _this.data('initvalue') + callback(_this.data('initvalue')) bind_select2(el, cfg) @@ -40,7 +43,5 @@ bind_select2_ajax = (el, cfg = {}) -> $('select.form-control.tags').each -> bind_select2(this, {tags: true}) - - $ -> select_2() diff --git a/app/assets/stylesheets/_layout.sass b/app/assets/stylesheets/_layout.sass index b6b91b2a5..340467e77 100644 --- a/app/assets/stylesheets/_layout.sass +++ b/app/assets/stylesheets/_layout.sass @@ -28,6 +28,7 @@ body // width: 75% border-bottom: 1px solid rgba($blue, 0.5) margin: 30px auto 45px auto + clear: both .content_header font-size: 2.2rem diff --git a/app/assets/stylesheets/base/_config.sass b/app/assets/stylesheets/base/_config.sass index 65444479f..ec1c43e7f 100644 --- a/app/assets/stylesheets/base/_config.sass +++ b/app/assets/stylesheets/base/_config.sass @@ -16,6 +16,7 @@ $blue: #007fbb $darkgrey: #4b4b4b $grey: #a4a4a4 +$lightgrey: rgba($grey, 0.15) $green: #70b12b $red: #da2f36 diff --git a/app/assets/stylesheets/components/_breadcrumb.sass b/app/assets/stylesheets/components/_breadcrumb.sass index 62f167eb4..1b30ca42b 100644 --- a/app/assets/stylesheets/components/_breadcrumb.sass +++ b/app/assets/stylesheets/components/_breadcrumb.sass @@ -1,3 +1,6 @@ .breadcrumbs + white-space: nowrap + text-overflow: ellipsis + overflow: hidden a - color: white
\ No newline at end of file + color: white diff --git a/app/assets/stylesheets/components/_buttons.sass b/app/assets/stylesheets/components/_buttons.sass index a59699383..a649a07ef 100644 --- a/app/assets/stylesheets/components/_buttons.sass +++ b/app/assets/stylesheets/components/_buttons.sass @@ -165,6 +165,13 @@ table, .table .fa:first-child margin-right: 0.5em + & + li.delete-action + > a, > button + margin-top: 0 + &:before + display: none + + &.table-2entries .t2e-item > .th position: relative diff --git a/app/assets/stylesheets/components/_color_selector.sass b/app/assets/stylesheets/components/_color_selector.sass new file mode 100644 index 000000000..07bfa0c80 --- /dev/null +++ b/app/assets/stylesheets/components/_color_selector.sass @@ -0,0 +1,21 @@ +select.color_selector + option[value='#9B9B9B'] + background-color: #9B9B9B + option[value='#FFA070'] + background-color: #FFA070 + option[value='#C67300'] + background-color: #C67300 + option[value='#7F551B'] + background-color: #7F551B + option[value='#41CCE3'] + background-color: #41CCE3 + option[value='#09B09C'] + background-color: #09B09C + option[value='#3655D7'] + background-color: #3655D7 + option[value='#6321A0'] + background-color: #6321A0 + option[value='#E796C6'] + background-color: #E796C6 + option[value='#DD2DAA'] + background-color: #DD2DAA
\ No newline at end of file diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass index 9a363ab97..47faf19b1 100644 --- a/app/assets/stylesheets/components/_forms.sass +++ b/app/assets/stylesheets/components/_forms.sass @@ -229,6 +229,13 @@ $cbx-size-xs: 15px &[type='checkbox']:checked + label:before background-color: $blue + &[type='checkbox']:disabled + label + &:before + border-color: $grey + background-color: $lightgrey + cursor: not-allowed + &:after + display: none // Table adjustments table, .table .td, td, .th, th diff --git a/app/assets/stylesheets/components/_lists.sass b/app/assets/stylesheets/components/_lists.sass index d8f83d72b..3cce20021 100644 --- a/app/assets/stylesheets/components/_lists.sass +++ b/app/assets/stylesheets/components/_lists.sass @@ -54,3 +54,8 @@ $dlWidth: 40% // Definition .dl-def width: 100% - $dlWidth + + ul + list-style: none + padding-left: 0 + margin-bottom: 0
\ No newline at end of file diff --git a/app/assets/stylesheets/components/_main_nav.sass b/app/assets/stylesheets/components/_main_nav.sass index fdbf5836a..f102c4617 100644 --- a/app/assets/stylesheets/components/_main_nav.sass +++ b/app/assets/stylesheets/components/_main_nav.sass @@ -17,6 +17,9 @@ $menuW: 300px line-height: $menuH padding-left: 10px opacity: 0.6 + > a + color: rgba(#fff, 0.9) + text-decoration: none #menu_left position: absolute @@ -240,6 +243,9 @@ $menuW: 300px left: 0 top: 13px + & > .menu-item + max-width: 75% + .menu-item padding: 0 10px @@ -321,8 +327,11 @@ $menuW: 300px height: $menuH * 2 transition: 0.1s - #menu_top > .menu-content > .menu-item-group - display: none + #menu_top > .menu-content + & > .menu-item + max-width: 90% + & > .menu-item-group + display: none .sticky-content height: $menuH @@ -342,16 +351,16 @@ $menuW: 300px white-space: nowrap max-height: 1.1em margin: 0 -1.4em 0 0 - padding: 0 1.4em 0 0 + padding: 0 1.4em 5px 0 overflow: hidden &:before content: '[...]' font-size: 0.65em position: absolute - z-index: 5 + z-index: 6 right: 0 - bottom: 4px + bottom: 9px &:after content: '' diff --git a/app/assets/stylesheets/components/_panels.sass b/app/assets/stylesheets/components/_panels.sass index e9f615081..ab25d8184 100644 --- a/app/assets/stylesheets/components/_panels.sass +++ b/app/assets/stylesheets/components/_panels.sass @@ -34,6 +34,7 @@ a text-decoration: none color: $blue + text-transform: capitalize &:hover, &:focus color: $darkblue diff --git a/app/assets/stylesheets/components/_referentials.sass b/app/assets/stylesheets/components/_referentials.sass new file mode 100644 index 000000000..0bbb18f2b --- /dev/null +++ b/app/assets/stylesheets/components/_referentials.sass @@ -0,0 +1,4 @@ +#referential_form + .metadatas-errors + margin-top: -20px + margin-bottom: 20px diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass index 178ec2f36..119a38c07 100644 --- a/app/assets/stylesheets/components/_tables.sass +++ b/app/assets/stylesheets/components/_tables.sass @@ -211,6 +211,18 @@ top: 50% margin-top: -8px + .zdlp + background: url( image-path('map/zdlp.png') ) no-repeat left 50% + padding-left: 30px + + .lda + background: url( image-path('map/lda.png') ) no-repeat left 50% + padding-left: 30px + + .gdl + background: url( image-path('map/lda.png') ) no-repeat left 50% + padding-left: 30px + // select_toolbox .select_toolbox diff --git a/app/assets/stylesheets/modules/import_messages.sass b/app/assets/stylesheets/modules/import_messages.sass index e5666cbcd..6a088dc06 100644 --- a/app/assets/stylesheets/modules/import_messages.sass +++ b/app/assets/stylesheets/modules/import_messages.sass @@ -2,4 +2,42 @@ .status_icon padding-right: 20px h1 - padding-bottom: 20px + padding-bottom: 20px + + +.import_message-list + padding-bottom: 20px + + .import_messages-head + display: block + font-size: 1.8rem + font-weight: 700 + border-bottom: 2px solid #4b4b4b + padding: 5px 15px 6px 15px + + dl + dd, dt + display: inline-block + letter-spacing: normal + word-spacing: normal + text-rendering: auto + vertical-align: top + padding: 5px 15px 6px 15px + + dt + position: relative + width: 7% + font-weight: 700 + + &:before + content: "" + display: block + position: absolute + z-index: 1 + top: 0 + left: 0 + width: 250% + border-bottom: 1px solid rgba(164, 164, 164, 0.5) + + dd + width: 93%
\ No newline at end of file diff --git a/app/assets/stylesheets/typography/_sboiv.sass b/app/assets/stylesheets/typography/_sboiv.sass index 6c82a5739..f0943f843 100644 --- a/app/assets/stylesheets/typography/_sboiv.sass +++ b/app/assets/stylesheets/typography/_sboiv.sass @@ -52,6 +52,8 @@ &.sb-5x font-size: 5em + &.sb-strong + font-weight: bold .sb-ZDLR:before content: '\e904' @@ -80,7 +82,7 @@ .sb-network:before content: '\e90c' -.sb-compliance-check-set:before +.sb-compliance_check_set:before, .sb-import_resource:before content: '\e90d' .sb-OAT:before @@ -89,7 +91,7 @@ .sb-OAS:before content: '\e90f' -.sb-calendar:before +.sb-calendar:before, .sb-purchase_window:before content: '\e910' .sb-journey_pattern:before @@ -104,7 +106,7 @@ .sb-LDA:before content: '\e914' -.sb-referential:before +.sb-referential:before, .sb-workbench:before content: '\e915' .sb-compliance_control_set:before diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 97f5548ae..474277da1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,7 @@ class ApplicationController < ActionController::Base include PaperTrailSupport include Pundit + include FeatureChecker rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized diff --git a/app/controllers/autocomplete_purchase_windows_controller.rb b/app/controllers/autocomplete_purchase_windows_controller.rb new file mode 100644 index 000000000..70dc5a346 --- /dev/null +++ b/app/controllers/autocomplete_purchase_windows_controller.rb @@ -0,0 +1,12 @@ +class AutocompletePurchaseWindowsController < ChouetteController + respond_to :json, :only => [:index] + + requires_feature :purchase_windows + + include ReferentialSupport + + protected + def collection + @purchase_windows = referential.purchase_windows.search(params[:q]).result.paginate(page: params[:page]) + end +end diff --git a/app/controllers/autocomplete_stop_areas_controller.rb b/app/controllers/autocomplete_stop_areas_controller.rb index 233012028..d82fa316a 100644 --- a/app/controllers/autocomplete_stop_areas_controller.rb +++ b/app/controllers/autocomplete_stop_areas_controller.rb @@ -17,13 +17,20 @@ class AutocompleteStopAreasController < ChouetteController scope = scope.possible_parents if relation_parent? scope = scope.possible_parents if relation_children? end + if search_scope.present? + scope = StopAreaPolicy::Scope.new(current_user, scope).search_scope(search_scope) + end args = [].tap{|arg| 4.times{arg << "%#{params[:q]}%"}} - @stop_areas = scope.where("name ILIKE ? OR city_name ILIKE ? OR registration_number ILIKE ? OR objectid ILIKE ?", *args).limit(50) + @stop_areas = scope.where("unaccent(name) ILIKE unaccent(?) OR unaccent(city_name) ILIKE unaccent(?) OR registration_number ILIKE ? OR objectid ILIKE ?", *args).limit(50) @stop_areas end def target_type? - params.has_key?( :target_type) + params.has_key?(:target_type) + end + + def search_scope + params[:scope] end def relation_parent? diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index 2ed10a111..4a752f2b9 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -19,7 +19,7 @@ class CalendarsController < ChouetteController private def calendar_params - permitted_params = [:id, :name, :short_name, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]] + permitted_params = [:id, :name, :short_name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]] permitted_params << :shared if policy(Calendar).share? params.require(:calendar).permit(*permitted_params) end diff --git a/app/controllers/companies_controller.rb b/app/controllers/companies_controller.rb index 931d846c5..f84252920 100644 --- a/app/controllers/companies_controller.rb +++ b/app/controllers/companies_controller.rb @@ -61,6 +61,10 @@ class CompaniesController < ChouetteController alias_method :current_referential, :line_referential helper_method :current_referential + def begin_of_association_chain + current_organisation + end + def company_params params.require(:company).permit( :objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone ) end diff --git a/app/controllers/concerns/activatable.rb b/app/controllers/concerns/activatable.rb new file mode 100644 index 000000000..1a34551a9 --- /dev/null +++ b/app/controllers/concerns/activatable.rb @@ -0,0 +1,11 @@ +module Activatable + extend ActiveSupport::Concern + + %w(activate deactivate).each do |action| + define_method action do + authorize resource, "#{action}?" + resource.send "#{action}!" + redirect_to request.referer || [current_referential, resource] + end + end +end diff --git a/app/controllers/concerns/feature_checker.rb b/app/controllers/concerns/feature_checker.rb new file mode 100644 index 000000000..9ca5ed0a7 --- /dev/null +++ b/app/controllers/concerns/feature_checker.rb @@ -0,0 +1,42 @@ +# Check availability of optional features +# +# In your controller, use : +# +# requires_feature :test +# requires_feature :test, only: [:show] +# +# In your view, use : +# +# has_feature? :test +# +module FeatureChecker + extend ActiveSupport::Concern + + module ClassMethods + def requires_feature(feature, options = {}) + before_action options do + check_feature! feature + end + end + end + + included do + helper_method :has_feature? + end + + protected + + def has_feature?(*features) + features.all? do |feature| + current_organisation.has_feature? feature + end + end + + def check_feature!(*features) + unless has_feature?(*features) + raise NotAuthorizedError, "Feature not autorized" + end + end + + class NotAuthorizedError < StandardError; end +end diff --git a/app/controllers/group_of_lines_controller.rb b/app/controllers/group_of_lines_controller.rb index 5762108dc..46d9d077f 100644 --- a/app/controllers/group_of_lines_controller.rb +++ b/app/controllers/group_of_lines_controller.rb @@ -42,7 +42,6 @@ class GroupOfLinesController < ChouetteController end end - protected def filtered_group_of_lines_maps @@ -70,6 +69,10 @@ class GroupOfLinesController < ChouetteController alias_method :line_referential, :parent + def begin_of_association_chain + current_organisation + end + private def group_of_line_params diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb index 736fb1441..5fe78766c 100644 --- a/app/controllers/journey_patterns_collections_controller.rb +++ b/app/controllers/journey_patterns_collections_controller.rb @@ -49,6 +49,7 @@ class JourneyPatternsCollectionsController < ChouetteController end def user_permissions + @features = Hash[*current_organisation.features.map{|f| [f, true]}.flatten].to_json policy = policy(:journey_pattern) @perms = %w{create destroy update}.inject({}) do | permissions, action | diff --git a/app/controllers/line_referentials_controller.rb b/app/controllers/line_referentials_controller.rb index 39c2cdb89..03dab3f8f 100644 --- a/app/controllers/line_referentials_controller.rb +++ b/app/controllers/line_referentials_controller.rb @@ -3,6 +3,7 @@ class LineReferentialsController < ChouetteController defaults :resource_class => LineReferential def sync + authorize resource, :synchronize? @sync = resource.line_referential_syncs.build if @sync.save flash[:notice] = t('notice.line_referential_sync.created') diff --git a/app/controllers/lines_controller.rb b/app/controllers/lines_controller.rb index 2f0ef1542..7041a3a26 100644 --- a/app/controllers/lines_controller.rb +++ b/app/controllers/lines_controller.rb @@ -1,6 +1,8 @@ class LinesController < ChouetteController include ApplicationHelper + include Activatable include PolicyChecker + defaults :resource_class => Chouette::Line respond_to :html respond_to :xml @@ -112,6 +114,10 @@ class LinesController < ChouetteController alias_method :current_referential, :line_referential helper_method :current_referential + def begin_of_association_chain + current_organisation + end + def line_params params.require(:line).permit( :transport_mode, @@ -135,6 +141,8 @@ class LinesController < ChouetteController :color, :text_color, :stable_id, + :transport_submode, + :secondary_company_ids => [], footnotes_attributes: [:code, :label, :_destroy, :id] ) end diff --git a/app/controllers/networks_controller.rb b/app/controllers/networks_controller.rb index 494d1e69f..79a5eb97b 100644 --- a/app/controllers/networks_controller.rb +++ b/app/controllers/networks_controller.rb @@ -71,6 +71,10 @@ class NetworksController < ChouetteController alias_method :current_referential, :line_referential helper_method :current_referential + def begin_of_association_chain + current_organisation + end + def network_params params.require(:network).permit(:objectid, :object_version, :version_date, :description, :name, :registration_number, :source_name, :source_type_name, :source_identifier, :comment ) end diff --git a/app/controllers/purchase_windows_controller.rb b/app/controllers/purchase_windows_controller.rb new file mode 100644 index 000000000..04b5736bb --- /dev/null +++ b/app/controllers/purchase_windows_controller.rb @@ -0,0 +1,75 @@ +class PurchaseWindowsController < ChouetteController + include ReferentialSupport + include RansackDateFilter + include PolicyChecker + before_action :ransack_contains_date, only: [:index] + defaults :resource_class => Chouette::PurchaseWindow, collection_name: 'purchase_windows', instance_name: 'purchase_window' + belongs_to :referential + + requires_feature :purchase_windows + + def index + index! do + @purchase_windows = decorate_purchase_windows(@purchase_windows) + end + end + + def show + show! do + @purchase_window = @purchase_window.decorate(context: { + referential: @referential + }) + end + end + + protected + + def create_resource(purchase_window) + purchase_window.referential = @referential + super + end + + private + + def purchase_window_params + params.require(:purchase_window).permit(:id, :name, :color, :referential_id, periods_attributes: [:id, :begin, :end, :_destroy]) + end + + def decorate_purchase_windows(purchase_windows) + ModelDecorator.decorate( + purchase_windows, + with: PurchaseWindowDecorator, + context: { + referential: @referential + } + ) + end + + def sort_column + Chouette::PurchaseWindow.column_names.include?(params[:sort]) ? params[:sort] : 'name' + end + + def sort_direction + %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc' + end + + def collection + return @purchase_windows if @purchase_windows + @q = Chouette::PurchaseWindow.ransack(params[:q]) + + purchase_windows = @q.result + purchase_windows = purchase_windows.order(sort_column + ' ' + sort_direction) if sort_column && sort_direction + @purchase_windows = purchase_windows.paginate(page: params[:page]) + end + + def ransack_contains_date + date =[] + if params[:q] && params[:q]['contains_date(1i)'].present? + ['contains_date(1i)', 'contains_date(2i)', 'contains_date(3i)'].each do |key| + date << params[:q][key].to_i + params[:q].delete(key) + end + params[:q]['contains_date'] = @date = Date.new(*date) rescue nil + end + end +end diff --git a/app/controllers/referential_companies_controller.rb b/app/controllers/referential_companies_controller.rb index ca1ff67db..7e65a72cf 100644 --- a/app/controllers/referential_companies_controller.rb +++ b/app/controllers/referential_companies_controller.rb @@ -35,7 +35,8 @@ class ReferentialCompaniesController < ChouetteController def collection scope = referential.line_referential.companies if params[:line_id] - scope = referential.line_referential.lines.find(params[:line_id]).companies + line_scope = referential.line_referential.lines.find(params[:line_id]).companies + scope = line_scope if line_scope.exists? end @q = scope.search(params[:q]) diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb new file mode 100644 index 000000000..ad08699a5 --- /dev/null +++ b/app/controllers/referential_vehicle_journeys_controller.rb @@ -0,0 +1,17 @@ +# +# Browse all VehicleJourneys of the Referential +# +class ReferentialVehicleJourneysController < ChouetteController + include ReferentialSupport + defaults :resource_class => Chouette::VehicleJourney, collection_name: :vehicle_journeys + + requires_feature :referential_vehicle_journeys + + private + + 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 + end + +end diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 227651a59..83e3bc56a 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -13,11 +13,16 @@ class ReferentialsController < ChouetteController end def create - create! do |format| - build_referential - - if !!@referential.created_from_id - format.html { redirect_to workbench_path(@referential.workbench) } + create! do |success, failure| + success.html do + if @referential.created_from_id.present? + flash[:notice] = t('notice.referentials.duplicate') + end + redirect_to workbench_path(@referential.workbench) + end + failure.html do + Rails.logger.info "Can't create Referential : #{@referential.errors.inspect}" + render :new end end end @@ -60,7 +65,7 @@ class ReferentialsController < ChouetteController def validate ComplianceControlSetCopyWorker.perform_async(params[:compliance_control_set], params[:id]) - flash[:notice] = I18n.t("referentials.operation_in_progress") + flash[:notice] = t('notice.referentials.validate') redirect_to(referential_path) end diff --git a/app/controllers/stop_area_referentials_controller.rb b/app/controllers/stop_area_referentials_controller.rb index 85541230d..f2d375e49 100644 --- a/app/controllers/stop_area_referentials_controller.rb +++ b/app/controllers/stop_area_referentials_controller.rb @@ -2,6 +2,7 @@ class StopAreaReferentialsController < ChouetteController defaults :resource_class => StopAreaReferential def sync + authorize resource, :synchronize? @sync = resource.stop_area_referential_syncs.build if @sync.save flash[:notice] = t('notice.stop_area_referential_sync.created') diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb index 133518324..5243ce56c 100644 --- a/app/controllers/stop_areas_controller.rb +++ b/app/controllers/stop_areas_controller.rb @@ -1,5 +1,6 @@ class StopAreasController < ChouetteController include ApplicationHelper + include Activatable defaults :resource_class => Chouette::StopArea @@ -13,10 +14,12 @@ class StopAreasController < ChouetteController respond_to :html, :kml, :xml, :json respond_to :js, :only => :index - # def complete - # @stop_areas = line.stop_areas - # render :layout => false - # end + def autocomplete + scope = stop_area_referential.stop_areas.where(deleted_at: nil) + args = [].tap{|arg| 4.times{arg << "%#{params[:q]}%"}} + @stop_areas = scope.where("unaccent(name) ILIKE unaccent(?) OR unaccent(city_name) ILIKE unaccent(?) OR registration_number ILIKE ? OR objectid ILIKE ?", *args).limit(50) + @stop_areas + end def select_parent @stop_area = stop_area @@ -154,6 +157,10 @@ class StopAreasController < ChouetteController end end + def begin_of_association_chain + current_organisation + end + private def sort_column @@ -171,7 +178,35 @@ class StopAreasController < ChouetteController helper_method :current_referential def stop_area_params - params.require(:stop_area).permit( :routing_stop_ids, :routing_line_ids, :children_ids, :stop_area_type, :parent_id, :objectid, :object_version, :name, :comment, :area_type, :registration_number, :nearest_topic_name, :fare_code, :longitude, :latitude, :long_lat_type, :country_code, :street_name, :zip_code, :city_name, :mobility_restricted_suitability, :stairs_availability, :lift_availability, :int_user_needs, :coordinates, :url, :time_zone ) + params.require(:stop_area).permit( + :area_type, + :children_ids, + :city_name, + :comment, + :coordinates, + :country_code, + :fare_code, + :int_user_needs, + :latitude, + :lift_availability, + :long_lat_type, + :longitude, + :mobility_restricted_suitability, + :name, + :nearest_topic_name, + :object_version, + :objectid, + :parent_id, + :registration_number, + :routing_line_ids, + :routing_stop_ids, + :stairs_availability, + :street_name, + :time_zone, + :url, + :waiting_time, + :zip_code, + ) end end diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index c941aeae4..c03db0c7f 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -40,43 +40,46 @@ class VehicleJourneysController < ChouetteController end def index - @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 - - @transport_mode = route.line['transport_mode'] - @transport_submode = route.line['transport_submode'] - - if params[:jp] - @jp_origin = Chouette::JourneyPattern.find_by(objectid: params[:jp]) - @jp_origin_stop_points = @jp_origin.stop_points - end - - index! do + index! do |format| if collection.out_of_bounds? redirect_to params.merge(:page => 1) end + format.json do + @vehicle_journeys = @vehicle_journeys.includes({stop_points: :stop_area}) + end + format.html do + @stop_points_list = [] + @stop_points_list = route.stop_points.includes(:stop_area).map do |sp| + { + :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 + @transport_mode = route.line['transport_mode'] + @transport_submode = route.line['transport_submode'] + + if params[:jp] + @jp_origin = Chouette::JourneyPattern.find_by(objectid: params[:jp]) + @jp_origin_stop_points = @jp_origin.stop_points + end + end end end @@ -92,13 +95,15 @@ class VehicleJourneysController < ChouetteController scope = maybe_filter_by_departure_time(scope) scope = maybe_filter_out_journeys_with_time_tables(scope) - @q = scope.search filtered_ransack_params + @vehicle_journeys ||= begin + @q = scope.search filtered_ransack_params - @ppage = 20 - @vehicle_journeys = @q.result.paginate(:page => params[:page], :per_page => @ppage) - @footnotes = route.line.footnotes.to_json - @matrix = resource_class.matrix(@vehicle_journeys) - @vehicle_journeys + @ppage = 20 + vehicle_journeys = @q.result.paginate(:page => params[:page], :per_page => @ppage) + @footnotes = route.line.footnotes.to_json + @matrix = resource_class.matrix(vehicle_journeys) + vehicle_journeys + end end def maybe_filter_by_departure_time(scope) @@ -159,6 +164,7 @@ class VehicleJourneysController < ChouetteController end def user_permissions + @features = Hash[*current_organisation.features.map{|f| [f, true]}.flatten].to_json policy = policy(:vehicle_journey) @perms = %w{create destroy update}.inject({}) do | permissions, action | diff --git a/app/decorators/company_decorator.rb b/app/decorators/company_decorator.rb index 9416c73ae..50b82d276 100644 --- a/app/decorators/company_decorator.rb +++ b/app/decorators/company_decorator.rb @@ -18,13 +18,6 @@ class CompanyDecorator < Draper::Decorator def action_links links = [] - if h.policy(Chouette::Company).create? - links << Link.new( - content: h.t('companies.actions.new'), - href: h.new_line_referential_company_path(context[:referential]) - ) - end - if h.policy(object).update? links << Link.new( content: h.t('companies.actions.edit'), @@ -37,7 +30,7 @@ class CompanyDecorator < Draper::Decorator if h.policy(object).destroy? links << Link.new( - content: t('companies.actions.destroy'), + content: h.destroy_link_content('companies.actions.destroy'), href: h.line_referential_company_path( context[:referential], object diff --git a/app/decorators/compliance_check_set_decorator.rb b/app/decorators/compliance_check_set_decorator.rb index 096596b19..c58e14d88 100644 --- a/app/decorators/compliance_check_set_decorator.rb +++ b/app/decorators/compliance_check_set_decorator.rb @@ -3,15 +3,6 @@ class ComplianceCheckSetDecorator < Draper::Decorator def action_links links = [] - - links << Link.new( - content: h.destroy_link_content, - href: h.workbench_compliance_check_sets_path(object.id), - method: :delete, - data: {confirm: h.t('imports.actions.destroy_confirm')} - ) - - links end def lines_status diff --git a/app/decorators/line_decorator.rb b/app/decorators/line_decorator.rb index ede670cbd..9c0cf7292 100644 --- a/app/decorators/line_decorator.rb +++ b/app/decorators/line_decorator.rb @@ -18,7 +18,8 @@ class LineDecorator < Draper::Decorator links << Link.new( content: h.t('lines.actions.show_company'), - href: [context[:line_referential], object.company] + href: [context[:line_referential], object.company], + disabled: object.company.nil? ) if h.policy(Chouette::Line).create? && @@ -41,6 +42,26 @@ class LineDecorator < Draper::Decorator ) end + if h.policy(object).deactivate? + links << Link.new( + content: h.deactivate_link_content('lines.actions.deactivate'), + href: h.deactivate_line_referential_line_path(context[:line_referential], object), + method: :put, + data: {confirm: h.t('lines.actions.deactivate_confirm')}, + extra_class: "delete-action" + ) + end + + if h.policy(object).activate? + links << Link.new( + content: h.activate_link_content('lines.actions.activate'), + href: h.activate_line_referential_line_path(context[:line_referential], object), + method: :put, + data: {confirm: h.t('lines.actions.activate_confirm')}, + extra_class: "delete-action" + ) + end + if h.policy(object).destroy? links << Link.new( content: h.destroy_link_content('lines.actions.destroy'), diff --git a/app/decorators/purchase_window_decorator.rb b/app/decorators/purchase_window_decorator.rb new file mode 100644 index 000000000..646fdea0d --- /dev/null +++ b/app/decorators/purchase_window_decorator.rb @@ -0,0 +1,34 @@ +class PurchaseWindowDecorator < Draper::Decorator + decorates Chouette::PurchaseWindow + delegate_all + + def action_links + policy = h.policy(object) + links = [] + + if policy.update? + links << Link.new( + content: I18n.t('actions.edit'), + href: h.edit_referential_purchase_window_path(context[:referential].id, object) + ) + end + + if policy.destroy? + links << Link.new( + content: I18n.t('actions.destroy'), + href: h.referential_purchase_window_path(context[:referential].id, object), + method: :delete, + data: { confirm: h.t('purchase_windows.actions.destroy_confirm') } + ) + end + + links + end + + def bounding_dates + unless object.date_ranges.empty? + object.date_ranges.map(&:min).min..object.date_ranges.map(&:max).max + end + end + +end diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb index 4103790aa..d75ad1050 100644 --- a/app/decorators/referential_decorator.rb +++ b/app/decorators/referential_decorator.rb @@ -3,12 +3,26 @@ class ReferentialDecorator < Draper::Decorator def action_links policy = h.policy(object) - links = [ - Link.new( - content: h.t('time_tables.index.title'), - href: h.referential_time_tables_path(object) + links = [] + + if has_feature?(:referential_vehicle_journeys) + links << Link.new( + content: h.t('referential_vehicle_journeys.index.title'), + href: h.referential_vehicle_journeys_path(object) + ) + end + + if has_feature?(:purchase_windows) + links << Link.new( + content: h.t('purchase_windows.index.title'), + href: h.referential_purchase_windows_path(object) ) - ] + end + + links << Link.new( + content: h.t('time_tables.index.title'), + href: h.referential_time_tables_path(object) + ) if policy.clone? links << Link.new( @@ -63,4 +77,12 @@ class ReferentialDecorator < Draper::Decorator links end + + private + + # TODO move to a base Decorator (ApplicationDecorator) + def has_feature?(*features) + h.has_feature?(*features) rescue false + end + end diff --git a/app/decorators/referential_line_decorator.rb b/app/decorators/referential_line_decorator.rb index 55acf7ed9..dceb3e2a9 100644 --- a/app/decorators/referential_line_decorator.rb +++ b/app/decorators/referential_line_decorator.rb @@ -24,30 +24,6 @@ class ReferentialLineDecorator < Draper::Decorator ) ) - if h.policy(Chouette::Line).create? && - context[:referential].organisation == context[:current_organisation] - links << Link.new( - content: h.t('actions.new'), - href: h.new_referential_line_path(context[:referential]) - ) - end - - if h.policy(object).update? - links << Link.new( - content: h.t('actions.edit'), - href: h.edit_referential_line_path(context[:referential], object) - ) - end - - if h.policy(object).destroy? - links << Link.new( - content: h.destroy_link_content('actions.destroy'), - href: h.referential_line_path(context[:referential], object), - method: :delete, - data: { confirm: t('lines.actions.destroy_confirm') } - ) - end - if !object.hub_restricted? || (object.hub_restricted? && object.routes.size < 2) if h.policy(Chouette::Route).create? && diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb index 510c941a3..ec7f0d6aa 100644 --- a/app/decorators/route_decorator.rb +++ b/app/decorators/route_decorator.rb @@ -13,7 +13,7 @@ class RouteDecorator < Draper::Decorator if object.stop_points.any? links << Link.new( - content: h.t('journey_patterns.index.title'), + content: h.t('journey_patterns.actions.index'), href: [ context[:referential], context[:line], diff --git a/app/decorators/stop_area_decorator.rb b/app/decorators/stop_area_decorator.rb index 4e777292d..32f6e1d2b 100644 --- a/app/decorators/stop_area_decorator.rb +++ b/app/decorators/stop_area_decorator.rb @@ -3,21 +3,12 @@ class StopAreaDecorator < Draper::Decorator delegate_all - def action_links(stop_area = nil) - links = [] + def common_action_links(stop_area = nil) + top_links, bottom_links = [], [] stop_area ||= object - if h.policy(Chouette::StopArea).new? - links << Link.new( - content: h.t('stop_areas.actions.new'), - href: h.new_stop_area_referential_stop_area_path( - stop_area.stop_area_referential - ) - ) - end - if h.policy(stop_area).update? - links << Link.new( + top_links << Link.new( content: h.t('stop_areas.actions.edit'), href: h.edit_stop_area_referential_stop_area_path( stop_area.stop_area_referential, @@ -27,7 +18,7 @@ class StopAreaDecorator < Draper::Decorator end if h.policy(stop_area).destroy? - links << Link.new( + bottom_links << Link.new( content: h.destroy_link_content('stop_areas.actions.destroy'), href: h.stop_area_referential_stop_area_path( stop_area.stop_area_referential, @@ -38,6 +29,40 @@ class StopAreaDecorator < Draper::Decorator ) end - links + [top_links, bottom_links] + end + + def action_links(stop_area = nil) + stop_area ||= object + top_links, bottom_links = common_action_links(stop_area) + links = [] + + if h.policy(object).deactivate? + links << Link.new( + content: h.deactivate_link_content('stop_areas.actions.deactivate'), + href: h.deactivate_stop_area_referential_stop_area_path(stop_area.stop_area_referential, object), + method: :put, + data: {confirm: h.t('stop_areas.actions.deactivate_confirm')}, + extra_class: "delete-action" + ) + end + + if h.policy(object).activate? + links << Link.new( + content: h.activate_link_content('stop_areas.actions.activate'), + href: h.activate_stop_area_referential_stop_area_path(stop_area.stop_area_referential, object), + method: :put, + data: {confirm: h.t('stop_areas.actions.activate_confirm')}, + extra_class: "delete-action" + ) + end + + top_links + links + bottom_links end + + def waiting_time_text + return '-' if [nil, 0].include? waiting_time + h.t('stop_areas.waiting_time_format', value: waiting_time) + end + end diff --git a/app/decorators/stop_point_decorator.rb b/app/decorators/stop_point_decorator.rb index 196d6d490..27e1a7058 100644 --- a/app/decorators/stop_point_decorator.rb +++ b/app/decorators/stop_point_decorator.rb @@ -4,6 +4,6 @@ class StopPointDecorator < StopAreaDecorator delegate_all def action_links - super(object.stop_area) + common_action_links(object.stop_area).flatten end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d2cdaaa20..713542ff4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -3,24 +3,38 @@ module ApplicationHelper include NewapplicationHelper + def array_to_html_list items + content_tag :ul do + items.each do |item| + concat content_tag :li, item + end + end + end + def page_header_title(object) # Unwrap from decorator, we want to know the object model name object = object.object if object.try(:object) local = "#{object.model_name.name.underscore.pluralize}.#{params[:action]}.title" if object.try(:name) - t(local, name: object.name) + t(local, name: object.name || object.id) else t(local) end end def page_header_meta(object) - info = t('last_update', time: l(object.updated_at, format: :short)) - if object.try(:versions) - author = object.versions.try(:last).try(:whodunnit) || t('default_whodunnit') - info = "#{info} <br/> #{t('whodunnit', author: author)}" + out = "" + display = true + display = policy(object).synchronize? if policy(object).respond_to?(:synchronize?) rescue false + if display + info = t('last_update', time: l(object.updated_at, format: :short)) + if object.try(:versions) + author = object.versions.try(:last).try(:whodunnit) || t('default_whodunnit') + info = "#{info} <br/> #{t('whodunnit', author: author)}" + end + out += content_tag :div, info.html_safe, class: 'small last-update' end - content_tag :div, info.html_safe, class: 'small' + out.html_safe end def page_header_content_for(object) diff --git a/app/helpers/common_helpers.rb b/app/helpers/common_helpers.rb deleted file mode 100644 index 29cabddac..000000000 --- a/app/helpers/common_helpers.rb +++ /dev/null @@ -1,26 +0,0 @@ - -module CommonHelpers - # TODO: Needs refactoring, but does not seem to be under test - # so let us refactor this **after** test coverage. - def access_links_pairs(access_links) - hpairs = Hash.new - pairs = Array.new - access_links.each do |link| - key = pair_key(link) - pair = nil - if (hpairs.has_key? key) - pair = hpairs[key] - else - pair = AccessLinkPair.new - pairs << pair - hpairs[key] = pair - end - if (link.link_orientation_type == "access_point_to_stop_area") - pair.from_access_point = link - else - pair.to_access_point = link - end - end - pairs - end -end diff --git a/app/helpers/compliance_control_sets_helper.rb b/app/helpers/compliance_control_sets_helper.rb index bb2a72623..57e6d9608 100644 --- a/app/helpers/compliance_control_sets_helper.rb +++ b/app/helpers/compliance_control_sets_helper.rb @@ -4,12 +4,12 @@ module ComplianceControlSetsHelper [current_organisation, Organisation.find_by_name("STIF")].uniq end - def flotted_links ccs_id = @compliance_control_set + def floating_links ccs_id links = [new_control(ccs_id), new_block(ccs_id)] - unless links.all? &:nil? - content_tag :div, class: 'select_toolbox' do + if links.any? + content_tag :div, class: 'select_toolbox', id: 'floating-links' do content_tag :ul do - links.collect {|link| concat content_tag(:li, link, class: 'st_action with_text') unless link.nil?} + links.collect {|link| concat content_tag(:li, link, class: 'st_action with_text') if link} end end end @@ -21,8 +21,6 @@ module ComplianceControlSetsHelper concat content_tag :span, nil, class: 'fa fa-plus' concat content_tag :span, t('compliance_control_sets.actions.add_compliance_control') end - else - nil end end @@ -32,8 +30,83 @@ module ComplianceControlSetsHelper concat content_tag :span, nil, class: 'fa fa-plus' concat content_tag :span,t('compliance_control_sets.actions.add_compliance_control_block') end - else - nil end end -end
\ No newline at end of file + + def render_compliance_control_block(block=nil) + content_tag :div, class: 'row' do + content_tag :div, class: 'col-lg-12' do + content_tag :h2 do + concat transport_mode_text(block) + concat dropdown(block) if block + end + end + end + end + + def dropdown(block) + dropdown_button = content_tag :div, class: 'btn dropdown-toggle', "data-toggle": "dropdown" do + content_tag :div, nil, class: 'span fa fa-cog' + end + + dropdown_menu = content_tag :ul, class: 'dropdown-menu' do + link_1 = content_tag :li do + link_to t('compliance_control_sets.actions.edit'), edit_compliance_control_set_compliance_control_block_path(@compliance_control_set.id, block.id) + end + link_2 = content_tag :li do + link_to t('compliance_control_sets.actions.destroy'), compliance_control_set_compliance_control_block_path(@compliance_control_set.id, block.id), :method => :delete, :data => {:confirm => t('compliance_control_sets.actions.destroy_confirm')} + end + link_1 + link_2 + end + + content_tag :div, class: 'btn-group' do + dropdown_button + dropdown_menu + end + + end + + def render_compliance_controls(compliance_controls) + content_tag :div, class: 'row' do + content_tag :div, class: 'col-lg-12' do + compliance_controls.try(:any?) ? render_table_builder(compliance_controls) : render_no_controls + end + end + + end + + def render_table_builder(compliance_controls) + table = content_tag :div, class: 'select_table' do + table_builder_2 compliance_controls, + [ + TableBuilderHelper::Column.new( + key: :code, + attribute: 'code' + ), + TableBuilderHelper::Column.new( + key: :name, + attribute: 'name', + link_to: lambda do |compliance_control| + compliance_control_set_compliance_control_path(@compliance_control_set, compliance_control) + end + ), + TableBuilderHelper::Column.new( + key: :criticity, + attribute: 'criticity' + ), + TableBuilderHelper::Column.new( + key: :comment, + attribute: 'comment' + ), + ], + sortable: true, + cls: 'table has-filter has-search', + model: ComplianceControl + end + metas = content_tag :div, I18n.t('compliance_control_blocks.metas.control', count: compliance_controls.count), class: 'pull-right' + table + metas + end + + def render_no_controls + content_tag :div, I18n.t('compliance_control_blocks.metas.control.zero'), class: 'alert alert-warning' + end +end diff --git a/app/helpers/links_helper.rb b/app/helpers/links_helper.rb index 4fb7a797d..088415dc3 100644 --- a/app/helpers/links_helper.rb +++ b/app/helpers/links_helper.rb @@ -1,5 +1,18 @@ module LinksHelper + def custom_link_content(translation_key, klass, extra_class: nil) + klass = ["fa", "fa-#{klass}", "mr-xs", extra_class].compact.join(" ") + content_tag(:span, nil, class: klass) + t(translation_key) + end + def destroy_link_content(translation_key = 'actions.destroy') - content_tag(:span, nil, class: 'fa fa-trash mr-xs') + t(translation_key) + custom_link_content translation_key, 'trash' + end + + def deactivate_link_content(translation_key = 'actions.deactivate') + custom_link_content translation_key, 'power-off', extra_class: "text-danger" + end + + def activate_link_content(translation_key = 'actions.activate') + custom_link_content translation_key, 'power-off', extra_class: "text-success" end end diff --git a/app/helpers/multiple_selection_toolbox_helper.rb b/app/helpers/multiple_selection_toolbox_helper.rb index 85294af6d..e0a1d2dd4 100644 --- a/app/helpers/multiple_selection_toolbox_helper.rb +++ b/app/helpers/multiple_selection_toolbox_helper.rb @@ -1,7 +1,11 @@ module MultipleSelectionToolboxHelper # Box of links that floats at the bottom right of the page - def multiple_selection_toolbox(actions) + # c.f. https://projects.af83.io/issues/5206 + # #5206 method too long + def multiple_selection_toolbox(actions, collection_name:) links = content_tag :ul do + + # #5206 `if params[:controller]` mieux passer comme parametre si besoin delete_path = nil if params[:controller] = 'workbenches' @@ -15,6 +19,7 @@ module MultipleSelectionToolboxHelper method: :delete, data: { path: delete_path, + # #5206 Missing Translations confirm: 'Etes-vous sûr(e) de vouloir effectuer cette action ?' }, title: t("actions.#{action}") @@ -33,7 +38,9 @@ module MultipleSelectionToolboxHelper class: 'info-msg' ) - content_tag :div, '', class: 'select_toolbox noselect' do + content_tag :div, '', + class: 'select_toolbox noselect', + id: "selected-#{collection_name}-action-box" do links + label end end diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index 37f01ce0d..dede51920 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -95,6 +95,18 @@ module TableBuilderHelper class: cls end + def self.item_row_class_name collection + if collection.respond_to?(:model) + model_name = collection.model.name + elsif collection.respond_to?(:first) + model_name = collection.first.class.name + else + model_name = "item" + end + + model_name.split("::").last.parameterize + end + private def thead(collection, columns, sortable, selectable, has_links, overhead, model ) @@ -187,86 +199,92 @@ module TableBuilderHelper end end - def tbody(collection, columns, selectable, links, overhead) - content_tag :tbody do - collection.map do |item| - - content_tag :tr do - bcont = [] - - if selectable - bcont << content_tag( - :td, - checkbox(id_name: item.try(:id), value: item.try(:id)) - ) - end - - columns.each do |column| - value = column.value(item) + def tr item, columns, selectable, links, overhead, model_name + klass = "#{model_name}-#{item.id}" + content_tag :tr, class: klass do + bcont = [] + if selectable + disabled = selectable.respond_to?(:call) && !selectable.call(item) + bcont << content_tag( + :td, + checkbox(id_name: item.try(:id), value: item.try(:id), disabled: disabled) + ) + end - if column.linkable? - path = column.link_to(item) - link = link_to(value, path) + columns.each do |column| + value = column.value(item) - if overhead.empty? - bcont << content_tag(:td, link, title: 'Voir') + if column.linkable? + path = column.link_to(item) + link = link_to(value, path) - else - i = columns.index(column) - - if overhead[i].blank? - if (i > 0) && (overhead[i - 1][:width] > 1) - clsArrayAlt = overhead[i - 1][:cls].split + if overhead.empty? + bcont << content_tag(:td, link, title: 'Voir') - bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArrayAlt)) + else + i = columns.index(column) - else - bcont << content_tag(:td, link, title: 'Voir') - end + if overhead[i].blank? + if (i > 0) && (overhead[i - 1][:width] > 1) + clsArrayAlt = overhead[i - 1][:cls].split - else - clsArray = overhead[columns.index(column)][:cls].split + bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArrayAlt)) - bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArray)) - end + else + bcont << content_tag(:td, link, title: 'Voir') end else - if overhead.empty? - bcont << content_tag(:td, value) + clsArray = overhead[columns.index(column)][:cls].split - else - i = columns.index(column) + bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArray)) + end + end - if overhead[i].blank? - if (i > 0) && (overhead[i - 1][:width] > 1) - clsArrayAlt = overhead[i - 1][:cls].split + else + if overhead.empty? + bcont << content_tag(:td, value) - bcont << content_tag(:td, value, class: td_cls(clsArrayAlt)) + else + i = columns.index(column) - else - bcont << content_tag(:td, value) - end + if overhead[i].blank? + if (i > 0) && (overhead[i - 1][:width] > 1) + clsArrayAlt = overhead[i - 1][:cls].split - else - clsArray = overhead[i][:cls].split + bcont << content_tag(:td, value, class: td_cls(clsArrayAlt)) - bcont << content_tag(:td, value, class: td_cls(clsArray)) - end + else + bcont << content_tag(:td, value) end + + else + clsArray = overhead[i][:cls].split + + bcont << content_tag(:td, value, class: td_cls(clsArray)) end end + end + end - if links.any? || item.try(:action_links).try(:any?) - bcont << content_tag( - :td, - build_links(item, links), - class: 'actions' - ) - end + if links.any? || item.try(:action_links).try(:any?) + bcont << content_tag( + :td, + build_links(item, links), + class: 'actions' + ) + end - bcont.join.html_safe - end + bcont.join.html_safe + end + end + + def tbody(collection, columns, selectable, links, overhead) + model_name = TableBuilderHelper.item_row_class_name collection + + content_tag :tbody do + collection.map do |item| + tr item, columns, selectable, links, overhead, model_name end.join.html_safe end end @@ -341,14 +359,20 @@ module TableBuilderHelper end end - def checkbox(id_name:, value:) + def checkbox(id_name:, value:, disabled: false) content_tag :div, '', class: 'checkbox' do - check_box_tag(id_name, value).concat( + check_box_tag(id_name, value, nil, disabled: disabled).concat( content_tag(:label, '', for: id_name) ) end end + def gear_menu_link(link) + klass = [] + klass << link.extra_class if link.extra_class + klass << 'delete-action' if link.method == :delete + klass << 'disabled' if link.disabled + content_tag( :li, link_to( @@ -358,7 +382,7 @@ module TableBuilderHelper ) do link.content end, - class: ('delete-action' if link.method == :delete) + class: (klass.join(' ') if klass.present?) ) end diff --git a/app/helpers/table_builder_helper/url.rb b/app/helpers/table_builder_helper/url.rb index a53ac5620..28f1ade76 100644 --- a/app/helpers/table_builder_helper/url.rb +++ b/app/helpers/table_builder_helper/url.rb @@ -10,7 +10,7 @@ module TableBuilderHelper polymorph_url << item.route.line if item.is_a?(Chouette::RoutingConstraintZone) polymorph_url << item if item.respond_to? :line_referential polymorph_url << item.stop_area if item.respond_to? :stop_area - polymorph_url << item if item.respond_to?(:stop_points) || item.is_a?(Chouette::TimeTable) + polymorph_url << item if item.respond_to?(:stop_points) || item.is_a?(Chouette::TimeTable) || item.is_a?(Chouette::PurchaseWindow) elsif item.respond_to? :referential if item.respond_to? :workbench polymorph_url << item.workbench diff --git a/app/inputs/color_select_input.rb b/app/inputs/color_select_input.rb new file mode 100644 index 000000000..f92c80a22 --- /dev/null +++ b/app/inputs/color_select_input.rb @@ -0,0 +1,44 @@ +class ColorSelectInput < SimpleForm::Inputs::CollectionInput + enable :placeholder + + def input(wrapper_options = {}) + selected_color = object.send(attribute_name) + label = if selected_color + collection.find{|i| i.is_a?(Enumerable) && i.last == selected_color}.try(:first) + end + + out = @builder.hidden_field attribute_name, value: selected_color + tag_name = ActionView::Helpers::Tags::Base.new( ActiveModel::Naming.param_key(object), attribute_name, :dummy ).send(:tag_name) + select = <<-eos + <div class="dropdown color_selector"> + <button type='button' class="btn btn-default dropdown-toggle" data-toggle='dropdown' aria-haspopup='true' aria-expanded='true' + ><span + class='fa fa-circle mr-xs' + style='color: #{selected_color == nil ? 'transparent' : selected_color}' + > + </span> + #{label} + <span class='caret'></span> + </button> + + <div class="form-group dropdown-menu" aria-labelledby='dpdwn_color'> + eos + + collection.each do |color| + name = nil + name, color = color if color.is_a?(Enumerable) + select += <<-eos + <span class="radio" key=#{color} > + <label> + <input type='radio' class='color_selector' value='#{color}' data-for='#{tag_name}'/> + <span class='fa fa-circle mr-xs' style='color: #{color == nil ? 'transparent' : color}'></span> + #{name} + </label> + </span> + eos + end + select += "</div></div>" + + out + select.html_safe + end +end diff --git a/app/javascript/date_filters/index.js b/app/javascript/date_filters/index.js index ee892a7fe..432166008 100644 --- a/app/javascript/date_filters/index.js +++ b/app/javascript/date_filters/index.js @@ -3,6 +3,7 @@ import complianceControlSetDF from './compliance_control_set' import complianceCheckSetDF from './compliance_check_set' import timetableDF from './time_table' import importDF from './import' +import purchaseWindowDF from './purchase_window' import workbenchDF from './workbench' const DateFilters = { @@ -11,6 +12,7 @@ const DateFilters = { complianceControlSetDF, importDF, timetableDF, + purchaseWindowDF, workbenchDF } diff --git a/app/javascript/date_filters/purchase_window.js b/app/javascript/date_filters/purchase_window.js new file mode 100644 index 000000000..2c46b6d52 --- /dev/null +++ b/app/javascript/date_filters/purchase_window.js @@ -0,0 +1,5 @@ +import DateFilter from '../helpers/date_filters' + +const purchaseWindowDF = new DateFilter("purchase_window_filter_btn", "Tous les champs du filtre de date doivent être remplis", "q_contains_date_NUMi") + +export default purchaseWindowDF
\ No newline at end of file diff --git a/app/javascript/packs/vehicle_journeys/index.js b/app/javascript/packs/vehicle_journeys/index.js index 38431af1d..53c5d5417 100644 --- a/app/javascript/packs/vehicle_journeys/index.js +++ b/app/javascript/packs/vehicle_journeys/index.js @@ -23,6 +23,7 @@ var initialState = { filters: { selectedJourneyPatterns : selectedJP, policy: window.perms, + features: window.features, toggleArrivals: false, queryString: '', query: { @@ -54,7 +55,7 @@ var initialState = { }, status: { - fetchSuccess: true, + fetchSuccess: false, isFetching: false }, vehicleJourneys: [], @@ -99,4 +100,4 @@ render( <App /> </Provider>, document.getElementById('vehicle_journeys_wip') -)
\ No newline at end of file +) diff --git a/app/javascript/routes/components/BSelect2.js b/app/javascript/routes/components/BSelect2.js index 340d9df95..0d8d7787f 100644 --- a/app/javascript/routes/components/BSelect2.js +++ b/app/javascript/routes/components/BSelect2.js @@ -96,17 +96,26 @@ class BSelect2 extends Component{ data: function(params) { return { q: params.term, - target_type: 'zdep' + scope: 'route_editor' }; }, processResults: function(data, params) { return { - results: data.map( - item => _.assign( - {}, - item, - { text: item.name + ", " + item.zip_code + " " + item.short_city_name + " <small><em>(" + item.user_objectid + ")</em></small>" } - ) + results: data.map( + function(item) { + var text = item.name; + if (item.zip_code || item.short_city_name) { + text += "," + } + if (item.zip_code) { + text += ` ${item.zip_code}` + } + if (item.short_city_name) { + text += ` ${item.short_city_name}` + } + text += ` <small><em>(${item.area_type.toUpperCase()}, ${item.user_objectid})</em></small>`; + return _.assign({}, item, { text: text }); + } ) }; }, diff --git a/app/javascript/routes/form_helper.js b/app/javascript/routes/form_helper.js index 8a3277234..865722fb6 100644 --- a/app/javascript/routes/form_helper.js +++ b/app/javascript/routes/form_helper.js @@ -7,14 +7,14 @@ const formHelper = { input.setAttribute('name', formatedName) input.setAttribute('value', value) form.appendChild(input) - }, + }, addError: (ids) => { ids.forEach((id) => { if (!$(id).parents('.form-group').hasClass('has-error')) { $(id).parents('.form-group').addClass('has-error') $(id).parent().append(`<span class='help-block small'>${'doit être rempli(e)'}</span>`) } - }) + }) }, cleanInputs: (ids) => { ids.forEach((id) =>{ @@ -28,21 +28,22 @@ const formHelper = { ids.forEach(id => { $(id).val() == "" ? blankInputs.push(id) : filledInputs.push(id) }) - + if (filledInputs.length > 0) formHelper.cleanInputs(filledInputs) - if (blankInputs.length > 0) formHelper.addError(blankInputs) + if (blankInputs.length > 0) formHelper.addError(blankInputs) }, handleStopPoints: (event, state) => { if (state.stopPoints.length >= 2) { state.stopPoints.map((stopPoint, i) => { formHelper.addInput('id', stopPoint.stoppoint_id ? stopPoint.stoppoint_id : '', i) formHelper.addInput('stop_area_id', stopPoint.stoparea_id, i) - formHelper.addInput('position', i, i) + formHelper.addInput('position', stopPoint.index, i) formHelper.addInput('for_boarding', stopPoint.for_boarding, i) formHelper.addInput('for_alighting', stopPoint.for_alighting, i) }) - if ($('.alert.alert-danger').length > 0) $('.alert.alert-danger').remove() - } else { + if ($('.alert.alert-danger').length > 0) $('.alert.alert-danger').remove() + } + else { event.preventDefault() let msg = "L'itinéraire doit comporter au moins deux arrêts" if ($('.alert.alert-danger').length == 0) { @@ -52,4 +53,4 @@ const formHelper = { } } -export default formHelper
\ No newline at end of file +export default formHelper diff --git a/app/javascript/routes/reducers/stopPoints.js b/app/javascript/routes/reducers/stopPoints.js index eeec06327..0b42b504f 100644 --- a/app/javascript/routes/reducers/stopPoints.js +++ b/app/javascript/routes/reducers/stopPoints.js @@ -38,15 +38,15 @@ const stopPoints = (state = [], action) => { case 'MOVE_STOP_UP': return [ ...state.slice(0, action.index - 1), - _.assign({}, state[action.index], { stoppoint_id: state[action.index - 1].stoppoint_id }), - _.assign({}, state[action.index - 1], { stoppoint_id: state[action.index].stoppoint_id }), + _.assign({}, state[action.index], { index: action.index - 1 }), + _.assign({}, state[action.index - 1], { index: action.index }), ...state.slice(action.index + 1) ] case 'MOVE_STOP_DOWN': return [ ...state.slice(0, action.index), - _.assign({}, state[action.index + 1], { stoppoint_id: state[action.index].stoppoint_id }), - _.assign({}, state[action.index], { stoppoint_id: state[action.index + 1].stoppoint_id }), + _.assign({}, state[action.index + 1], { index: action.index }), + _.assign({}, state[action.index], { index: action.index + 1 }), ...state.slice(action.index + 2) ] case 'DELETE_STOP': @@ -141,4 +141,4 @@ const stopPoints = (state = [], action) => { } } -export default stopPoints
\ No newline at end of file +export default stopPoints diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index ddb54d615..40c8006f1 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -84,7 +84,8 @@ const actions = { selectedItem:{ id: selectedTT.id, comment: selectedTT.comment, - objectid: selectedTT.objectid + objectid: selectedTT.objectid, + color: selectedTT.color } }), addSelectedTimetable: () => ({ @@ -99,6 +100,31 @@ const actions = { vehicleJourneys, timetables }), + openPurchaseWindowsEditModal : (vehicleJourneys) => ({ + type : 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL', + vehicleJourneys + }), + selectPurchaseWindowsModal: (selectedWindow) =>({ + type: 'SELECT_PURCHASE_WINDOW_MODAL', + selectedItem:{ + id: selectedWindow.id, + name: selectedWindow.name, + color: selectedWindow.color, + objectid: selectedWindow.objectid + } + }), + addSelectedPurchaseWindow: () => ({ + type: 'ADD_SELECTED_PURCHASE_WINDOW' + }), + deletePurchaseWindowsModal : (purchaseWindow) => ({ + type : 'DELETE_PURCHASE_WINDOW_MODAL', + purchaseWindow + }), + editVehicleJourneyPurchaseWindows : (vehicleJourneys, purchase_windows) => ({ + type: 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS', + vehicleJourneys, + purchase_windows + }), openShiftModal : () => ({ type : 'SHIFT_VEHICLEJOURNEY_MODAL' }), @@ -163,25 +189,17 @@ const actions = { $(target).parent().removeClass('has-error').children('.help-block').remove() }, validateFields : (fields) => { - const test = [] - - Object.keys(fields).map(function(key) { - test.push(fields[key].validity.valid) + let valid = true + Object.keys(fields).forEach((key) => { + let field = fields[key] + if(field.validity && !field.validity.valid){ + valid = false + $(field).parent().addClass('has-error').children('.help-block').remove() + $(field).parent().append("<span class='small help-block'>" + field.validationMessage + "</span>") + } }) - 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 - } + + return valid }, toggleArrivals : () => ({ type: 'TOGGLE_ARRIVALS', @@ -313,6 +331,7 @@ const actions = { let val for (val of json.vehicle_journeys){ var timeTables = [] + var purchaseWindows = [] let tt for (tt of val.time_tables){ timeTables.push({ @@ -322,26 +341,35 @@ const actions = { color: tt.color }) } + if(val.purchase_windows){ + for (tt of val.purchase_windows){ + purchaseWindows.push({ + objectid: tt.objectid, + name: tt.name, + id: tt.id, + color: tt.color + }) + } + } let vjasWithDelta = val.vehicle_journey_at_stops.map((vjas, i) => { actions.fillEmptyFields(vjas) return actions.getDelta(vjas) }) - vehicleJourneys.push({ - journey_pattern: val.journey_pattern, - published_journey_name: val.published_journey_name, - objectid: val.objectid, - short_id: val.short_id, - footnotes: val.footnotes, - time_tables: timeTables, - vehicle_journey_at_stops: vjasWithDelta, - deletable: false, - selected: false, - published_journey_name: val.published_journey_name || 'non renseigné', - published_journey_identifier: val.published_journey_identifier || 'non renseigné', - company: val.company || 'non renseigné', - transport_mode: val.route.line.transport_mode || 'undefined', - transport_submode: val.route.line.transport_submode || 'undefined' - }) + + vehicleJourneys.push( + _.assign({}, val, { + time_tables: timeTables, + purchase_windows: purchaseWindows, + vehicle_journey_at_stops: vjasWithDelta, + deletable: false, + selected: false, + published_journey_name: val.published_journey_name || 'non renseigné', + published_journey_identifier: val.published_journey_identifier || 'non renseigné', + company: val.company || {name: 'non renseigné'}, + transport_mode: val.route.line.transport_mode || 'undefined', + transport_submode: val.route.line.transport_submode || 'undefined' + }) + ) } window.currentItemsLength = vehicleJourneys.length dispatch(actions.receiveVehicleJourneys(vehicleJourneys)) @@ -439,6 +467,20 @@ const actions = { vjas.delta = delta return vjas }, + adjustSchedule: (action, schedule) => { + // we enforce that the departure time remains after the arrival time + actions.getDelta(schedule) + if(schedule.delta < 0){ + if(action.isDeparture){ + schedule.arrival_time = schedule.departure_time + } + else{ + schedule.departure_time = schedule.arrival_time + } + actions.getDelta(schedule) + } + return schedule + }, getShiftedSchedule: ({departure_time, arrival_time}, additional_time) => { // We create dummy dates objects to manipulate time more easily let departureDT = new Date (Date.UTC(2017, 2, 1, parseInt(departure_time.hour), parseInt(departure_time.minute))) @@ -457,10 +499,6 @@ const actions = { minute: actions.simplePad(newArrivalDT.getUTCMinutes()) } } - }, - escapeWildcardCharacters(search) { - let newSearch = search.replace(/^_/, "\\_") - return newSearch.replace(/^%/, "\\%") } } diff --git a/app/javascript/vehicle_journeys/components/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js index db6707520..3bc4f7ff7 100644 --- a/app/javascript/vehicle_journeys/components/Filters.js +++ b/app/javascript/vehicle_journeys/components/Filters.js @@ -33,6 +33,7 @@ export default function Filters({filters, pagination, onFilter, onResetFilters, onSelect2Timetable={onSelect2Timetable} hasRoute={true} chunkURL={("/autocomplete_time_tables.json?route_id=" + String(window.route_id))} + searchKey={"comment_or_objectid_cont_any"} filters={filters} isFilter={true} /> @@ -165,4 +166,4 @@ Filters.propTypes = { onSelect2Timetable: PropTypes.func.isRequired, onSelect2JourneyPattern: PropTypes.func.isRequired, onSelect2VehicleJourney: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js b/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js index e8c27f92e..8bab5baa9 100644 --- a/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js @@ -14,16 +14,27 @@ export default class SaveVehicleJourneys extends Component{ <div className='row mt-md'> <div className='col-lg-12 text-right'> <form className='vehicle_journeys formSubmitr ml-xs' onSubmit={e => {e.preventDefault()}}> - <button - className='btn btn-default' - type='button' - onClick={e => { - e.preventDefault() - this.props.editMode ? this.props.onSubmitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys) : this.props.onEnterEditMode() - }} - > - {this.props.editMode ? "Valider" : "Editer"} - </button> + <div className="btn-group sticky-actions"> + <button + className={'btn ' + (this.props.editMode ? 'btn-success' : 'btn-default') + (this.props.status.fetchSuccess ? '' : ' disabled')} + type='button' + onClick={e => { + e.preventDefault() + this.props.editMode ? this.props.onSubmitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys) : this.props.onEnterEditMode() + }} + > + {this.props.editMode ? "Valider" : "Editer"} + </button> + {this.props.editMode && <button + className='btn btn-default' + type='button' + onClick={e => { + e.preventDefault() + this.props.onExitEditMode() + }} + > Annuler </button> + } + </div> </form> </div> </div> @@ -38,5 +49,6 @@ SaveVehicleJourneys.propTypes = { status: PropTypes.object.isRequired, filters: PropTypes.object.isRequired, onEnterEditMode: PropTypes.func.isRequired, + onExitEditMode: PropTypes.func.isRequired, onSubmitVehicleJourneys: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/Tools.js b/app/javascript/vehicle_journeys/components/Tools.js index 7621dfc10..d6e04f00e 100644 --- a/app/javascript/vehicle_journeys/components/Tools.js +++ b/app/javascript/vehicle_journeys/components/Tools.js @@ -7,6 +7,7 @@ import DuplicateVehicleJourney from '../containers/tools/DuplicateVehicleJourney import EditVehicleJourney from '../containers/tools/EditVehicleJourney' import NotesEditVehicleJourney from '../containers/tools/NotesEditVehicleJourney' import TimetablesEditVehicleJourney from '../containers/tools/TimetablesEditVehicleJourney' +import PurchaseWindowsEditVehicleJourney from '../containers/tools/PurchaseWindowsEditVehicleJourney' export default class Tools extends Component { @@ -17,7 +18,12 @@ export default class Tools extends Component { hasPolicy(key) { // Check if the user has the policy to disable or not the action - return this.props.filters.policy[`vehicle_journeys.${key}`] + return this.props.filters.policy[`vehicle_journeys.${key}`] + } + + hasFeature(key) { + // Check if the organisation has the given feature + return this.props.filters.features[key] } render() { @@ -25,13 +31,16 @@ export default class Tools extends Component { return ( <div className='select_toolbox'> <ul> - <AddVehicleJourney disabled={this.hasPolicy("create") && !editMode} /> - <DuplicateVehicleJourney disabled={this.hasPolicy("create") && this.hasPolicy("update") && !editMode}/> - <ShiftVehicleJourney disabled={this.hasPolicy("update") && !editMode}/> + <AddVehicleJourney disabled={!this.hasPolicy("create") || !editMode} /> + <DuplicateVehicleJourney disabled={!this.hasPolicy("create") || !this.hasPolicy("update") || !editMode}/> + <ShiftVehicleJourney disabled={!this.hasPolicy("update") || !editMode}/> <EditVehicleJourney disabled={!this.hasPolicy("update")}/> <TimetablesEditVehicleJourney disabled={!this.hasPolicy("update")}/> + { this.hasFeature('purchase_windows') && + <PurchaseWindowsEditVehicleJourney disabled={!this.hasPolicy("update")}/> + } <NotesEditVehicleJourney disabled={!this.hasPolicy("update")}/> - <DeleteVehicleJourneys disabled={this.hasPolicy("destroy") && !editMode}/> + <DeleteVehicleJourneys disabled={!this.hasPolicy("destroy") || !editMode}/> </ul> <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span> @@ -45,4 +54,4 @@ Tools.propTypes = { vehicleJourneys : PropTypes.array.isRequired, onCancelSelection: PropTypes.func.isRequired, filters: PropTypes.object.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index 8fb4b8a7e..5f6281487 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -17,12 +17,25 @@ export default class VehicleJourney extends Component { return bool } + hasFeature(key) { + return this.props.filters.features[key] + } + timeTableURL(tt) { let refURL = window.location.pathname.split('/', 3).join('/') let ttURL = refURL + '/time_tables/' + tt.id return ( - <a href={ttURL} title='Voir le calendrier'><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '')}}></span></a> + <a href={ttURL} title='Voir le calendrier'><span className='fa fa-calendar' style={{ color: (tt.color ? tt.color : '#4B4B4B')}}></span></a> + ) + } + + purchaseWindowURL(tt) { + let refURL = window.location.pathname.split('/', 3).join('/') + let ttURL = refURL + '/purchase_windows/' + tt.id + + return ( + <a href={ttURL} title='Voir le calendrier commercial'><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '')}}></span></a> ) } @@ -44,19 +57,33 @@ export default class VehicleJourney extends Component { render() { this.previousCity = undefined - let {time_tables} = this.props.value + let {time_tables, purchase_windows} = this.props.value return ( <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.errors ? ' has-error': '')}> - <div className='th'> - <div className='strong mb-xs'>{this.props.value.objectid ? this.props.value.short_id : '-'}</div> - <div>{this.props.value.journey_pattern.short_id}</div> + <div + className='th' + onClick={(e) => + ($(e.target).parents("a").length == 0) && this.props.onSelectVehicleJourney(this.props.index) + } + > + <div className='strong mb-xs'>{this.props.value.short_id || '-'}</div> + <div>{this.props.value.published_journey_name && this.props.value.published_journey_name != "non renseigné" ? this.props.value.published_journey_name : '-'}</div> + <div>{this.props.value.journey_pattern.short_id || '-'}</div> <div> {time_tables.slice(0,3).map((tt, i)=> <span key={i} className='vj_tt'>{this.timeTableURL(tt)}</span> )} {time_tables.length > 3 && <span className='vj_tt'> + {time_tables.length - 3}</span>} </div> + { this.hasFeature('purchase_windows') && + <div> + {purchase_windows.slice(0,3).map((tt, i)=> + <span key={i} className='vj_tt'>{this.purchaseWindowURL(tt)}</span> + )} + {purchase_windows.length > 3 && <span className='vj_tt'> + {purchase_windows.length - 3}</span>} + </div> + } <div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}> <input id={this.props.index} @@ -83,6 +110,7 @@ export default class VehicleJourney extends Component { className='form-control' disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} + disabled={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}} value={vj.arrival_time['hour']} /> @@ -94,6 +122,7 @@ export default class VehicleJourney extends Component { className='form-control' disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} + disabled={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false)}} value={vj.arrival_time['minute']} /> @@ -114,6 +143,7 @@ export default class VehicleJourney extends Component { className='form-control' disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} + disabled={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals)}} value={vj.departure_time['hour']} /> @@ -125,6 +155,7 @@ export default class VehicleJourney extends Component { className='form-control' disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} + disabled={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals)}} value={vj.departure_time['minute']} /> @@ -144,4 +175,4 @@ VehicleJourney.propTypes = { index: PropTypes.number.isRequired, onUpdateTime: PropTypes.func.isRequired, onSelectVehicleJourney: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index 6bce9766b..dc480d6b4 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -12,6 +12,10 @@ export default class VehicleJourneys extends Component { this.props.onLoadFirstPage(this.props.filters) } + hasFeature(key) { + return this.props.filters.features[key] + } + componentDidUpdate(prevProps, prevState) { if(this.props.status.isFetching == false){ $('.table-2entries').each(function() { @@ -111,8 +115,10 @@ export default class VehicleJourneys extends Component { <div className='t2e-head w20'> <div className='th'> <div className='strong mb-xs'>ID course</div> + <div>Nom course</div> <div>ID mission</div> <div>Calendriers</div> + { this.hasFeature('purchase_windows') && <div>Calendriers Commerciaux</div> } </div> {this.props.stopPointsList.map((sp, i) =>{ return ( @@ -132,6 +138,7 @@ export default class VehicleJourneys extends Component { index={index} editMode={this.props.editMode} filters={this.props.filters} + features={this.props.features} onUpdateTime={this.props.onUpdateTime} onSelectVehicleJourney={this.props.onSelectVehicleJourney} /> @@ -153,4 +160,4 @@ VehicleJourneys.propTypes = { onLoadFirstPage: PropTypes.func.isRequired, onUpdateTime: PropTypes.func.isRequired, onSelectVehicleJourney: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js index 2bffebdf6..cd593cdff 100644 --- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js +++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js @@ -9,7 +9,7 @@ export default class CreateModal extends Component { } handleSubmit() { - if(actions.validateFields(this.refs) == true && this.props.modal.modalProps.selectedJPModal) { + if (actions.validateFields(...this.refs, $('.vjCreateSelectJP')[0]) && this.props.modal.modalProps.selectedJPModal) { this.props.onAddVehicleJourney(this.refs, this.props.modal.modalProps.selectedJPModal, this.props.stopPointsList, this.props.modal.modalProps.selectedCompany) this.props.onModalClose() $('#NewVehicleJourneyModal').modal('hide') @@ -61,7 +61,7 @@ export default class CreateModal extends Component { <div className='form-group'> <label className='control-label'>Nom du transporteur</label> <CompanySelect2 - company = {undefined} + company = {this.props.modal.modalProps.vehicleJourney && this.props.modal.modalProps.vehicleJourney.company || undefined} onSelect2Company = {(e) => this.props.onSelect2Company(e)} /> </div> @@ -130,4 +130,4 @@ CreateModal.propTypes = { onAddVehicleJourney: PropTypes.func.isRequired, onSelect2JourneyPattern: PropTypes.func.isRequired, disabled: PropTypes.bool.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js index 7d91896eb..f8d6add03 100644 --- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js @@ -97,6 +97,7 @@ export default class EditVehicleJourney extends Component { <div className='form-group'> <label className='control-label'>Transporteur</label> <CompanySelect2 + editModal={this.props.modal.type == "edit"} editMode={this.props.editMode} company = {this.props.modal.modalProps.vehicleJourney.company} onSelect2Company = {(e) => this.props.onSelect2Company(e)} diff --git a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js new file mode 100644 index 000000000..d61c7a34b --- /dev/null +++ b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js @@ -0,0 +1,152 @@ +import React, { PropTypes, Component } from 'react' +import actions from '../../actions' +import TimetableSelect2 from './select2s/TimetableSelect2' + +export default class PurchaseWindowsEditVehicleJourney extends Component { + constructor(props) { + super(props) + this.handleSubmit = this.handleSubmit.bind(this) + this.purchaseWindowURL = this.purchaseWindowURL.bind(this) + } + + handleSubmit() { + this.props.onShoppingWindowsEditVehicleJourney(this.props.modal.modalProps.vehicleJourneys, this.props.modal.modalProps.purchase_windows) + this.props.onModalClose() + $('#PurchaseWindowsEditVehicleJourneyModal').modal('hide') + } + + purchaseWindowURL(tt) { + let refURL = window.location.pathname.split('/', 3).join('/') + return refURL + '/purchase_windows/' + tt.id + } + + render() { + if(this.props.status.isFetching == true) { + return false + } + if(this.props.status.fetchSuccess == true) { + return ( + <li className='st_action'> + <button + type='button' + disabled={(actions.getSelected(this.props.vehicleJourneys).length < 1 || this.props.disabled)} + data-toggle='modal' + data-target='#PurchaseWindowsEditVehicleJourneyModal' + onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))} + title='Calendriers commerciaux' + > + <span className='sb sb-purchase_window sb-strong'></span> + </button> + + <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='PurchaseWindowsEditVehicleJourneyModal'> + <div className='modal-container'> + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <h4 className='modal-title'>Calendriers commerciaux associés</h4> + <span type="button" className="close modal-close" data-dismiss="modal">×</span> + </div> + + {(this.props.modal.type == 'purchase_windows_edit') && ( + <form> + <div className='modal-body'> + <div className='row'> + <div className='col-lg-12'> + <div className='subform'> + <div className='nested-head'> + <div className='wrapper'> + <div> + <div className='form-group'> + <label className='control-label'>{this.props.modal.modalProps.purchase_windows.length == 0 ? "Aucun calendrier commercial associé" : "Calendriers commerciaux associés"}</label> + </div> + </div> + <div></div> + </div> + </div> + {this.props.modal.modalProps.purchase_windows.map((tt, i) => + <div className='nested-fields' key={i}> + <div className='wrapper'> + <div> <a href={this.purchaseWindowURL(tt)} target="_blank"> + <span className="fa fa-circle mr-xs" style={{color: tt.color}}></span> + {tt.name} + </a> </div> + { + this.props.editMode && + <div> + <a + href='#' + title='Supprimer' + className='fa fa-trash remove_fields' + style={{ height: 'auto', lineHeight: 'normal' }} + onClick={(e) => { + e.preventDefault() + this.props.onDeleteCalendarModal(tt) + }} + ></a> + </div> + } + </div> + </div> + )} + { + this.props.editMode && + <div className='nested-fields'> + <div className='wrapper'> + <div> + <TimetableSelect2 + onSelect2Timetable={this.props.onSelect2Timetable} + chunkURL={'/autocomplete_purchase_windows.json'} + searchKey={"name_or_objectid_cont_any"} + isFilter={false} + /> + </div> + </div> + </div> + } + </div> + </div> + </div> + </div> + { + this.props.editMode && + <div className='modal-footer'> + <button + className='btn btn-link' + data-dismiss='modal' + type='button' + onClick={this.props.onModalClose} + > + Annuler + </button> + <button + className='btn btn-primary' + type='button' + onClick={this.handleSubmit} + > + Valider + </button> + </div> + } + </form> + )} + + </div> + </div> + </div> + </div> + </li> + ) + } else { + return false + } + } +} + +PurchaseWindowsEditVehicleJourney.propTypes = { + onOpenCalendarsEditModal: PropTypes.func.isRequired, + onModalClose: PropTypes.func.isRequired, + onShoppingWindowsEditVehicleJourney: PropTypes.func.isRequired, + onDeleteCalendarModal: PropTypes.func.isRequired, + onSelect2Timetable: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired +} diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js index fef3cdcc9..fdaa5aeed 100644 --- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js @@ -29,10 +29,11 @@ export default class TimetablesEditVehicleJourney extends Component { <li className='st_action'> <button type='button' - disabled={(actions.getSelected(this.props.vehicleJourneys).length != 1 || this.props.disabled)} + disabled={(actions.getSelected(this.props.vehicleJourneys).length < 1 || this.props.disabled)} data-toggle='modal' data-target='#CalendarsEditVehicleJourneyModal' onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))} + title='Calendriers' > <span className='fa fa-calendar'></span> </button> @@ -56,7 +57,7 @@ export default class TimetablesEditVehicleJourney extends Component { <div className='wrapper'> <div> <div className='form-group'> - <label className='control-label'>Calendriers associés</label> + <label className='control-label'>{this.props.modal.modalProps.timetables.length == 0 ? "Aucun calendrier associé" : "Calendriers associés"}</label> </div> </div> <div></div> @@ -65,9 +66,14 @@ export default class TimetablesEditVehicleJourney extends Component { {this.props.modal.modalProps.timetables.map((tt, i) => <div className='nested-fields' key={i}> <div className='wrapper'> - <div> <a href={this.timeTableURL(tt)} target="_blank">{tt.comment}</a> </div> + <div> + <a href={this.timeTableURL(tt)} target="_blank"> + <span className="fa fa-circle mr-xs" style={{color: tt.color || 'black'}}></span> + {tt.comment} + </a> + </div> { - this.props.editMode && + this.props.editMode && <div> <a href='#' @@ -85,13 +91,14 @@ export default class TimetablesEditVehicleJourney extends Component { </div> )} { - this.props.editMode && + this.props.editMode && <div className='nested-fields'> <div className='wrapper'> <div> <TimetableSelect2 onSelect2Timetable={this.props.onSelect2Timetable} chunkURL={'/autocomplete_time_tables.json'} + searchKey={"comment_or_objectid_cont_any"} isFilter={false} /> </div> @@ -103,7 +110,7 @@ export default class TimetablesEditVehicleJourney extends Component { </div> </div> { - this.props.editMode && + this.props.editMode && <div className='modal-footer'> <button className='btn btn-link' @@ -144,4 +151,4 @@ TimetablesEditVehicleJourney.propTypes = { onDeleteCalendarModal: PropTypes.func.isRequired, onSelect2Timetable: PropTypes.func.isRequired, disabled: PropTypes.bool.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js index 0697e9141..79ba8f094 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js @@ -21,7 +21,7 @@ export default class BSelect4 extends Component { value={(this.props.company) ? this.props.company.name : undefined} onSelect={(e) => this.props.onSelect2Company(e) } onUnselect={() => this.props.onUnselect2Company()} - disabled={!this.props.editMode} + disabled={!this.props.editMode && this.props.editModal} multiple={false} ref='company_id' options={{ @@ -36,7 +36,7 @@ export default class BSelect4 extends Component { delay: '500', data: function(params) { return { - q: { name_cont: actions.escapeWildcardCharacters(params.term)}, + q: { name_cont: params.term}, }; }, processResults: function(data, params) { diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js index 5b4ae564c..fa847886c 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js @@ -21,6 +21,8 @@ export default class BSelect4 extends Component { onSelect={(e) => this.props.onSelect2JourneyPattern(e)} multiple={false} ref='journey_pattern_id' + className={!this.props.isFilter ? "vjCreateSelectJP" : null} + required={!this.props.isFilter} options={{ allowClear: false, theme: 'bootstrap', @@ -33,7 +35,7 @@ export default class BSelect4 extends Component { delay: '500', data: function(params) { return { - q: { published_name_or_objectid_or_registration_number_cont: actions.escapeWildcardCharacters(params.term)}, + q: { published_name_or_objectid_or_registration_number_cont: params.term}, }; }, processResults: function(data, params) { diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js index a90a9f7b8..eb8651be2 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js @@ -31,12 +31,10 @@ export default class BSelect4 extends Component { url: origin + path + this.props.chunkURL, dataType: 'json', delay: '500', - data: function(params) { - return { - q: { - comment_or_objectid_cont_any: actions.escapeWildcardCharacters(params.term) - } - }; + data: (params) => { + let q = {} + q[this.props.searchKey] = params.term + return {q} }, processResults: function(data, params) { return { @@ -44,7 +42,7 @@ export default class BSelect4 extends Component { item => _.assign( {}, item, - {text: '<strong>' + "<span class='fa fa-circle' style='color:" + (item.color ? item.color : '#4B4B4B') + "'></span> " + item.comment + ' - ' + item.short_id + '</strong><br/><small>' + (item.day_types ? item.day_types.match(/[A-Z]?[a-z]+/g).join(', ') : "") + '</small>'} + {text: '<strong>' + "<span class='fa fa-circle' style='color:" + (item.color ? item.color : '#4B4B4B') + "'></span> " + (item.comment || item.name) + ' - ' + item.short_id + '</strong><br/><small>' + (item.day_types ? item.day_types.match(/[A-Z]?[a-z]+/g).join(', ') : "") + '</small>'} ) ) }; @@ -62,4 +60,4 @@ export default class BSelect4 extends Component { const formatRepo = (props) => { if(props.text) return props.text -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js index 37628fce0..b063abeca 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js @@ -33,7 +33,7 @@ export default class BSelect4b extends Component { delay: '500', data: function(params) { return { - q: { objectid_cont: actions.escapeWildcardCharacters(params.term)}, + q: { objectid_cont: params.term}, }; }, processResults: function(data, params) { diff --git a/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js b/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js index 18f9e994e..f5f879ed8 100644 --- a/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js +++ b/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js @@ -17,6 +17,10 @@ const mapDispatchToProps = (dispatch) => { onEnterEditMode: () => { dispatch(actions.enterEditMode()) }, + onExitEditMode: () => { + dispatch(actions.cancelSelection()) + dispatch(actions.exitEditMode()) + }, onSubmitVehicleJourneys: (next, state) => { actions.submitVehicleJourneys(dispatch, state, next) } diff --git a/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js new file mode 100644 index 000000000..3fef44489 --- /dev/null +++ b/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js @@ -0,0 +1,38 @@ +import actions from '../../actions' +import { connect } from 'react-redux' +import PurchaseWindowsEditVehicleJourneyComponent from '../../components/tools/PurchaseWindowsEditVehicleJourney' + +const mapStateToProps = (state, ownProps) => { + return { + editMode: state.editMode, + modal: state.modal, + vehicleJourneys: state.vehicleJourneys, + status: state.status, + disabled: ownProps.disabled + } +} + +const mapDispatchToProps = (dispatch) => { + return { + onModalClose: () =>{ + dispatch(actions.closeModal()) + }, + onOpenCalendarsEditModal: (vehicleJourneys) =>{ + dispatch(actions.openPurchaseWindowsEditModal(vehicleJourneys)) + }, + onDeleteCalendarModal: (timetable) => { + dispatch(actions.deletePurchaseWindowsModal(timetable)) + }, + onShoppingWindowsEditVehicleJourney: (vehicleJourneys, timetables) =>{ + dispatch(actions.editVehicleJourneyPurchaseWindows(vehicleJourneys, timetables)) + }, + onSelect2Timetable: (e) =>{ + dispatch(actions.selectPurchaseWindowsModal(e.params.data)) + dispatch(actions.addSelectedPurchaseWindow()) + } + } +} + +const PurchaseWindowsEditVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(PurchaseWindowsEditVehicleJourneyComponent) + +export default PurchaseWindowsEditVehicleJourney diff --git a/app/javascript/vehicle_journeys/reducers/modal.js b/app/javascript/vehicle_journeys/reducers/modal.js index 57f54a144..eae3314e8 100644 --- a/app/javascript/vehicle_journeys/reducers/modal.js +++ b/app/javascript/vehicle_journeys/reducers/modal.js @@ -1,6 +1,6 @@ import _ from 'lodash' -let vehicleJourneysModal, newModalProps +let vehicleJourneysModal, newModalProps, vehicleJourney export default function modal(state = {}, action) { switch (action.type) { @@ -40,7 +40,6 @@ export default function modal(state = {}, action) { case 'EDIT_CALENDARS_VEHICLEJOURNEY_MODAL': vehicleJourneysModal = JSON.parse(JSON.stringify(action.vehicleJourneys)) let uniqTimetables = [] - let timetable = {} vehicleJourneysModal.map((vj, i) => { vj.time_tables.map((tt, j) =>{ if(!(_.find(uniqTimetables, tt))){ @@ -56,15 +55,38 @@ export default function modal(state = {}, action) { }, confirmModal: {} } + case 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL': + var vehicleJourneys = JSON.parse(JSON.stringify(action.vehicleJourneys)) + let uniqPurchaseWindows = [] + vehicleJourneys.map((vj, i) => { + vj.purchase_windows.map((pw, j) =>{ + if(!(_.find(uniqPurchaseWindows, pw))){ + uniqPurchaseWindows.push(pw) + } + }) + }) + return { + type: 'purchase_windows_edit', + modalProps: { + vehicleJourneys: vehicleJourneys, + purchase_windows: uniqPurchaseWindows + }, + confirmModal: {} + } case 'SELECT_CP_EDIT_MODAL': - newModalProps = _.assign({}, state.modalProps, {selectedCompany : action.selectedItem}) + vehicleJourney = _.assign({}, state.modalProps.vehicleJourney, {company: action.selectedItem}) + newModalProps = _.assign({}, state.modalProps, {vehicleJourney}) return _.assign({}, state, {modalProps: newModalProps}) case 'UNSELECT_CP_EDIT_MODAL': - newModalProps = _.assign({}, state.modalProps, {selectedCompany : undefined}) + vehicleJourney = _.assign({}, state.modalProps.vehicleJourney, {company: undefined}) + newModalProps = _.assign({}, state.modalProps, {vehicleJourney}) return _.assign({}, state, {modalProps: newModalProps}) case 'SELECT_TT_CALENDAR_MODAL': newModalProps = _.assign({}, state.modalProps, {selectedTimetable : action.selectedItem}) return _.assign({}, state, {modalProps: newModalProps}) + case 'SELECT_PURCHASE_WINDOW_MODAL': + newModalProps = _.assign({}, state.modalProps, {selectedPurchaseWindow : action.selectedItem}) + return _.assign({}, state, {modalProps: newModalProps}) case 'ADD_SELECTED_TIMETABLE': if(state.modalProps.selectedTimetable){ newModalProps = JSON.parse(JSON.stringify(state.modalProps)) @@ -73,6 +95,14 @@ export default function modal(state = {}, action) { } return _.assign({}, state, {modalProps: newModalProps}) } + case 'ADD_SELECTED_PURCHASE_WINDOW': + if(state.modalProps.selectedPurchaseWindow){ + newModalProps = JSON.parse(JSON.stringify(state.modalProps)) + if (!_.find(newModalProps.purchase_windows, newModalProps.selectedPurchaseWindow)){ + newModalProps.purchase_windows.push(newModalProps.selectedPurchaseWindow) + } + return _.assign({}, state, {modalProps: newModalProps}) + } case 'DELETE_CALENDAR_MODAL': newModalProps = JSON.parse(JSON.stringify(state.modalProps)) let timetablesModal = state.modalProps.timetables.slice(0) @@ -92,6 +122,25 @@ export default function modal(state = {}, action) { newModalProps.vehicleJourneys = vehicleJourneysModal newModalProps.timetables = timetablesModal return _.assign({}, state, {modalProps: newModalProps}) + case 'DELETE_PURCHASE_WINDOW_MODAL': + newModalProps = JSON.parse(JSON.stringify(state.modalProps)) + let purchase_windows = state.modalProps.purchase_windows.slice(0) + purchase_windows.map((tt, i) =>{ + if(tt == action.purchaseWindow){ + purchase_windows.splice(i, 1) + } + }) + vehicleJourneysModal = state.modalProps.vehicleJourneys.slice(0) + vehicleJourneysModal.map((vj) =>{ + vj.purchase_windows.map((tt, i) =>{ + if (_.isEqual(tt, action.purchaseWindow)){ + vj.purchase_windows.splice(i, 1) + } + }) + }) + newModalProps.vehicleJourneys = vehicleJourneysModal + newModalProps.purchase_windows = purchase_windows + return _.assign({}, state, {modalProps: newModalProps}) case 'CREATE_VEHICLEJOURNEY_MODAL': let selectedJP = {} if (window.jpOrigin){ @@ -135,4 +184,4 @@ export default function modal(state = {}, action) { default: return state } -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index 775fefdca..d057bf704 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -38,8 +38,10 @@ const vehicleJourney= (state = {}, action, keep) => { published_journey_name: action.data.published_journey_name.value, published_journey_identifier: action.data.published_journey_identifier.value, objectid: '', + short_id: '', footnotes: [], time_tables: [], + purchase_windows: [], vehicle_journey_at_stops: pristineVjasList, selected: false, deletable: false, @@ -78,18 +80,12 @@ const vehicleJourney= (state = {}, action, keep) => { if (action.isDeparture){ newSchedule.departure_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) if(!action.isArrivalsToggled) - newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) - newSchedule = actions.getDelta(newSchedule) - if(newSchedule.delta < 0){ - return vjas - } + newSchedule.arrival_time[action.timeUnit] = newSchedule.departure_time[action.timeUnit] + newSchedule = actions.adjustSchedule(action, newSchedule) return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta}) }else{ newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) - newSchedule = actions.getDelta(newSchedule) - if(newSchedule.delta < 0){ - return vjas - } + newSchedule = actions.adjustSchedule(action, newSchedule) return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta}) } }else{ @@ -160,6 +156,21 @@ export default function vehicleJourneys(state = [], action) { return vj } }) + case 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS': + let newWindows = JSON.parse(JSON.stringify(action.purchase_windows)) + return state.map((vj,i) =>{ + if(vj.selected){ + let updatedVJ = _.assign({}, vj) + action.vehicleJourneys.map((vjm, j) =>{ + if(vj.objectid == vjm.objectid){ + updatedVJ.purchase_windows = newWindows + } + }) + return updatedVJ + }else{ + return vj + } + }) case 'SHIFT_VEHICLEJOURNEY': return state.map((vj, i) => { if (vj.selected){ @@ -224,4 +235,4 @@ export default function vehicleJourneys(state = [], action) { default: return state } -}
\ No newline at end of file +} diff --git a/app/models/calendar.rb b/app/models/calendar.rb index b2e73929f..34ed51374 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -3,22 +3,19 @@ require_relative 'calendar/date_value' require_relative 'calendar/period' class Calendar < ActiveRecord::Base + include DateSupport + include PeriodSupport + has_paper_trail belongs_to :organisation - has_many :time_tables validates_presence_of :name, :short_name, :organisation validates_uniqueness_of :short_name - after_initialize :init_dates_and_date_ranges + has_many :time_tables scope :contains_date, ->(date) { where('date ? = any (dates) OR date ? <@ any (date_ranges)', date, date) } - def init_dates_and_date_ranges - self.dates ||= [] - self.date_ranges ||= [] - end - def self.ransackable_scopes(auth_object = nil) [:contains_date] end @@ -35,144 +32,4 @@ class Calendar < ActiveRecord::Base end end - - ### Calendar::Period - # Required by coocon - def build_period - Calendar::Period.new - end - - def periods - @periods ||= init_periods - end - - def init_periods - (date_ranges || []) - .each_with_index - .map( &Calendar::Period.method(:from_range) ) - end - private :init_periods - - validate :validate_periods - - def validate_periods - periods_are_valid = periods.all?(&:valid?) - - periods.each do |period| - if period.intersect?(periods) - period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods')) - periods_are_valid = false - end - end - - unless periods_are_valid - errors.add(:periods, :invalid) - end - end - - def flatten_date_array attributes, key - date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i } - Date.new(*date_int) - end - - def periods_attributes=(attributes = {}) - @periods = [] - attributes.each do |index, period_attribute| - # Convert date_select to date - ['begin', 'end'].map do |attr| - period_attribute[attr] = flatten_date_array(period_attribute, attr) - end - period = Calendar::Period.new(period_attribute.merge(id: index)) - @periods << period unless period.marked_for_destruction? - end - - date_ranges_will_change! - end - - before_validation :fill_date_ranges - - def fill_date_ranges - if @periods - self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin) - end - end - - after_save :clear_periods - - def clear_periods - @periods = nil - end - - private :clear_periods - - ### Calendar::DateValue - - # Required by coocon - def build_date_value - Calendar::DateValue.new - end - - def date_values - @date_values ||= init_date_values - end - - def init_date_values - if dates - dates.each_with_index.map { |d, index| Calendar::DateValue.from_date(index, d) } - else - [] - end - end - private :init_date_values - - validate :validate_date_values - - def validate_date_values - date_values_are_valid = date_values.all?(&:valid?) - - date_values.each do |date_value| - if date_values.count { |d| d.value == date_value.value } > 1 - date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates')) - date_values_are_valid = false - end - date_ranges.each do |date_range| - if date_range.cover? date_value.value - date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) - date_values_are_valid = false - end - end - end - - unless date_values_are_valid - errors.add(:date_values, :invalid) - end - end - - def date_values_attributes=(attributes = {}) - @date_values = [] - attributes.each do |index, date_value_attribute| - date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value') - date_value = Calendar::DateValue.new(date_value_attribute.merge(id: index)) - @date_values << date_value unless date_value.marked_for_destruction? - end - - dates_will_change! - end - - before_validation :fill_dates - - def fill_dates - if @date_values - self.dates = @date_values.map(&:value).compact.sort - end - end - - after_save :clear_date_values - - def clear_date_values - @date_values = nil - end - - private :clear_date_values - end diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb index 1c423dfcc..56ab722fe 100644 --- a/app/models/calendar/period.rb +++ b/app/models/calendar/period.rb @@ -1,5 +1,5 @@ class Calendar < ActiveRecord::Base - + class Period include ActiveAttr::Model diff --git a/app/models/chouette/area_type.rb b/app/models/chouette/area_type.rb new file mode 100644 index 000000000..43d96b391 --- /dev/null +++ b/app/models/chouette/area_type.rb @@ -0,0 +1,42 @@ +class Chouette::AreaType + include Comparable + + ALL = %i(zdep zder zdlp zdlr lda gdl).freeze + + @@all = ALL + mattr_accessor :all + + def self.all=(values) + @@all = ALL & values + reset_caches! + end + + @@instances = {} + def self.find(code) + code = code.to_sym + @@instances[code] ||= new(code) if ALL.include? code + end + + def self.reset_caches! + @@instances = {} + @@options = nil + end + + def self.options + @@options ||= all.map { |c| find(c) }.map { |t| [ t.label, t.code ] } + end + + attr_reader :code + def initialize(code) + @code = code + end + + def <=>(other) + all.index(code) <=> all.index(other.code) + end + + def label + I18n.translate code, scope: 'area_types.label' + end + +end diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index 784e3f5b9..2d776e94b 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -72,12 +72,23 @@ module Chouette end def display_name - [self.get_objectid.local_id, number, name, company.try(:name)].compact.join(' - ') + [self.get_objectid.short_id, number, name, company.try(:name)].compact.join(' - ') end def companies line_referential.companies.where(id: ([company_id] + Array(secondary_company_ids)).compact) end + def deactivate! + update_attribute :deactivated, true + end + + def activate! + update_attribute :deactivated, false + end + + def activated? + !deactivated + end end end diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb new file mode 100644 index 000000000..e89a0ec7f --- /dev/null +++ b/app/models/chouette/purchase_window.rb @@ -0,0 +1,36 @@ +require 'range_ext' +require_relative '../calendar/period' + +module Chouette + class PurchaseWindow < Chouette::TridentActiveRecord + # include ChecksumSupport + include ObjectidSupport + include PeriodSupport + extend Enumerize + enumerize :color, in: %w(#9B9B9B #FFA070 #C67300 #7F551B #41CCE3 #09B09C #3655D7 #6321A0 #E796C6 #DD2DAA) + + has_paper_trail + belongs_to :referential + has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney' + + validates_presence_of :name, :referential + + scope :contains_date, ->(date) { where('date ? <@ any (date_ranges)', date) } + + def self.ransackable_scopes(auth_object = nil) + [:contains_date] + end + + def self.colors_i18n + Hash[*color.values.map{|c| [I18n.t("enumerize.purchase_window.color.#{c[1..-1]}"), c]}.flatten] + end + + def local_id + "IBOO-#{self.referential.id}-#{self.id}" + end + + # def checksum_attributes + # end + + end +end diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index cc7170728..4f1359ff8 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -9,7 +9,7 @@ module Chouette include ObjectidSupport extend Enumerize - enumerize :area_type, in: %i(zdep zder zdlp zdlr lda) + enumerize :area_type, in: Chouette::AreaType::ALL with_options dependent: :destroy do |assoc| assoc.has_many :stop_points @@ -39,11 +39,21 @@ module Chouette validates_format_of :coordinates, :with => %r{\A *-?(0?[0-9](\.[0-9]*)?|[0-8][0-9](\.[0-9]*)?|90(\.[0]*)?) *\, *-?(0?[0-9]?[0-9](\.[0-9]*)?|1[0-7][0-9](\.[0-9]*)?|180(\.[0]*)?) *\Z}, :allow_nil => true, :allow_blank => true validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true + validates_numericality_of :waiting_time, greater_than_or_equal_to: 0, only_integer: true, if: :waiting_time + validate :parent_area_type_must_be_greater + def self.nullable_attributes [:registration_number, :street_name, :country_code, :fare_code, :nearest_topic_name, :comment, :long_lat_type, :zip_code, :city_name, :url, :time_zone] end + def parent_area_type_must_be_greater + return unless self.parent + if Chouette::AreaType.find(self.area_type) >= Chouette::AreaType.find(self.parent.area_type) + errors.add(:parent_id, I18n.t('stop_areas.errors.parent_area_type', area_type: self.parent.area_type)) + end + end + after_update :clean_invalid_access_links before_save :coordinates_to_lat_lng @@ -72,6 +82,10 @@ module Chouette end end + def full_name + "#{name} #{zip_code} #{city_name} - #{user_objectid}" + end + def user_objectid if objectid =~ /^.*:([0-9A-Za-z_-]+):STIF$/ $1 @@ -196,10 +210,12 @@ module Chouette GeoRuby::SimpleFeatures::Envelope.from_coordinates coordinates end + # DEPRECATED use StopArea#area_type def stop_area_type area_type ? area_type : " " end + # DEPRECATED use StopArea#area_type def stop_area_type=(stop_area_type) self.area_type = (stop_area_type ? stop_area_type.camelcase : nil) end @@ -324,5 +340,20 @@ module Chouette end end + def activated? + deleted_at.nil? + end + + def deactivated? + !activated? + end + + def activate! + update_attribute :deleted_at, nil + end + + def deactivate! + update_attribute :deleted_at, Time.now + end end end diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 247c30668..983bf5363 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -1,3 +1,4 @@ +# coding: utf-8 module Chouette class VehicleJourney < Chouette::TridentActiveRecord has_paper_trail @@ -23,6 +24,7 @@ module Chouette belongs_to :journey_pattern has_and_belongs_to_many :footnotes, :class_name => 'Chouette::Footnote' + has_and_belongs_to_many :purchase_windows, :class_name => 'Chouette::PurchaseWindow' validates_presence_of :route validates_presence_of :journey_pattern @@ -139,7 +141,7 @@ module Chouette end def update_has_and_belongs_to_many_from_state item - ['time_tables', 'footnotes'].each do |assos| + ['time_tables', 'footnotes', 'purchase_windows'].each do |assos| saved = self.send(assos).map(&:id) (saved - item[assos].map{|t| t['id']}).each do |id| @@ -241,11 +243,9 @@ module Chouette end def self.matrix(vehicle_journeys) - {}.tap do |hash| - vehicle_journeys.map{ |vj| - vj.vehicle_journey_at_stops.map{ |vjas |hash[ "#{vj.id}-#{vjas.stop_point_id}"] = vjas } - } - end + Hash[*VehicleJourneyAtStop.where(vehicle_journey_id: vehicle_journeys.pluck(:id)).map do |vjas| + [ "#{vjas.vehicle_journey_id}-#{vjas.stop_point_id}", vjas] + end.flatten] end def self.with_stops diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb index 6f0119e74..6b3c1e7de 100644 --- a/app/models/chouette/vehicle_journey_at_stop.rb +++ b/app/models/chouette/vehicle_journey_at_stop.rb @@ -75,5 +75,14 @@ module Chouette attrs << self.arrival_day_offset.to_s end end + + def departure + departure_time.utc.strftime "%H:%M" if departure_time + end + + def arrival + arrival_time.utc.strftime "%H:%M" if arrival_time + end + end -end
\ No newline at end of file +end diff --git a/app/models/compliance_check_block.rb b/app/models/compliance_check_block.rb index 05240b428..059547e1b 100644 --- a/app/models/compliance_check_block.rb +++ b/app/models/compliance_check_block.rb @@ -6,8 +6,8 @@ class ComplianceCheckBlock < ActiveRecord::Base has_many :compliance_checks - hstore_accessor :condition_attributes, - transport_mode: :string, - transport_submode: :string + store_accessor :condition_attributes, + :transport_mode, + :transport_submode end diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb index 65e22643d..2bde5b95a 100644 --- a/app/models/compliance_control.rb +++ b/app/models/compliance_control.rb @@ -6,7 +6,7 @@ class ComplianceControl < ActiveRecord::Base def prerequisite; I18n.t('compliance_controls.metas.no_prerequisite'); end def predicate; I18n.t("compliance_controls.#{self.name.underscore}.description") end def dynamic_attributes - hstore_metadata_for_control_attributes.keys + stored_attributes[:control_attributes] || [] end def policy_class @@ -39,7 +39,6 @@ class ComplianceControl < ActiveRecord::Base belongs_to :compliance_control_block enumerize :criticity, in: criticities, scope: true, default: :warning - hstore_accessor :control_attributes, {} validates :criticity, presence: true validates :name, presence: true diff --git a/app/models/compliance_control_block.rb b/app/models/compliance_control_block.rb index cfcdfd1a6..d7d84fd06 100644 --- a/app/models/compliance_control_block.rb +++ b/app/models/compliance_control_block.rb @@ -5,11 +5,15 @@ class ComplianceControlBlock < ActiveRecord::Base belongs_to :compliance_control_set has_many :compliance_controls, dependent: :destroy - hstore_accessor :condition_attributes, - transport_mode: :string, - transport_submode: :string + store_accessor :condition_attributes, + :transport_mode, + :transport_submode validates :transport_mode, presence: true validates :compliance_control_set, presence: true + def name + ApplicationController.helpers.transport_mode_text(self) + end + end diff --git a/app/models/concerns/date_support.rb b/app/models/concerns/date_support.rb new file mode 100644 index 000000000..fbfe19af1 --- /dev/null +++ b/app/models/concerns/date_support.rb @@ -0,0 +1,80 @@ +module DateSupport + extend ActiveSupport::Concern + + included do + after_initialize :init_dates + + def init_dates + self.dates ||= [] + end + + ### Calendar::DateValue + # Required by coocon + def build_date_value + Calendar::DateValue.new + end + + def date_values + @date_values ||= init_date_values + end + + def init_date_values + if dates + dates.each_with_index.map { |d, index| Calendar::DateValue.from_date(index, d) } + else + [] + end + end + private :init_date_values + + validate :validate_date_values + + def validate_date_values + date_values_are_valid = date_values.all?(&:valid?) + + date_values.each do |date_value| + if date_values.count { |d| d.value == date_value.value } > 1 + date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates')) + date_values_are_valid = false + end + date_ranges.each do |date_range| + if date_range.cover? date_value.value + date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) + date_values_are_valid = false + end + end + end + + unless date_values_are_valid + errors.add(:date_values, :invalid) + end + end + + def date_values_attributes=(attributes = {}) + @date_values = [] + attributes.each do |index, date_value_attribute| + date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value') + date_value = Calendar::DateValue.new(date_value_attribute.merge(id: index)) + @date_values << date_value unless date_value.marked_for_destruction? + end + + dates_will_change! + end + + before_validation :fill_dates + + def fill_dates + if @date_values + self.dates = @date_values.map(&:value).compact.sort + end + end + + after_save :clear_date_values + + def clear_date_values + @date_values = nil + end + + private :clear_date_values + end +end
\ No newline at end of file diff --git a/app/models/concerns/period_support.rb b/app/models/concerns/period_support.rb new file mode 100644 index 000000000..e17451fe4 --- /dev/null +++ b/app/models/concerns/period_support.rb @@ -0,0 +1,80 @@ +module PeriodSupport + extend ActiveSupport::Concern + + included do + after_initialize :init_date_ranges + + def init_date_ranges + self.date_ranges ||= [] + end + + ### Calendar::Period + # Required by coocon + def build_period + Calendar::Period.new + end + + def periods + @periods ||= init_periods + end + + def init_periods + (date_ranges || []) + .each_with_index + .map( &Calendar::Period.method(:from_range) ) + end + private :init_periods + + validate :validate_periods + + def validate_periods + periods_are_valid = periods.all?(&:valid?) + + periods.each do |period| + if period.intersect?(periods) + period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods')) + periods_are_valid = false + end + end + + unless periods_are_valid + errors.add(:periods, :invalid) + end + end + + def flatten_date_array attributes, key + date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i } + Date.new(*date_int) + end + + def periods_attributes=(attributes = {}) + @periods = [] + attributes.each do |index, period_attribute| + # Convert date_select to date + ['begin', 'end'].map do |attr| + period_attribute[attr] = flatten_date_array(period_attribute, attr) + end + period = Calendar::Period.new(period_attribute.merge(id: index)) + @periods << period unless period.marked_for_destruction? + end + + date_ranges_will_change! + end + + before_validation :fill_date_ranges + + def fill_date_ranges + if @periods + self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin) + end + end + + after_save :clear_periods + + def clear_periods + @periods = nil + end + + private :clear_periods + end +end diff --git a/app/models/generic_attribute_control/min_max.rb b/app/models/generic_attribute_control/min_max.rb index ab6f546a7..1c429b9a4 100644 --- a/app/models/generic_attribute_control/min_max.rb +++ b/app/models/generic_attribute_control/min_max.rb @@ -1,6 +1,6 @@ module GenericAttributeControl class MinMax < ComplianceControl - hstore_accessor :control_attributes, minimum: :integer, maximum: :integer, target: :string + store_accessor :control_attributes, :minimum, :maximum, :target validates :minimum, numericality: true, allow_nil: true validates :maximum, numericality: true, allow_nil: true diff --git a/app/models/generic_attribute_control/pattern.rb b/app/models/generic_attribute_control/pattern.rb index 3a4a55d5c..7fc008e28 100644 --- a/app/models/generic_attribute_control/pattern.rb +++ b/app/models/generic_attribute_control/pattern.rb @@ -1,6 +1,6 @@ module GenericAttributeControl class Pattern < ComplianceControl - hstore_accessor :control_attributes, pattern: :string, target: :string + store_accessor :control_attributes, :pattern, :target validates :target, presence: true validates :pattern, presence: true diff --git a/app/models/generic_attribute_control/uniqueness.rb b/app/models/generic_attribute_control/uniqueness.rb index f707c944b..82b5c0892 100644 --- a/app/models/generic_attribute_control/uniqueness.rb +++ b/app/models/generic_attribute_control/uniqueness.rb @@ -1,6 +1,6 @@ module GenericAttributeControl class Uniqueness < ComplianceControl - hstore_accessor :control_attributes, target: :string + store_accessor :control_attributes, :target validates :target, presence: true diff --git a/app/models/import.rb b/app/models/import.rb index 20e7f2d8a..049a65f40 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -14,12 +14,11 @@ class Import < ActiveRecord::Base end extend Enumerize - enumerize :status, in: %i(new pending successful warning failed running aborted canceled), scope: true, default: :new + enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new validates :name, presence: true validates :file, presence: true validates_presence_of :workbench, :creator - validates_format_of :file, with: %r{\.zip\z}i, message: I18n.t('activerecord.errors.models.import.attributes.file.wrong_file_extension') before_create :initialize_fields @@ -28,15 +27,19 @@ class Import < ActiveRecord::Base end def children_succeedeed - children.with_status(:successful).count + children.with_status(:successful, :warning).count end - def self.failing_statuses - symbols_with_indifferent_access(%i(failed aborted canceled)) + def self.launched_statuses + %w(new pending) + end + + def self.failed_statuses + %w(failed aborted canceled) end def self.finished_statuses - symbols_with_indifferent_access(%i(successful failed warning aborted canceled)) + %w(successful failed warning aborted canceled) end def notify_parent @@ -52,35 +55,25 @@ class Import < ActiveRecord::Base end def update_status - status_count = children.group(:status).count - children_finished_count = children_failed_count = children_count = 0 - - status_count.each do |status, count| - if self.class.failing_statuses.include?(status) - children_failed_count += count - end - if self.class.finished_statuses.include?(status) - children_finished_count += count - end - children_count += count - end - - attributes = { - current_step: children_finished_count - } - status = - if children_failed_count > 0 + if children.where(status: self.class.failed_statuses).count > 0 'failed' - elsif status_count['successful'] == children_count + elsif children.where(status: "warning").count > 0 + 'warning' + elsif children.where(status: "successful").count == children.count 'successful' end + attributes = { + current_step: children.count, + status: status + } + if self.class.finished_statuses.include?(status) attributes[:ended_at] = Time.now end - update attributes.merge(status: status) + update attributes end def update_referentials @@ -97,7 +90,4 @@ class Import < ActiveRecord::Base self.token_download = SecureRandom.urlsafe_base64 end - def self.symbols_with_indifferent_access(array) - array.flat_map { |symbol| [symbol, symbol.to_s] } - end end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index f6fba2d67..da7d1fcf3 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -1,3 +1,4 @@ +# coding: utf-8 class Organisation < ActiveRecord::Base include DataFormatEnumerations @@ -18,36 +19,39 @@ class Organisation < ActiveRecord::Base validates_presence_of :name validates_uniqueness_of :code - def self.portail_api_request - conf = Rails.application.config.try(:stif_portail_api) - raise 'Rails.application.config.stif_portail_api configuration is not defined' unless conf + class << self - HTTPService.get_json_resource( - host: conf[:url], - path: '/api/v1/organizations', - token: conf[:key]) - end + def portail_api_request + conf = Rails.application.config.try(:stif_portail_api) + raise 'Rails.application.config.stif_portail_api configuration is not defined' unless conf + + HTTPService.get_json_resource( + host: conf[:url], + path: '/api/v1/organizations', + token: conf[:key]) + end - def self.sync_update code, name, scope - org = Organisation.find_or_initialize_by(code: code) - if scope - org.sso_attributes ||= {} - if org.sso_attributes['functional_scope'] != scope - org.sso_attributes['functional_scope'] = scope - # FIXME see #1941 - org.sso_attributes_will_change! + def sync_update code, name, scope + org = Organisation.find_or_initialize_by(code: code) + if scope + org.sso_attributes ||= {} + if org.sso_attributes['functional_scope'] != scope + org.sso_attributes['functional_scope'] = scope + # FIXME see #1941 + org.sso_attributes_will_change! + end end + org.name = name + org.synced_at = Time.now + org.save + org end - org.name = name - org.synced_at = Time.now - org.save - org - end - def self.portail_sync - self.portail_api_request.each do |el| - org = self.sync_update el['code'], el['name'], el['functional_scope'] - puts "✓ Organisation #{org.name} has been updated" unless Rails.env.test? + def portail_sync + portail_api_request.each do |el| + org = self.sync_update el['code'], el['name'], el['functional_scope'] + puts "✓ Organisation #{org.name} has been updated" unless Rails.env.test? + end end end @@ -64,4 +68,16 @@ class Organisation < ActiveRecord::Base raise ActiveRecord::RecordNotFound end + def functional_scope + JSON.parse( (sso_attributes || {}).fetch('functional_scope', '[]') ) + end + + def lines_set + STIF::CodifligneLineId.lines_set_from_functional_scope( functional_scope ) + end + + def has_feature?(feature) + features && features.include?(feature.to_s) + end + end diff --git a/app/models/referential.rb b/app/models/referential.rb index 3a9ef2027..1cdda9e6a 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -1,3 +1,4 @@ +# coding: utf-8 class Referential < ActiveRecord::Base include DataFormatEnumerations include ObjectidFormatterSupport @@ -34,7 +35,7 @@ class Referential < ActiveRecord::Base I18n.t('referentials.errors.inconsistent_organisation', indirect_name: workbench.organisation.name, direct_name: organisation.name)) - end + end, if: :organisation belongs_to :line_referential validates_presence_of :line_referential @@ -139,6 +140,10 @@ class Referential < ActiveRecord::Base Chouette::RoutingConstraintZone.all end + def purchase_windows + Chouette::PurchaseWindow.all + end + before_validation :define_default_attributes def define_default_attributes @@ -295,7 +300,8 @@ class Referential < ActiveRecord::Base def detect_overlapped_referentials self.class.where(id: overlapped_referential_ids).each do |referential| - errors.add :metadatas, I18n.t("referentials.errors.overlapped_referential", referential: referential.name) + Rails.logger.info "Referential #{referential.id} #{referential.metadatas.inspect} overlaps #{metadatas.inspect}" + errors.add :metadatas, I18n.t("referentials.errors.overlapped_referential", :referential => referential.name) end end @@ -315,11 +321,11 @@ class Referential < ActiveRecord::Base end def assign_slug - self.slug ||= "#{self.name.parameterize.gsub('-', '_')}_#{Time.now.to_i}" + self.slug ||= "#{name.parameterize.gsub('-', '_')}_#{Time.now.to_i}" if name end def assign_prefix - self.prefix = organisation.name.parameterize.gsub('-', '_') + self.prefix = organisation.name.parameterize.gsub('-', '_') if organisation end def assign_line_and_stop_area_referential diff --git a/app/models/referential_cloning.rb b/app/models/referential_cloning.rb index 5bf283814..24117e6c8 100644 --- a/app/models/referential_cloning.rb +++ b/app/models/referential_cloning.rb @@ -2,14 +2,27 @@ class ReferentialCloning < ActiveRecord::Base include AASM belongs_to :source_referential, class_name: 'Referential' belongs_to :target_referential, class_name: 'Referential' - after_commit :perform_clone, :on => :create + after_commit :clone, on: :create - private - def perform_clone + def clone ReferentialCloningWorker.perform_async(id) - # ReferentialCloningWorker.new.perform(id) end + def clone! + run! + + AF83::SchemaCloner + .new(source_referential.slug, target_referential.slug) + .clone_schema + + successful! + rescue Exception => e + Rails.logger.error "Clone failed : #{e}" + failed! + end + + private + aasm column: :status do state :new, :initial => true state :pending diff --git a/app/models/user.rb b/app/models/user.rb index 37d35209a..1342f60ed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,7 +36,7 @@ class User < ActiveRecord::Base self.name = extra[:full_name] self.email = extra[:email] self.organisation = Organisation.sync_update extra[:organisation_code], extra[:organisation_name], extra[:functional_scope] - self.permissions = Stif::PermissionTranslator.translate(extra[:permissions]) + self.permissions = Stif::PermissionTranslator.translate(extra[:permissions], self.organisation) end def self.portail_api_request diff --git a/app/models/vehicle_journey_control/delta.rb b/app/models/vehicle_journey_control/delta.rb index 1f3a4d492..077dd6c4a 100644 --- a/app/models/vehicle_journey_control/delta.rb +++ b/app/models/vehicle_journey_control/delta.rb @@ -1,7 +1,7 @@ module VehicleJourneyControl class Delta < ComplianceControl - hstore_accessor :control_attributes, maximum: :integer + store_accessor :control_attributes, :maximum validates :maximum, numericality: true, allow_nil: true diff --git a/app/models/vehicle_journey_control/speed.rb b/app/models/vehicle_journey_control/speed.rb index be9f838e4..14fad9139 100644 --- a/app/models/vehicle_journey_control/speed.rb +++ b/app/models/vehicle_journey_control/speed.rb @@ -1,6 +1,6 @@ module VehicleJourneyControl class Speed < ComplianceControl - hstore_accessor :control_attributes, minimum: :integer, maximum: :integer + store_accessor :control_attributes, :minimum, :maximum validates :minimum, numericality: true, allow_nil: true validates :maximum, numericality: true, allow_nil: true diff --git a/app/models/vehicle_journey_control/waiting_time.rb b/app/models/vehicle_journey_control/waiting_time.rb index 68fccb5c1..f2666cb72 100644 --- a/app/models/vehicle_journey_control/waiting_time.rb +++ b/app/models/vehicle_journey_control/waiting_time.rb @@ -1,8 +1,8 @@ module VehicleJourneyControl class WaitingTime < ComplianceControl - hstore_accessor :control_attributes, maximum: :integer + store_accessor :control_attributes, :maximum - validates :maximum, numericality: true, allow_nil: true + validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 def self.default_code; "3-VehicleJourney-1" end end diff --git a/app/policies/calendar_policy.rb b/app/policies/calendar_policy.rb index 074c41d8d..c2da8c924 100644 --- a/app/policies/calendar_policy.rb +++ b/app/policies/calendar_policy.rb @@ -5,18 +5,15 @@ class CalendarPolicy < ApplicationPolicy end end - def create? - !archived? && user.has_permission?('calendars.create') - end - def destroy? - !archived? & organisation_match? && user.has_permission?('calendars.destroy') - end - def update? - !archived? && organisation_match? && user.has_permission?('calendars.update') + def create? + user.has_permission?('calendars.create') end + def destroy?; instance_permission("destroy") end + def update?; instance_permission("update") end + def share?; instance_permission("share") end - def share? - user.organisation.name == 'STIF' # FIXME + private + def instance_permission permission + organisation_match? && user.has_permission?("calendars.#{permission}") end - end diff --git a/app/policies/line_policy.rb b/app/policies/line_policy.rb index 67ea0b611..e7263cc3b 100644 --- a/app/policies/line_policy.rb +++ b/app/policies/line_policy.rb @@ -6,7 +6,6 @@ class LinePolicy < ApplicationPolicy end def create? - Rails.logger.debug "LinePolicy.create?" user.has_permission?('lines.create') end @@ -14,6 +13,14 @@ class LinePolicy < ApplicationPolicy user.has_permission?('lines.destroy') end + def deactivate? + !record.deactivated? && user.has_permission?('lines.change_status') + end + + def activate? + record.deactivated? && user.has_permission?('lines.change_status') + end + def update? user.has_permission?('lines.update') end diff --git a/app/policies/line_referential_policy.rb b/app/policies/line_referential_policy.rb new file mode 100644 index 000000000..ee742a083 --- /dev/null +++ b/app/policies/line_referential_policy.rb @@ -0,0 +1,14 @@ +class LineReferentialPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def synchronize?; instance_permission("synchronize") end + + private + def instance_permission permission + user.has_permission?("line_referentials.#{permission}") + end +end diff --git a/app/policies/purchase_window_policy.rb b/app/policies/purchase_window_policy.rb new file mode 100644 index 000000000..75143a8bd --- /dev/null +++ b/app/policies/purchase_window_policy.rb @@ -0,0 +1,20 @@ +class PurchaseWindowPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def create? + !archived? && organisation_match? && user.has_permission?('purchase_windows.create') + end + + def update? + !archived? && organisation_match? && user.has_permission?('purchase_windows.update') + end + + def destroy? + !archived? && organisation_match? && user.has_permission?('purchase_windows.destroy') + end + +end
\ No newline at end of file diff --git a/app/policies/referential_policy.rb b/app/policies/referential_policy.rb index bc8a3e24b..253917509 100644 --- a/app/policies/referential_policy.rb +++ b/app/policies/referential_policy.rb @@ -22,7 +22,7 @@ class ReferentialPolicy < ApplicationPolicy end def validate? - !archived? && create? + !archived? && create? && organisation_match? end def archive? diff --git a/app/policies/stop_area_policy.rb b/app/policies/stop_area_policy.rb index faeebbc2a..6db48b702 100644 --- a/app/policies/stop_area_policy.rb +++ b/app/policies/stop_area_policy.rb @@ -1,5 +1,13 @@ class StopAreaPolicy < ApplicationPolicy class Scope < Scope + def search_scope scope_name + scope = resolve + if scope_name&.to_s == "route_editor" + scope = scope.where(area_type: 'zdep') unless user.organisation.has_feature?("route_stop_areas_all_types") + end + scope + end + def resolve scope end @@ -16,4 +24,12 @@ class StopAreaPolicy < ApplicationPolicy def update? user.has_permission?('stop_areas.update') end + + def deactivate? + !record.deactivated? && user.has_permission?('stop_areas.change_status') + end + + def activate? + record.deactivated? && user.has_permission?('stop_areas.change_status') + end end diff --git a/app/policies/stop_area_referential_policy.rb b/app/policies/stop_area_referential_policy.rb new file mode 100644 index 000000000..e370babf8 --- /dev/null +++ b/app/policies/stop_area_referential_policy.rb @@ -0,0 +1,14 @@ +class StopAreaReferentialPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def synchronize?; instance_permission("synchronize") end + + private + def instance_permission permission + user.has_permission?("stop_area_referentials.#{permission}") + end +end diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb index 7a4bdad1b..7166e6448 100644 --- a/app/services/zip_service.rb +++ b/app/services/zip_service.rb @@ -1,12 +1,16 @@ class ZipService - class Subdir < Struct.new(:name, :stream, :spurious) + class Subdir < Struct.new(:name, :stream, :spurious, :foreign_lines) + def ok? + foreign_lines.empty? && spurious.empty? + end end - attr_reader :current_key, :current_output, :current_spurious, :yielder + attr_reader :allowed_lines, :current_key, :foreign_lines, :current_output, :current_spurious, :yielder - def initialize data + def initialize data, allowed_lines @zip_data = StringIO.new(data) + @allowed_lines = allowed_lines @current_key = nil @current_output = nil end @@ -35,7 +39,8 @@ class ZipService end def add_to_current_output entry - return if is_spurious! entry.name + return if is_spurious!(entry.name) || is_foreign_line!(entry.name) + current_output.put_next_entry entry.name write_to_current_output entry.get_input_stream end @@ -52,20 +57,22 @@ class ZipService current_key, # Second part of the solution, yield the closed stream current_output.close_buffer, - current_spurious) + current_spurious.to_a, + foreign_lines) end end def open_new_output entry_key @current_key = entry_key # First piece of the solution, use internal way to create a Zip::OutputStream - @current_output = Zip::OutputStream.new(StringIO.new(''), true, nil) - @current_spurious = [] + @current_output = Zip::OutputStream.new(StringIO.new(''), true, nil) + @current_spurious = Set.new + @foreign_lines = [] end def entry_key entry # last dir name File.dirname.split("/").last - entry.name.split('/', -1)[-2] + entry.name.split('/').first end def is_spurious! entry_name @@ -75,4 +82,12 @@ class ZipService current_spurious << segments.second return true end + + def is_foreign_line! entry_name + STIF::NetexFile::Frame.get_short_id(entry_name).tap do | line_object_id | + return nil unless line_object_id + return nil if line_object_id.in? allowed_lines + foreign_lines << line_object_id + end + end end diff --git a/app/uploaders/import_uploader.rb b/app/uploaders/import_uploader.rb index 2740393ca..60e17ca0f 100644 --- a/app/uploaders/import_uploader.rb +++ b/app/uploaders/import_uploader.rb @@ -36,9 +36,9 @@ class ImportUploader < CarrierWave::Uploader::Base # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: - # def extension_whitelist - # %w(jpg jpeg gif png) - # end + def extension_whitelist + %w(zip) + end # Override the filename of the uploaded files: # Avoid using model.id or version_name here, see uploader/store.rb for details. diff --git a/app/views/autocomplete_purchase_windows/index.rabl b/app/views/autocomplete_purchase_windows/index.rabl new file mode 100644 index 000000000..1d0287602 --- /dev/null +++ b/app/views/autocomplete_purchase_windows/index.rabl @@ -0,0 +1,12 @@ +collection @purchase_windows, :object_root => false + +node do |window| + { + :id => window.id, + :name => window.name, + :objectid => window.objectid, + :color => window.color, + :short_id => window.get_objectid.short_id, + :text => "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>" + } +end diff --git a/app/views/autocomplete_stop_areas/index.rabl b/app/views/autocomplete_stop_areas/index.rabl index 5a9f76a47..d20051ad5 100644 --- a/app/views/autocomplete_stop_areas/index.rabl +++ b/app/views/autocomplete_stop_areas/index.rabl @@ -14,7 +14,8 @@ node do |stop_area| :longitude => stop_area.longitude, :latitude => stop_area.latitude, :area_type => stop_area.area_type, - :comment => stop_area.comment + :comment => stop_area.comment, + :text => stop_area.full_name } end diff --git a/app/views/companies/index.html.slim b/app/views/companies/index.html.slim index 5d746642f..e031f3776 100644 --- a/app/views/companies/index.html.slim +++ b/app/views/companies/index.html.slim @@ -34,7 +34,7 @@ end \ ) \ ], - links: [:show, :edit], + links: [:show], cls: 'table has-search' = new_pagination @companies, 'pull-right' diff --git a/app/views/compliance_check_sets/index.html.slim b/app/views/compliance_check_sets/index.html.slim index f5d1bd777..f109845b4 100644 --- a/app/views/compliance_check_sets/index.html.slim +++ b/app/views/compliance_check_sets/index.html.slim @@ -12,7 +12,7 @@ [ \ TableBuilderHelper::Column.new( \ key: :ref, \ - attribute: 'compliance_check_set_id' \ + attribute: 'id' \ ), \ TableBuilderHelper::Column.new( \ key: :creation_date, \ @@ -41,5 +41,3 @@ .row.mt-xs .col-lg-12 = replacement_msg t('compliance_check_sets.search_no_results') - - diff --git a/app/views/compliance_check_sets/show.html.slim b/app/views/compliance_check_sets/show.html.slim index c9d0583a5..5d8e3fa15 100644 --- a/app/views/compliance_check_sets/show.html.slim +++ b/app/views/compliance_check_sets/show.html.slim @@ -1,11 +1,6 @@ - breadcrumb :compliance_check_sets, @workbench, @compliance_check_set / PageHeader -= pageheader 'jeux-de-donnees', - @compliance_check_set.name, - '', - t('last_update', time: l(@compliance_check_set.updated_at, format: :short)) do - - / Below is secundary actions & optional contents (filters, ...) +- content_for :page_header_content do .row .col-lg-12.text-right.mb-sm - @compliance_check_set.action_links.each do |link| @@ -13,7 +8,23 @@ method: link.method, data: link.data, class: 'btn btn-primary' do - = link.content + = link.content + +- page_header_content_for @compliance_check_set +/ = pageheader 'jeux-de-donnees', +/ @compliance_check_set.name, +/ '', +/ t('last_update', time: l(@compliance_check_set.updated_at, format: :short)) do + + / Below is secundary actions & optional contents (filters, ...) + / .row + / .col-lg-12.text-right.mb-sm + / - @compliance_check_set.action_links.each do |link| + / = link_to link.href, + / method: link.method, + / data: link.data, + / class: 'btn btn-primary' do + / = link.content / PageContent .page_content.import_messages diff --git a/app/views/compliance_control_sets/index.html.slim b/app/views/compliance_control_sets/index.html.slim index 2a5651280..28d2254bf 100644 --- a/app/views/compliance_control_sets/index.html.slim +++ b/app/views/compliance_control_sets/index.html.slim @@ -30,7 +30,7 @@ ), \ TableBuilderHelper::Column.new( \ key: :control_numbers, \ - attribute: 'control_numbers' \ + attribute: Proc.new {|n| n.compliance_controls.count }\ ), \ TableBuilderHelper::Column.new( \ key: :updated_at, \ diff --git a/app/views/compliance_control_sets/show.html.slim b/app/views/compliance_control_sets/show.html.slim index e6416fda4..d915bbdaf 100644 --- a/app/views/compliance_control_sets/show.html.slim +++ b/app/views/compliance_control_sets/show.html.slim @@ -26,82 +26,23 @@ .col-lg-12 = render '/compliance_controls/filters' + / compliance controls without block + = render_compliance_control_block + = render_compliance_controls(@direct_compliance_controls) - - if @direct_compliance_controls.try(:any?) - .row - .col-lg-12 - h2 - = transport_mode_text() - .row - .col-lg-12 - .select_table - = table_builder_2 @direct_compliance_controls, - [ \ - TableBuilderHelper::Column.new( \ - key: :code, \ - attribute: 'code' \ - ), \ - TableBuilderHelper::Column.new( \ - key: :name, \ - attribute: 'name', \ - link_to: lambda do |compliance_control| \ - compliance_control_set_compliance_control_path(@compliance_control_set, compliance_control) \ - end \ - ), \ - TableBuilderHelper::Column.new( \ - key: :criticity, \ - attribute: 'criticity' \ - ), \ - TableBuilderHelper::Column.new( \ - key: :comment, \ - attribute: 'comment' \ - ), \ - ], - sortable: true, - cls: 'table has-filter has-search', - model: ComplianceControl - + / compliance controls with block + - if params[:q] && params[:q][:compliance_control_block_id_eq_any].try(:present?) - @blocks_to_compliance_controls_map.each do |block, compliance_controls| + = render_compliance_control_block(block) + = render_compliance_controls(compliance_controls) + - else + - @compliance_control_set.compliance_control_blocks.each do |block| + = render_compliance_control_block(block) + = render_compliance_controls(@blocks_to_compliance_controls_map[block]) - - if compliance_controls.try(:any?) - .row - .col-lg-12 - h2 - = transport_mode_text(block) - .btn-group - .btn.dropdown-toggle{ data-toggle="dropdown" } - .span.fa.fa-cog - ul.dropdown-menu - li - = link_to t('compliance_control_sets.actions.edit'), edit_compliance_control_set_compliance_control_block_path(@compliance_control_set.id, block.id) - = link_to t('compliance_control_sets.actions.destroy'), compliance_control_set_compliance_control_block_path(@compliance_control_set.id, block.id), :method => :delete, :data => {:confirm => t('compliance_control_sets.actions.destroy_confirm')} - .row - .col-lg-12 - .select_table - = table_builder_2 compliance_controls, - [ \ - TableBuilderHelper::Column.new( \ - key: :code, \ - attribute: 'code' \ - ), \ - TableBuilderHelper::Column.new( \ - key: :name, \ - attribute: 'name', \ - link_to: lambda do |compliance_control| \ - compliance_control_set_compliance_control_path(@compliance_control_set, compliance_control) \ - end \ - ), \ - TableBuilderHelper::Column.new( \ - key: :criticity, \ - attribute: 'criticity' \ - ), \ - TableBuilderHelper::Column.new( \ - key: :comment, \ - attribute: 'comment' \ - ), \ - ], - sortable: true, - cls: 'table has-filter has-search', - model: ComplianceControl + - if params[:q].present? && !@blocks_to_compliance_controls_map.try(:any?) && @direct_compliance_controls.nil? + .row.mt-xs + .col-lg-12 + = replacement_msg t('compliance_controls.search_no_results') - = flotted_links @compliance_control_set.id + = floating_links @compliance_control_set.id diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index f03301e23..075b94ddc 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -5,7 +5,7 @@ .panel-heading h3.panel-title.with_actions div - = workbench.name + = link_to workbench.name, workbench_path(workbench) span.badge.ml-xs = workbench.referentials.count if workbench.referentials.present? div @@ -22,7 +22,7 @@ .panel.panel-default .panel-heading h3.panel-title.with_actions - = "Modèles de calendrier" + = link_to I18n.t("activerecord.models.calendar", count: @dashboard.current_organisation.calendars.size), calendars_path div = link_to '', calendars_path, class: ' fa fa-chevron-right pull-right' - if @dashboard.current_organisation.calendars.present? @@ -31,21 +31,24 @@ = link_to calendar.name, calendar_path(calendar), class: 'list-group-item' - else .panel-body - em.small.text-muted Aucun modèle de calendrier défini + em.small.text-muted + = t('dasboard.calendars.none') .col-lg-6.col-md-6.col-sm-6.col-xs-12 .panel.panel-default - .panel-heading - h3.panel-title - = "Référentiels d'arrêts" - .list-group - - @dashboard.current_organisation.stop_area_referentials.each do |referential| - = link_to referential.name, stop_area_referential_stop_areas_path(referential), class: 'list-group-item' + - @dashboard.current_organisation.stop_area_referentials.each do |referential| + .panel-heading + h3.panel-title + = referential.name + .list-group + = link_to Chouette::StopArea.model_name.human.pluralize.capitalize, stop_area_referential_stop_areas_path(referential), class: 'list-group-item' .panel.panel-default - .panel-heading - h3.panel-title - = "Référentiels de lignes" - .list-group - - @dashboard.current_organisation.line_referentials.all.each do |referential| - = link_to referential.name, line_referential_lines_path(referential), class: 'list-group-item' + - @dashboard.current_organisation.line_referentials.all.each do |referential| + .panel-heading + h3.panel-title + = referential.name + .list-group + = link_to Chouette::Line.model_name.human.pluralize.capitalize, line_referential_lines_path(referential), class: 'list-group-item' + = link_to Chouette::Company.model_name.human.pluralize.capitalize, line_referential_companies_path(referential), class: 'list-group-item' + = link_to "Réseaux", line_referential_networks_path(referential), class: 'list-group-item' diff --git a/app/views/devise/invitations/edit.html.slim b/app/views/devise/invitations/edit.html.slim index 6c9a6f436..7a22146c0 100644 --- a/app/views/devise/invitations/edit.html.slim +++ b/app/views/devise/invitations/edit.html.slim @@ -1,14 +1,19 @@ -.col-md-offset-2.col-md-8 - .panel.panel-default - .panel-heading = t('devise.invitations.edit.header') - - .panel-body - = simple_form_for resource, as: resource_name, :url => invitation_path(resource_name), :html => { :method => :put, class: "form-horizontal" } do |form| - = form.hidden_field :invitation_token - - = form.input :name - = form.input :password, as: :password - = form.input :password_confirmation, as: :password - - .submit - = form.button :submit, value: t('devise.invitations.edit.submit_button'), class: 'btn-info'
\ No newline at end of file +/ PageHeader + +- content_for :page_header_title, t('.title') + +/ PageContent +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = simple_form_for resource, as: resource_name, :url => invitation_path(resource_name), :html => { :method => :put, class: "form-horizontal", id: 'invitation_form' } do |form| + .row + .col-lg-12 + = form.hidden_field :invitation_token + + = form.input :name + = form.input :password, as: :password + = form.input :password_confirmation, as: :password + + = form.button :submit, value: t('devise.invitations.edit.submit_button'), class: 'btn-info btn-default formSubmitr', form: 'invitation_form' diff --git a/app/views/devise/mailer/invitation_instructions.fr.html.slim b/app/views/devise/mailer/invitation_instructions.fr.html.slim index 484e0d68d..e263e188a 100644 --- a/app/views/devise/mailer/invitation_instructions.fr.html.slim +++ b/app/views/devise/mailer/invitation_instructions.fr.html.slim @@ -1,7 +1,7 @@ -p = t("devise.mailer.invitation_instructions.hello", email: @resource.email) +p = t("devise.mailer.invitation_instructions.hello", email: @resource.name) p = t("devise.mailer.invitation_instructions.someone_invited_you", url: unauthenticated_root_url) p = link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) -p = t("devise.mailer.invitation_instructions.ignore").html_safe
\ No newline at end of file +p = t("devise.mailer.invitation_instructions.ignore").html_safe diff --git a/app/views/devise/passwords/edit.html.slim b/app/views/devise/passwords/edit.html.slim index 864a44499..0d18f657c 100644 --- a/app/views/devise/passwords/edit.html.slim +++ b/app/views/devise/passwords/edit.html.slim @@ -1,13 +1,17 @@ -.col-md-offset-2.col-md-8 - .panel.panel-default - .panel-heading = t('.title') - .panel-body - = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put, class: "form-horizontal" }) do |f| +/ PageHeader - = f.input :reset_password_token, as: :hidden - = f.input :password, as: :password - = f.input :password_confirmation, as: :password - - .form-actions - = link_to t("cancel"), unauthenticated_root_path, class: 'btn btn-default' - = f.button :submit, :value => t("devise.passwords.edit.commit"), class: 'btn-info'
\ No newline at end of file +- content_for :page_header_title, t('.title') + +/ PageContent +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put, class: "form-horizontal", id: 'password_form' }) do |f| + .row + .col-lg-12 + = f.input :reset_password_token, as: :hidden + = f.input :password, as: :password + = f.input :password_confirmation, as: :password + + = f.button :submit, :value => t("devise.passwords.edit.commit"), class: 'btn btn-default formSubmitr', form: 'password_form' diff --git a/app/views/devise/passwords/new.html.slim b/app/views/devise/passwords/new.html.slim index 8a739ecc1..303f78f0e 100644 --- a/app/views/devise/passwords/new.html.slim +++ b/app/views/devise/passwords/new.html.slim @@ -1,11 +1,15 @@ -.col-md-offset-2.col-md-8 - .panel.panel-default - .panel-heading = t('.title') - - .panel-body - = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {class: 'form-horizontal' } ) do |form| - = form.input :email, :as => :email, placeholder: 'user@domain.com' +/ PageHeader - .form-actions - = link_to t("cancel"), unauthenticated_root_path, class: 'btn btn-default' - = form.button :submit, :value => t("devise.passwords.new.commit"), class: 'btn-info'
\ No newline at end of file +- content_for :page_header_title, t('.title') + +/ PageContent +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {class: 'form-horizontal', id: 'password_form' } ) do |form| + .row + .col-lg-12 + = form.input :email, :as => :email, placeholder: 'user@domain.com' + + = form.button :submit, :value => t("devise.passwords.new.commit"), class: 'btn btn-default formSubmitr', form: 'password_form' diff --git a/app/views/devise/sessions/new.html.slim b/app/views/devise/sessions/new.html.slim index 0ed17e24a..a812726e5 100644 --- a/app/views/devise/sessions/new.html.slim +++ b/app/views/devise/sessions/new.html.slim @@ -1,3 +1,5 @@ +- content_for :page_header_title, t('.title') + .page_content#devise .container-fluid #sessions_new.row diff --git a/app/views/import_resources/index.html.slim b/app/views/import_resources/index.html.slim index 1c4a5a765..728d9f4a8 100644 --- a/app/views/import_resources/index.html.slim +++ b/app/views/import_resources/index.html.slim @@ -1,4 +1,4 @@ - +- breadcrumb :import_resources, @import, @import_resources .page_content.import_messages .container-fluid .row diff --git a/app/views/imports/_import_messages.html.slim b/app/views/imports/_import_messages.html.slim new file mode 100644 index 000000000..a10dce065 --- /dev/null +++ b/app/views/imports/_import_messages.html.slim @@ -0,0 +1,11 @@ +- if import_messages.any? + .import_message-list + .import_messages-head Messages + dl + - import_messages.each do | import_message | + dt.criticity + = import_message.criticity + dd + = t( ['import_messages', + 'compliance_check_messages', + import_message.message_key].join('.'), import_message.message_attributes.symbolize_keys) diff --git a/app/views/imports/show.html.slim b/app/views/imports/show.html.slim index 5e22e03e0..cf137867b 100644 --- a/app/views/imports/show.html.slim +++ b/app/views/imports/show.html.slim @@ -19,46 +19,52 @@ .row .col-lg-12 - = table_builder_2 @import.children, - [ \ - TableBuilderHelper::Column.new( \ - name: 'Nom du jeu de données', \ - attribute: 'name', \ - sortable: false, \ - link_to: lambda do |import| \ - referential_path(import.referential) if import.referential.present? \ - end \ - ), \ - TableBuilderHelper::Column.new( \ - key: :status, \ - attribute: Proc.new { |n| import_status(n.status) }, \ - sortable: false, \ - link_to: lambda do |import| \ - workbench_import_import_resources_path(import.workbench_id, import) \ - end \ - ), \ - TableBuilderHelper::Column.new( \ - name: 'Contrôle STIF', \ - attribute: '', \ - sortable: false, \ - ), \ - TableBuilderHelper::Column.new( \ - name: 'Contrôle organisation', \ - attribute: '', \ - sortable: false, \ - ) \ - ], - links: [], - cls: 'table', - overhead: [ \ - {}, \ - { \ - title: "#{@import.children_succeedeed} jeu de données validé sur #{@import.children.count} présent(s) dans l'archive", \ - width: 1, \ - cls: "#{@import.import_status_css_class} full-border" \ - }, { \ - title: 'Bilan des jeux de contrôles d\'import <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>', \ - width: 2, \ - cls: 'overheaded-default colspan="2"' \ - } \ - ] + .error_messages + = render 'import_messages', import_messages: @import.messages + + - if @import.children.any? + .row + .col-lg-12 + = table_builder_2 @import.children, + [ \ + TableBuilderHelper::Column.new( \ + name: 'Nom du jeu de données', \ + attribute: 'name', \ + sortable: false, \ + link_to: lambda do |import| \ + referential_path(import.referential) if import.referential.present? \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| import_status(n.status) }, \ + sortable: false, \ + link_to: lambda do |import| \ + workbench_import_import_resources_path(import.workbench_id, import) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + name: 'Contrôle STIF', \ + attribute: '', \ + sortable: false, \ + ), \ + TableBuilderHelper::Column.new( \ + name: 'Contrôle organisation', \ + attribute: '', \ + sortable: false, \ + ) \ + ], + links: [], + cls: 'table', + overhead: [ \ + {}, \ + { \ + title: "#{@import.children_succeedeed} jeu de données validé sur #{@import.children.count} présent(s) dans l'archive", \ + width: 1, \ + cls: "#{@import.import_status_css_class} full-border" \ + }, { \ + title: 'Bilan des jeux de contrôles d\'import <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>', \ + width: 2, \ + cls: 'overheaded-default colspan="2"' \ + } \ + ] diff --git a/app/views/journey_patterns_collections/show.html.slim b/app/views/journey_patterns_collections/show.html.slim index 834501da3..8a7b0c47c 100644 --- a/app/views/journey_patterns_collections/show.html.slim +++ b/app/views/journey_patterns_collections/show.html.slim @@ -17,6 +17,7 @@ | window.stopPoints = #{(@stop_points_list.to_json).html_safe}; | window.journeyPatternLength = #{@journey_patterns.total_entries()}; | window.journeyPatternsPerPage = #{@ppage}; - | window.perms = #{raw @perms} + | window.perms = #{raw @perms}; + | window.features = #{raw @features}; = javascript_pack_tag 'journey_patterns/index.js' diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 000000000..977cbfe5b --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <style> + /* Email styles need to be inline */ + body { + font-family: "Open Sans", sans-serif; + font-size: 14px; + line-height: 1.4; + color: #4b4b4b; + width: 800px; + } + </style> + </head> + + <body> + <%= image_tag 'mail-header.png' %> + + <%= yield %> + </body> +</html> diff --git a/app/views/layouts/mailer.html.slim b/app/views/layouts/mailer.html.slim deleted file mode 100644 index 5db6917b3..000000000 --- a/app/views/layouts/mailer.html.slim +++ /dev/null @@ -1,7 +0,0 @@ -doctype html -html - head - meta charset="utf-8" - /!* Email styles need to be inline */ - body - = yield diff --git a/app/views/layouts/navigation/_main_nav_top.html.slim b/app/views/layouts/navigation/_main_nav_top.html.slim index d6c849d3f..278249e09 100644 --- a/app/views/layouts/navigation/_main_nav_top.html.slim +++ b/app/views/layouts/navigation/_main_nav_top.html.slim @@ -1,5 +1,5 @@ .nav-menu#menu_top - .brandname = t('brandname') + .brandname = link_to t('brandname'), root_path .menu-content .menu-item @@ -11,7 +11,7 @@ span.fa.fa-lg.fa-tasks = link_to '#', class: 'menu-item', data: { panel: 'toggle', target: '#profile_panel' }, title: 'Profil' do - span = current_user.username + span = current_user.name span.fa.fa-lg.fa-user diff --git a/app/views/line_referentials/show.html.slim b/app/views/line_referentials/show.html.slim index b4b32bc52..763eb076e 100644 --- a/app/views/line_referentials/show.html.slim +++ b/app/views/line_referentials/show.html.slim @@ -1,7 +1,8 @@ - breadcrumb :line_referential, @line_referential - page_header_content_for @line_referential -- content_for :page_header_actions do - = link_to(t('actions.sync'), sync_line_referential_path(@line_referential), method: :post, class: 'btn btn-default') +- if policy(@line_referential).synchronize? + - content_for :page_header_actions do + = link_to(t('actions.sync'), sync_line_referential_path(@line_referential), method: :post, class: 'btn btn-default') - content_for :page_header_content do .row.mb-md diff --git a/app/views/lines/_form.html.slim b/app/views/lines/_form.html.slim index 4952b72ff..909d6512e 100644 --- a/app/views/lines/_form.html.slim +++ b/app/views/lines/_form.html.slim @@ -3,7 +3,8 @@ .col-lg-12 = f.input :name = f.input :network_id, as: :select, :collection => @line_referential.networks, include_blank: false - = f.input :company_id, as: :select, :collection => @line_referential.companies, include_blank: false + = f.input :company_id, as: :select, :collection => @line_referential.companies, include_blank: true + = f.input :secondary_company_ids, :collection => @line_referential.companies, include_blank: false, input_html: { multiple: true, 'data-select2ed': true }, label: t('activerecord.attributes.line.secondary_company') = f.input :published_name = f.input :registration_number = f.input :number @@ -19,4 +20,3 @@ .separator = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'lines_form' - diff --git a/app/views/lines/index.html.slim b/app/views/lines/index.html.slim index 8b035b477..b62263263 100644 --- a/app/views/lines/index.html.slim +++ b/app/views/lines/index.html.slim @@ -1,6 +1,6 @@ - breadcrumb :lines, @line_referential - content_for :page_header_actions do - - if (policy(Chouette::Line).create? && @line_referential.organisations.include?(current_organisation)) + - if policy(Chouette::Line).create? = link_to(t('lines.actions.new'), new_line_referential_line_path(@line_referential), class: 'btn btn-primary') .page_content @@ -41,7 +41,7 @@ ), \ TableBuilderHelper::Column.new( \ key: 'companies.name', \ - attribute: Proc.new { |n| n.try(:company).try(:name) } \ + attribute: Proc.new { |n| n&.company&.name || "-" } \ ), \ TableBuilderHelper::Column.new( \ key: :transport_mode, \ diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim index 4969ca3cd..83244f739 100644 --- a/app/views/lines/show.html.slim +++ b/app/views/lines/show.html.slim @@ -6,7 +6,7 @@ = link_to link.href, method: link.method, data: link.data, - class: 'btn btn-primary' do + class: "btn btn-primary #{link.disabled ? "disabled" : ""}" do = link.content - page_header_content_for @line @@ -20,7 +20,7 @@ 'Activé' => (@line.deactivated? ? t('false') : t('true')), @line.human_attribute_name(:network_id) => (@line.network.nil? ? t('lines.index.unset') : @line.network.name), @line.human_attribute_name(:company_id) => (@line.company.nil? ? t('lines.index.unset') : @line.company.name), - 'Transporteur(s) secondaire(s)' => (@line.secondary_companies.nil? ? t('lines.index.unset') : @line.secondary_companies.collect(&:name).join(', ')), + 'Transporteur(s) secondaire(s)' => (@line.secondary_companies.nil? ? t('lines.index.unset') : array_to_html_list(@line.secondary_companies.collect(&:name))), 'Nom court' => @line.number, 'Code public' => (@line.registration_number ? @line.registration_number : '-'), @line.human_attribute_name(:transport_mode) => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), diff --git a/app/views/purchase_windows/_date_value_fields.html.slim b/app/views/purchase_windows/_date_value_fields.html.slim new file mode 100644 index 000000000..7bde06a94 --- /dev/null +++ b/app/views/purchase_windows/_date_value_fields.html.slim @@ -0,0 +1,13 @@ +.nested-fields + - if f.object.errors.has_key? :base + .row + .col-lg-12 + .alert.alert-danger + - f.object.errors[:base].each do |message| + p.small = message + + .wrapper + div + = f.input :value, as: :date, label: false, wrapper_html: { class: 'date smart_date' } + div + = link_to_remove_association '', f, class: 'fa fa-trash', data: { confirm: 'Etes-vous sûr(e) ?' }, title: t('actions.delete') diff --git a/app/views/purchase_windows/_filters.html.slim b/app/views/purchase_windows/_filters.html.slim new file mode 100644 index 000000000..4d7c8ce26 --- /dev/null +++ b/app/views/purchase_windows/_filters.html.slim @@ -0,0 +1,15 @@ += search_form_for @q, url: referential_purchase_windows_path, builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f| + .ffg-row + .input-group.search_bar + = f.search_field :name_cont, class: 'form-control', placeholder: t('purchase_windows.index.filter_placeholder') + span.input-group-btn + button.btn.btn-default#search_btn type='submit' + span.fa.fa-search + + .form-group + = f.label Chouette::PurchaseWindow.human_attribute_name(:date), class: 'control-label' + = f.input :contains_date, as: :date, label: false, wrapper_html: { class: 'date smart_date' }, class: 'form-control', default: @date, include_blank: @date ? false : true + + .actions + = link_to t('actions.erase'), referential_purchase_windows_path, class: 'btn btn-link' + = f.submit t('actions.filter'), id: 'purchase_window_filter_btn', class: 'btn btn-default' diff --git a/app/views/purchase_windows/_form.html.slim b/app/views/purchase_windows/_form.html.slim new file mode 100644 index 000000000..2101ae6db --- /dev/null +++ b/app/views/purchase_windows/_form.html.slim @@ -0,0 +1,29 @@ += simple_form_for [@referential, @purchase_window], html: { class: 'form-horizontal', id: 'purchase_window_form' }, wrapper: :horizontal_form do |f| + .row + .col-lg-12 + = f.input :name + = f.input :color, as: :color_select, collection: Chouette::PurchaseWindow.colors_i18n + + .separator + + .row + .col-lg-12 + .subform + .nested-head + .wrapper + div + .form-group + label.control-label + = t('simple_form.labels.purchase_window.ranges.begin') + div + .form-group + label.control-label + = t('simple_form.labels.purchase_window.ranges.end') + div + + = f.simple_fields_for :periods do |period| + = render 'period_fields', f: period + .links.nested-linker + = link_to_add_association t('simple_form.labels.purchase_window.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary' + + = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'purchase_window_form' diff --git a/app/views/purchase_windows/_period_fields.html.slim b/app/views/purchase_windows/_period_fields.html.slim new file mode 100644 index 000000000..95e204554 --- /dev/null +++ b/app/views/purchase_windows/_period_fields.html.slim @@ -0,0 +1,15 @@ +.nested-fields + - if f.object.errors.has_key? :base + .row + .col-lg-12 + .alert.alert-danger + - f.object.errors[:base].each do |message| + p.small = message + + .wrapper + div + = f.input :begin, as: :date, label: false, wrapper_html: { class: 'date smart_date' } + div + = f.input :end, as: :date, label: false, wrapper_html: { class: 'date smart_date' } + div + = link_to_remove_association '', f, class: 'fa fa-trash', data: { confirm: 'Etes-vous sûr(e) ?' }, title: t('actions.delete') diff --git a/app/views/purchase_windows/edit.html.slim b/app/views/purchase_windows/edit.html.slim new file mode 100644 index 000000000..6354db853 --- /dev/null +++ b/app/views/purchase_windows/edit.html.slim @@ -0,0 +1,7 @@ +- breadcrumb :purchase_window, @referential, @purchase_window +- page_header_content_for @purchase_window +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = render 'form' diff --git a/app/views/purchase_windows/index.html.slim b/app/views/purchase_windows/index.html.slim new file mode 100644 index 000000000..04f9fb0a8 --- /dev/null +++ b/app/views/purchase_windows/index.html.slim @@ -0,0 +1,45 @@ +- breadcrumb :purchase_windows, @referential +- content_for :page_header_actions do + - if policy(Chouette::PurchaseWindow).create? + = link_to(t('actions.add'), new_referential_purchase_window_path(@referential), class: 'btn btn-default') + +.page_content + .container-fluid + - if params[:q].present? or @purchase_windows.any? + .row + .col-lg-12 + = render 'filters' + + - if @purchase_windows.any? + .row + .col-lg-12 + = table_builder_2 @purchase_windows, + [ \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + link_to: lambda do |purchase_window| \ + referential_purchase_window_path(purchase_window.referential, purchase_window) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :color, \ + attribute: Proc.new { |tt| tt.color ? content_tag(:span, '', class: 'fa fa-circle', style: "color:#{tt.color}") : '-' }\ + ), \ + TableBuilderHelper::Column.new( \ + key: :bounding_dates, \ + attribute: Proc.new {|w| w.bounding_dates.nil? ? '-' : t('validity_range', debut: l(w.bounding_dates.begin, format: :short), end: l(w.bounding_dates.end, format: :short))}, \ + sortable: false \ + ) \ + ], + links: [:show], + cls: 'table has-filter' + + = new_pagination @purchase_windows, 'pull-right' + + - unless @purchase_windows.any? + .row.mt-xs + .col-lg-12 + = replacement_msg t('purchase_windows.search_no_results') + += javascript_pack_tag 'date_filters' diff --git a/app/views/purchase_windows/new.html.slim b/app/views/purchase_windows/new.html.slim new file mode 100644 index 000000000..402084167 --- /dev/null +++ b/app/views/purchase_windows/new.html.slim @@ -0,0 +1,6 @@ +- breadcrumb :purchase_windows, @referential +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = render 'form' diff --git a/app/views/purchase_windows/show.html.slim b/app/views/purchase_windows/show.html.slim new file mode 100644 index 000000000..9f3abb267 --- /dev/null +++ b/app/views/purchase_windows/show.html.slim @@ -0,0 +1,20 @@ +- breadcrumb :purchase_window, @referential, @purchase_window +- page_header_content_for @purchase_window +- content_for :page_header_content do + .row.mb-sm + .col-lg-12.text-right + - @purchase_window.action_links.each do |link| + = link_to link.href, + method: link.method, + data: link.data, + class: 'btn btn-primary' do + = link.content + +.page_content + .container-fluid + .row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + = definition_list t('metadatas'), + { Chouette::PurchaseWindow.human_attribute_name(:name) => @purchase_window.try(:name), + 'Organisation' => @purchase_window.referential.organisation.name, + Chouette::PurchaseWindow.human_attribute_name(:date_ranges) => @purchase_window.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe } diff --git a/app/views/referential_companies/index.html.slim b/app/views/referential_companies/index.html.slim index de0f7de69..07de2bc9d 100644 --- a/app/views/referential_companies/index.html.slim +++ b/app/views/referential_companies/index.html.slim @@ -46,7 +46,7 @@ attribute: 'url' \ ) \ ], - links: [:show, :edit], + links: [:show], cls: 'table has-search' = new_pagination @companies, 'pull-right' diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim new file mode 100644 index 000000000..963da8cea --- /dev/null +++ b/app/views/referential_vehicle_journeys/_filters.html.slim @@ -0,0 +1,11 @@ += search_form_for @q, url: referential_vehicle_journeys_path(@referential), html: {method: :get}, class: 'form form-filter' do |f| + .ffg-row + .input-group.search_bar + = f.search_field :published_journey_name_or_objectid_cont, placeholder: t('.published_journey_name_or_objectid'), class: 'form-control' + span.input-group-btn + button.btn.btn-default#search-btn type='submit' + span.fa.fa-search + + .actions + = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link' + = f.submit 'Filtrer', class: 'btn btn-default' diff --git a/app/views/referential_vehicle_journeys/index.html.slim b/app/views/referential_vehicle_journeys/index.html.slim new file mode 100644 index 000000000..394f4a3f7 --- /dev/null +++ b/app/views/referential_vehicle_journeys/index.html.slim @@ -0,0 +1,58 @@ +- breadcrumb :referential_vehicle_journeys, @referential +- content_for :page_header_title, t('.title') + +.page_content + .container-fluid + - if params[:q].present? or @vehicle_journeys.present? + .row + .col-lg-12 + = render 'filters' + + - if @vehicle_journeys.present? + .row + .col-lg-12 + .select_table + = table_builder_2 @vehicle_journeys, + [ \ + TableBuilderHelper::Column.new( \ + name: t('objectid'), \ + attribute: Proc.new { |n| n.get_objectid.short_id }, \ + sortable: false \ + ), \ + TableBuilderHelper::Column.new( \ + key: :published_journey_name, \ + attribute: 'published_journey_name', \ + link_to: lambda do |vehicle_journey| \ + referential_line_route_vehicle_journeys_path(@referential, vehicle_journey.route.line, vehicle_journey.route) \ + end, \ + sortable: false \ + ), \ + TableBuilderHelper::Column.new( \ + key: :line, \ + attribute: Proc.new {|v| v.route.line.name}, \ + sortable: false \ + ), \ + TableBuilderHelper::Column.new( \ + key: :route, \ + attribute: Proc.new {|v| v.route.name}, \ + sortable: false \ + ), \ + TableBuilderHelper::Column.new( \ + key: :departure_time, \ + attribute: Proc.new {|v| v.vehicle_journey_at_stops.first&.departure }, \ + sortable: false \ + ), \ + TableBuilderHelper::Column.new( \ + key: :arrival_time, \ + attribute: Proc.new {|v| v.vehicle_journey_at_stops.last&.arrival }, \ + sortable: false \ + ), \ + ], + cls: 'table has-filter has-search' + + = new_pagination @vehicle_journeys, 'pull-right' + + - unless @vehicle_journeys.any? + .row.mt-xs + .col-lg-12 + = replacement_msg t('.search_no_results') diff --git a/app/views/referentials/_filters.html.slim b/app/views/referentials/_filters.html.slim index c5b6042f0..93fa679df 100644 --- a/app/views/referentials/_filters.html.slim +++ b/app/views/referentials/_filters.html.slim @@ -12,11 +12,11 @@ = f.input :transport_mode_eq_any, collection: @referential.lines.pluck(:transport_mode).uniq.compact, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } .form-group.togglable - = f.label Chouette::Line.human_attribute_name(:network), required: false, class: 'control-label' + = f.label t('activerecord.attributes.referential.networks'), required: false, class: 'control-label' = f.input :network_id_eq_any, collection: LineReferential.first.networks.order('name').pluck(:id), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{LineReferential.first.networks.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } .form-group.togglable - = f.label Chouette::Line.human_attribute_name(:company), required: false, class: 'control-label' + = f.label t('activerecord.attributes.referential.companies'), required: false, class: 'control-label' = f.input :company_id_eq_any, collection: LineReferential.first.companies.order('name').pluck(:id), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{LineReferential.first.companies.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } .actions diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim index 6f7da84c7..1611ee6dd 100644 --- a/app/views/referentials/_form.html.slim +++ b/app/views/referentials/_form.html.slim @@ -17,7 +17,7 @@ .row .col-lg-12 - if @referential.errors.has_key? :metadatas - .row + .row.metadatas-errors .col-lg-12 .alert.alert-danger - @referential.errors[:metadatas].each do |msg| diff --git a/app/views/referentials/new.html.slim b/app/views/referentials/new.html.slim index 2bed9f912..13d58ee71 100644 --- a/app/views/referentials/new.html.slim +++ b/app/views/referentials/new.html.slim @@ -1,3 +1,5 @@ +- breadcrumb :referentials + .page_content .container-fluid .row diff --git a/app/views/referentials/show.html.slim b/app/views/referentials/show.html.slim index 9852fb0a3..96755359c 100644 --- a/app/views/referentials/show.html.slim +++ b/app/views/referentials/show.html.slim @@ -68,7 +68,7 @@ ), \ TableBuilderHelper::Column.new( \ key: 'companies.name', \ - attribute: Proc.new { |n| n.try(:company).try(:name) } \ + attribute: Proc.new { |n| n&.company&.name || "-" } \ ) \ ], links: [:show], diff --git a/app/views/routes/show.html.slim b/app/views/routes/show.html.slim index 3adf3e2f6..2b4ebf159 100644 --- a/app/views/routes/show.html.slim +++ b/app/views/routes/show.html.slim @@ -40,7 +40,7 @@ ), \ TableBuilderHelper::Column.new( \ key: :name, \ - attribute: Proc.new {|s| s.try(:stop_area).try(:name)}, \ + attribute: Proc.new { |s| content_tag :span, s.stop_area&.name, class: s.stop_area&.area_type }, \ link_to: lambda do |stop_point| \ referential_stop_area_path(@referential, stop_point.stop_area) \ end \ diff --git a/app/views/stop_area_referentials/show.html.slim b/app/views/stop_area_referentials/show.html.slim index d43333fd9..911006c39 100644 --- a/app/views/stop_area_referentials/show.html.slim +++ b/app/views/stop_area_referentials/show.html.slim @@ -1,13 +1,14 @@ - breadcrumb :stop_area_referential, @stop_area_referential -- content_for :page_header_actions do - = link_to(t('actions.sync'), sync_stop_area_referential_path(@stop_area_referential), method: :post, class: 'btn btn-default') +- if policy(@stop_area_referential).synchronize? + - content_for :page_header_actions do + = link_to(t('actions.sync'), sync_stop_area_referential_path(@stop_area_referential), method: :post, class: 'btn btn-default') - content_for :page_header_content do .row.mb-md .col-lg-12.text-right = link_to stop_area_referential_stop_areas_path(@stop_area_referential), class: 'btn btn-primary' do = Referential.human_attribute_name(:stop_areas) - em.small = " (#{@stop_area_referential.stop_areas.size})" + em.small = " (#{@stop_area_referential.stop_areas.count})" - page_header_content_for @stop_area_referential .page_content diff --git a/app/views/stop_areas/_filters.html.slim b/app/views/stop_areas/_filters.html.slim index 3b99f377c..90368dff4 100644 --- a/app/views/stop_areas/_filters.html.slim +++ b/app/views/stop_areas/_filters.html.slim @@ -12,7 +12,7 @@ .form-group.togglable = f.label Chouette::StopArea.human_attribute_name(:area_type), required: false, class: 'control-label' - = f.input :area_type_eq_any, collection: Chouette::StopArea.area_type.options.sort, as: :check_boxes, label: false, label_method: lambda{|w| ("<span>" + t("enumerize.stop_area.area_type.#{w[1]}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } + = f.input :area_type_eq_any, collection: Chouette::AreaType.options, as: :check_boxes, label: false, label_method: lambda{|w| ("<span>" + w[0] + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } .actions = link_to 'Effacer', @workbench, class: 'btn btn-link' diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim index 20c7c0468..af8b9d889 100644 --- a/app/views/stop_areas/_form.html.slim +++ b/app/views/stop_areas/_form.html.slim @@ -6,7 +6,10 @@ /= @map.to_html = f.input :id, as: :hidden = f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.name")} - = f.input :area_type, as: :select, :input_html => {:disabled => !@stop_area.new_record?}, :collection => Chouette::StopArea.area_type.options, :include_blank => false + + = f.input :parent_id, as: :select, :collection => [f.object.parent_id], input_html: { data: { select2_ajax: 'true', url: autocomplete_stop_area_referential_stop_areas_path(@stop_area_referential), initvalue: {id: f.object.parent_id, text: f.object.parent.try(:full_name)}}} + + = f.input :area_type, as: :select, :input_html => {:disabled => !@stop_area.new_record?}, :collection => Chouette::AreaType.options, :include_blank => false .location_info h3 = t("stop_areas.stop_area.localisation") @@ -19,13 +22,16 @@ = f.input :coordinates, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.coordinates")}, required: true = f.input :street_name - /= f.input :country_code, required: format_restriction_for_locales(@referential) == '.hub' = f.input :zip_code, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.zip_code")} = f.input :city_name, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.city_name")} + = f.input :country_code, as: :country, priority: ['FR', 'GB', 'DE', 'ES'], :include_blank => true .stop_areas.stop_area.general_info h3 = t("stop_areas.stop_area.general") + - if has_feature?(:stop_area_waiting_time) + = f.input :waiting_time + = f.input :registration_number, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number")} = f.input :fare_code = f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")} diff --git a/app/views/stop_areas/autocomplete.rabl b/app/views/stop_areas/autocomplete.rabl new file mode 100644 index 000000000..3208289b5 --- /dev/null +++ b/app/views/stop_areas/autocomplete.rabl @@ -0,0 +1,24 @@ +collection @stop_areas + +node do |stop_area| + { + :id => stop_area.id, + :registration_number => stop_area.registration_number || "", + :short_registration_number => truncate(stop_area.registration_number, :length => 10) || "", + :name => stop_area.name || "", + :short_name => truncate(stop_area.name, :length => 30) || "", + :zip_code => stop_area.zip_code || "", + :city_name => stop_area.city_name || "", + :short_city_name => truncate(stop_area.city_name, :length => 15) || "", + :user_objectid => stop_area.user_objectid, + :longitude => stop_area.longitude, + :latitude => stop_area.latitude, + :area_type => stop_area.area_type, + :comment => stop_area.comment, + :text => stop_area.full_name + } +end + +node(:stop_area_path) { |stop_area| + stop_area_picture_url(stop_area) || "" +} diff --git a/app/views/stop_areas/index.html.slim b/app/views/stop_areas/index.html.slim index c4d880081..63e99fd75 100644 --- a/app/views/stop_areas/index.html.slim +++ b/app/views/stop_areas/index.html.slim @@ -24,7 +24,7 @@ key: :name, \ attribute: 'name', \ link_to: lambda do |stop_area| \ - referential_stop_area_path( \ + stop_area_referential_stop_area_path( \ @stop_area_referential, \ stop_area \ ) \ @@ -48,10 +48,10 @@ ), \ TableBuilderHelper::Column.new( \ key: :area_type, \ - attribute: Proc.new { |s| (s.area_type.nil? ? '-' : t("enumerize.stop_area.area_type.#{s.try(:area_type)}")) } \ + attribute: Proc.new { |s| Chouette::AreaType.find(s.area_type).try :label } \ ), \ ], - links: [:show, :edit, :delete], + links: [:show], cls: 'table has-filter has-search' = new_pagination @stop_areas, 'pull-right' diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim index af673bb25..f9de34a98 100644 --- a/app/views/stop_areas/show.html.slim +++ b/app/views/stop_areas/show.html.slim @@ -15,12 +15,17 @@ .container-fluid .row .col-lg-6.col-md-6.col-sm-12.col-xs-12 - = definition_list t('metadatas'), - { t('id_reflex') => @stop_area.get_objectid.short_id, - @stop_area.human_attribute_name(:stop_area_type) => t("area_types.label.#{@stop_area.stop_area_type}"), + - attributes = { t('id_reflex') => @stop_area.get_objectid.short_id, + @stop_area.human_attribute_name(:parent) => @stop_area.parent ? link_to(@stop_area.parent.name, stop_area_referential_stop_area_path(@stop_area_referential, @stop_area.parent)) : "-", + @stop_area.human_attribute_name(:stop_area_type) => Chouette::AreaType.find(@stop_area.area_type).try(:label), @stop_area.human_attribute_name(:registration_number) => @stop_area.registration_number, - 'Coordonnées' => geo_data(@stop_area, @stop_area_referential), + } + - attributes.merge!(@stop_area.human_attribute_name(:waiting_time) => @stop_area.waiting_time_text) if has_feature?(:stop_area_waiting_time) + - attributes.merge!({ "Coordonnées" => geo_data(@stop_area, @stop_area_referential), @stop_area.human_attribute_name(:zip_code) => @stop_area.zip_code, @stop_area.human_attribute_name(:city_name) => @stop_area.city_name, + @stop_area.human_attribute_name(:country_code) => @stop_area.country_code.presence || '-', 'Etat' => (@stop_area.deleted_at ? 'Supprimé' : 'Actif'), - @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment) } + @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment), + }) + = definition_list t('metadatas'), attributes diff --git a/app/views/stop_points/_stop_point.html.slim b/app/views/stop_points/_stop_point.html.slim index ca86e339a..e54158cef 100644 --- a/app/views/stop_points/_stop_point.html.slim +++ b/app/views/stop_points/_stop_point.html.slim @@ -5,7 +5,7 @@ = link_to [@referential, stop_point.stop_area], class: "preview", title: "#{Chouette::StopArea.model_name.human.capitalize} #{stop_point.stop_area.name}" do span.name span.label.label-primary = stop_point.position + 1 - = image_tag "map/" + stop_point.stop_area.stop_area_type + ".png" + = image_tag "map/" + stop_point.stop_area.area_type + ".png" = truncate(stop_point.stop_area.name, length: 20) .panel-body @@ -27,4 +27,4 @@ = t(".no_object") - else - stop_point.stop_area.lines.each do |line| - span.label.label-default.line = line.number || truncate( line.name, length: 4 )
\ No newline at end of file + span.label.label-default.line = line.number || truncate( line.name, length: 4 ) diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim index 52c1a9728..ebcac8197 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -1,5 +1,11 @@ - breadcrumb :vehicle_journeys, @referential, @route - content_for :page_header_title, t('vehicle_journeys.index.title', route: @route.name) +- if @route.opposite_route.present? + - content_for :page_header_content do + .row.mb-sm + .col-lg-12.text-right + = link_to(t('routes.actions.opposite_route_timetable'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary sticky-action') + .page_content .container-fluid @@ -19,6 +25,7 @@ | window.vehicleJourneysPerPage = #{@ppage}; | window.line_footnotes = #{raw @footnotes}; | window.perms = #{raw @perms}; + | window.features = #{raw @features}; | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe}; = javascript_pack_tag 'vehicle_journeys/index.js' diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl index 830dee8bd..01175a85d 100644 --- a/app/views/vehicle_journeys/show.rabl +++ b/app/views/vehicle_journeys/show.rabl @@ -1,6 +1,6 @@ object @vehicle_journey -[:objectid, :published_journey_name, :published_journey_identifier, :company_id].each do |attr| +[:objectid, :published_journey_name, :published_journey_identifier, :company_id, :comment].each do |attr| attributes attr, :unless => lambda { |m| m.send( attr).nil?} end @@ -28,12 +28,22 @@ child(:time_tables, :object_root => false) do |time_tables| end end +if has_feature? :purchase_windows + child(:purchase_windows, :object_root => false) do |purchase_windows| + attributes :id, :objectid, :name, :color + end +end + child :footnotes, :object_root => false do |footnotes| attributes :id, :code, :label end child(:vehicle_journey_at_stops_matrix, :object_root => false) do |vehicle_stops| node do |vehicle_stop| + [:id, :connecting_service_id, :boarding_alighting_possibility].map do |att| + node(att) { vehicle_stop.send(att) ? vehicle_stop.send(att) : nil } + end + node(:dummy) { vehicle_stop.dummy } node(:stop_area_object_id) do @@ -49,11 +59,7 @@ child(:vehicle_journey_at_stops_matrix, :object_root => false) do |vehicle_stops vehicle_stop.stop_point.stop_area.city_name end - [:id, :connecting_service_id, :boarding_alighting_possibility].map do |att| - node(att) { vehicle_stop.send(att) ? vehicle_stop.send(att) : nil } - end - - [:arrival_time, :departure_time].map do |att| + [:arrival_time, :departure_time].each do |att| node(att) do |vs| { hour: vs.send(att).try(:strftime, '%H'), diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim index 22869b2d7..1c82c34b7 100644 --- a/app/views/workbenches/show.html.slim +++ b/app/views/workbenches/show.html.slim @@ -57,11 +57,11 @@ attribute: '' \ ) \ ], - selectable: true, + selectable: ->(ref){ @workbench.referentials.include?(ref) }, links: [:show, :edit], cls: 'table has-filter has-search' - = multiple_selection_toolbox([:delete]) + = multiple_selection_toolbox([:delete], collection_name: 'referentials') = new_pagination @wbench_refs, 'pull-right' diff --git a/app/workers/referential_cloning_worker.rb b/app/workers/referential_cloning_worker.rb index 6592160ec..e20148055 100644 --- a/app/workers/referential_cloning_worker.rb +++ b/app/workers/referential_cloning_worker.rb @@ -1,32 +1,7 @@ class ReferentialCloningWorker include Sidekiq::Worker - # Replace default apartment created schema with clone schema from source referential def perform(id) - ref_cloning = ReferentialCloning.find id - - source_schema = ref_cloning.source_referential.slug - target_schema = ref_cloning.target_referential.slug - - clone_schema ref_cloning, source_schema, target_schema - end - - private - - def clone_schema ref_cloning, source_schema, target_schema - ref_cloning.run! - - AF83::SchemaCloner - .new(source_schema, target_schema) - .clone_schema - - ref_cloning.successful! - rescue Exception => e - Rails.logger.error "ReferentialCloningWorker : #{e}" - ref_cloning.failed! - end - - def execute_sql sql - ActiveRecord::Base.connection.execute sql + ReferentialCloning.find(id).clone! end end diff --git a/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb index de51efded..6420be835 100644 --- a/app/workers/workbench_import_worker.rb +++ b/app/workers/workbench_import_worker.rb @@ -3,29 +3,25 @@ class WorkbenchImportWorker include Rails.application.routes.url_helpers include Configurable + include ObjectStateUpdater + + attr_reader :entries, :workbench_import + # Workers # ======= def perform(import_id) - @workbench_import = WorkbenchImport.find(import_id) - @response = nil - @workbench_import.update(status: 'running', started_at: Time.now) - downloaded = download - zip_service = ZipService.new(downloaded) + @entries = 0 + @workbench_import ||= WorkbenchImport.find(import_id) + + workbench_import.update(status: 'running', started_at: Time.now) + zip_service = ZipService.new(downloaded, allowed_lines) upload zip_service - @workbench_import.update(ended_at: Time.now) + workbench_import.update(ended_at: Time.now) rescue Zip::Error handle_corrupt_zip_file end - def download - logger.info "HTTP GET #{import_url}" - HTTPService.get_resource( - host: import_host, - path: import_path, - params: {token: @workbench_import.token_download}).body - end - def execute_post eg_name, eg_file logger.info "HTTP POST #{export_url} (for #{complete_entry_group_name(eg_name)})" HTTPService.post_resource( @@ -35,48 +31,43 @@ class WorkbenchImportWorker end def handle_corrupt_zip_file - @workbench_import.messages.create(criticity: :error, message_key: 'corrupt_zip_file', message_attributes: {source_filename: @workbench_import.file.file.file}) + workbench_import.messages.create(criticity: :error, message_key: 'corrupt_zip_file', message_attributes: {source_filename: workbench_import.file.file.file}) end def upload zip_service entry_group_streams = zip_service.subdirs - @workbench_import.update total_steps: entry_group_streams.size entry_group_streams.each_with_index(&method(:upload_entry_group)) + workbench_import.update total_steps: @entries rescue Exception => e logger.error e.message - @workbench_import.update( current_step: entry_group_streams.size, status: 'failed' ) + workbench_import.update( current_step: @entries, status: 'failed' ) raise end - def update_object_state entry, count - @workbench_import.update( current_step: count ) - unless entry.spurious.empty? - @workbench_import.messages.create( - criticity: :warning, - message_key: 'inconsistent_zip_file', - message_attributes: { - 'source_filename' => @workbench_import.file.file.file, - 'spurious_dirs' => entry.spurious.join(', ') - }) - end - end def upload_entry_group entry, element_count update_object_state entry, element_count.succ + return unless entry.ok? # status = retry_service.execute(&upload_entry_group_proc(entry)) - eg_name = entry.name - eg_stream = entry.stream + upload_entry_group_stream entry.name, entry.stream + end + def upload_entry_group_stream eg_name, eg_stream FileUtils.mkdir_p(Rails.root.join('tmp', 'imports')) - eg_file = File.new(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip"), 'wb').tap do |file| + File.open(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip"), 'wb') do |file| eg_stream.rewind file.write eg_stream.read end - eg_file.close - eg_file = File.new(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip")) + + upload_entry_group_tmpfile eg_name, File.new(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip")) + end + + def upload_entry_group_tmpfile eg_name, eg_file result = execute_post eg_name, eg_file if result && result.status < 400 + @entries += 1 + workbench_import.update( current_step: @entries ) result else raise StopIteration, result.body @@ -91,7 +82,7 @@ class WorkbenchImportWorker # ======= def complete_entry_group_name entry_group_name - [@workbench_import.name, entry_group_name].join("--") + [workbench_import.name, entry_group_name].join("--") end # Constants @@ -111,7 +102,7 @@ class WorkbenchImportWorker Rails.application.config.rails_host end def import_path - @__import_path__ ||= download_workbench_import_path(@workbench_import.workbench, @workbench_import) + @__import_path__ ||= download_workbench_import_path(workbench_import.workbench, workbench_import) end def import_url @__import_url__ ||= File.join(import_host, import_path) @@ -119,10 +110,29 @@ class WorkbenchImportWorker def params file, name { netex_import: - { parent_id: @workbench_import.id, - parent_type: @workbench_import.class.name, - workbench_id: @workbench_import.workbench_id, - name: name, - file: HTTPService.upload(file, 'application/zip', "#{name}.zip") } } + { parent_id: workbench_import.id, + parent_type: workbench_import.class.name, + workbench_id: workbench_import.workbench_id, + name: name, + file: HTTPService.upload(file, 'application/zip', "#{name}.zip") } } + end + + # Lazy Values + # =========== + + def allowed_lines + @__allowed_lines__ ||= workbench_import.workbench.organisation.lines_set end + def downloaded + @__downloaded__ ||= download_response.body + end + def download_response + @__download_response__ ||= HTTPService.get_resource( + host: import_host, + path: import_path, + params: {token: workbench_import.token_download}).tap do + logger.info "HTTP GET #{import_url}" + end + end + end diff --git a/app/workers/workbench_import_worker/object_state_updater.rb b/app/workers/workbench_import_worker/object_state_updater.rb new file mode 100644 index 000000000..67bdc0654 --- /dev/null +++ b/app/workers/workbench_import_worker/object_state_updater.rb @@ -0,0 +1,36 @@ + +class WorkbenchImportWorker + module ObjectStateUpdater + + def update_object_state entry, count + workbench_import.update( total_steps: count ) + update_spurious entry + update_foreign_lines entry + end + + + private + + def update_foreign_lines entry + return if entry.foreign_lines.empty? + workbench_import.messages.create( + criticity: :error, + message_key: 'foreign_lines_in_referential', + message_attributes: { + 'source_filename' => workbench_import.file.file.file, + 'foreign_lines' => entry.foreign_lines.join(', ') + }) + end + + def update_spurious entry + return if entry.spurious.empty? + workbench_import.messages.create( + criticity: :error, + message_key: 'inconsistent_zip_file', + message_attributes: { + 'source_filename' => workbench_import.file.file.file, + 'spurious_dirs' => entry.spurious.join(', ') + }) + end + end +end |
