From f961f15abf927732c6200e186656afeafe950cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gw=C3=A9na=C3=ABl=20Rault?= Date: Thu, 3 Apr 2025 18:40:45 +0200 Subject: [PATCH 1/2] Introduce Stats by tag --- CHANGELOG.md | 1 + app/assets/javascripts/plannings.js | 52 +++++++++++++++ app/assets/javascripts/scaffolds.js | 9 +-- app/assets/stylesheets/plannings.css.scss | 11 ++++ app/assets/stylesheets/scaffolds.css.scss | 78 +++++++++++++++++++++++ app/controllers/plannings_controller.rb | 62 +++++++++++++++++- app/helpers/plannings_helper.rb | 40 ++++++++++++ app/models/route.rb | 58 ++++++++++------- app/views/plannings/_edit.html.haml | 4 ++ app/views/plannings/_statistics.html.haml | 12 ++++ app/views/plannings/_statistics.js.erb | 2 + app/views/plannings/_tag_selector.haml | 9 +++ app/views/plannings/statistics.html.haml | 15 +++++ config/locales/en.yml | 21 ++++++ config/locales/fr.yml | 21 ++++++ config/routes.rb | 1 + 16 files changed, 368 insertions(+), 28 deletions(-) create mode 100644 app/views/plannings/_statistics.html.haml create mode 100644 app/views/plannings/_statistics.js.erb create mode 100644 app/views/plannings/_tag_selector.haml create mode 100644 app/views/plannings/statistics.html.haml diff --git a/CHANGELOG.md b/CHANGELOG.md index fe557d20d..ed38cd8cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Visits - Custom attributes are available in forms and displayed in plan stop pop-over [#338](https://github.com/cartoway/planner-web/pull/338) - Customers: Admin have access to the `cost_fixed` transmitted during route optimization [#374](https://github.com/cartoway/planner-web/pull/374) + - Tags: Introduce a new page to group route date by tags [#340](https://github.com/cartoway/planner-web/pull/340) - Vehicles: Introduce a maximum initial load associated to deliverable units [#373](https://github.com/cartoway/planner-web/pull/373) - Planning: - Routes: Allows to force the start time [#370](https://github.com/cartoway/planner-web/pull/370) diff --git a/app/assets/javascripts/plannings.js b/app/assets/javascripts/plannings.js index 94cac158d..7ed6cd5b5 100644 --- a/app/assets/javascripts/plannings.js +++ b/app/assets/javascripts/plannings.js @@ -2827,6 +2827,55 @@ var plannings_index = function(params) { }; +var planning_statistics = function(params) { + var previousSelection = []; + + $('#planning_tag_ids').select2({ + dropdownParent: $('#planning_tag_ids').parent(), + closeOnSelect: false, + allowClear: true, + theme: 'bootstrap', + placeholder: I18n.t('web.select2.search_placeholder'), + templateSelection: selectFormatOption, + templateResult: selectFormatOption, + }) + .off('select2:close select2:open select2:select select2:unselect') + .on('select2:open', function(e) { + previousSelection = $(this).val() || []; + }) + .on('select2:select', function(e) { + selectGlobalActions($(this), e); + }) + .on('select2:open', function(e) { + setTimeout(function() { + $('#route_selector .select2-results__option').each(function() { + var optionValue = $(this).attr('id').split('-').pop(); + if (['clear', 'reverse', 'all'].includes(optionValue)) { + $(this).addClass('global'); + } + }); + }, 0); + }) + .on('select2:select select2:unselect', function() { + updateSelectionCount('#tag_selector', '#planning_tag_ids', 'tag'); + }) + .on('select2:close', function(e) { + var selectedTags = $(this).val(); + + if (JSON.stringify(previousSelection)==JSON.stringify(selectedTags)) return; + + $.ajax({ + url: `/plannings/${params.planning_id}/statistics.js`, + data: { tag_ids: selectedTags.join(',') }, + beforeSend: beforeSendWaiting, + error: function(xhr, status, error) { + ajaxError(xhr, status, error); + }, + complete: completeAjaxMap + }); + }); +}; + Paloma.controller('Plannings', { index: function() { plannings_index(this.params); @@ -2845,5 +2894,8 @@ Paloma.controller('Plannings', { }, show: function() { plannings_show(this.params); + }, + statistics: function() { + planning_statistics(this.params); } }); diff --git a/app/assets/javascripts/scaffolds.js b/app/assets/javascripts/scaffolds.js index 9d0ec335a..82fc3cad9 100644 --- a/app/assets/javascripts/scaffolds.js +++ b/app/assets/javascripts/scaffolds.js @@ -740,7 +740,7 @@ export function continuousListLoading(listRef, linkRef, loadingRef, offset) { window.addEventListener('load', loadNextPage); }; -export function updateSelectionCount(containerRef, selectorRef) { +export function updateSelectionCount(containerRef, selectorRef, type = 'route') { var $select = $(selectorRef); var selectedValues = $select.val() || []; @@ -751,12 +751,13 @@ export function updateSelectionCount(containerRef, selectorRef) { var text = ''; if (selectedCount === 0) { - text = I18n.t('web.select2.route_none'); + text = I18n.t(`web.select2.${type}_none`); } else if (selectedCount === 1) { - text = "1 " + I18n.t('web.select2.route_selected'); + text = "1 " + I18n.t(`web.select2.${type}_selected`); } else { - text = selectedCount + " " + I18n.t('web.select2.routes_selected'); + text = selectedCount + " " + I18n.t(`web.select2.${type}s_selected`); } + $('.select2-selection__rendered', containerRef).html( '
  • ' + text + '
  • ' ); diff --git a/app/assets/stylesheets/plannings.css.scss b/app/assets/stylesheets/plannings.css.scss index 354552a84..1cb6f905c 100644 --- a/app/assets/stylesheets/plannings.css.scss +++ b/app/assets/stylesheets/plannings.css.scss @@ -367,6 +367,17 @@ $sidebar-margins: 10px; } #planning { + .tag-info { + white-space: nowrap; + margin: 2px; + padding: 7px 6px 2px 6px; + text-overflow: ellipsis; + overflow: hidden; + + .fa { + margin-right: 4px; + } + } .route-info { white-space: nowrap; margin: 2px; diff --git a/app/assets/stylesheets/scaffolds.css.scss b/app/assets/stylesheets/scaffolds.css.scss index 943b337d3..4ce6d81f7 100644 --- a/app/assets/stylesheets/scaffolds.css.scss +++ b/app/assets/stylesheets/scaffolds.css.scss @@ -1026,3 +1026,81 @@ input.form-control.number-of-days { width: fit-content !important; min-width: 0; } + +.route-info { + white-space: nowrap; + margin: 2px; + padding: 7px 6px 2px 6px; + text-overflow: ellipsis; + overflow: hidden; + height: 34px; + + .fa { + margin-right: 4px; + } +} + +.stop-info { + padding: 0 2px; + text-align: center; + height: 22px; + margin: 0 5px; +} + +.timewindow-info { + max-width: 99%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.stop-label { + margin: 0 !important; + line-height: unset !important; + border-radius: 0 !important; + font-weight: 600 !important; + font: 14px/1.5 Helvetica Neue,Arial,Helvetica,sans-serif; +} + +.info.route-info, .info.stop-info { + border: 1px solid $grey-color; + background-color: $lightgrey-color; +} + +.primary.route-info, .primary.stop-info { + border: 1px solid $primary-border-color; + background-color: $primary-background-color; +} + +.success.route-info, .success.stop-info { + border: 1px solid $success-border-color; + background-color: $success-background-color; +} + +.danger.route-info, .danger.stop-info { + border: 1px solid $danger-border-color !important; + background-color: $warning-color !important; + background: #fef1ec image-url("ui-bg_glass_95_fef1ec_1x400") 50% 50% repeat-x; + color: $danger-color !important; +} + +.inactive.route-info { + border: 1px solid lightgray; + background-color: #eee; + color: #555 +} + +.info.stop-info { + border: 1px solid lightgray; + background-color: $primary-background-color; +} + +.tag-info { + color: #555555; + background: #fff; + border: 1px solid $grey-color; + border-radius: 4px; + cursor: default; + float: left; + padding: 0 6px; +} diff --git a/app/controllers/plannings_controller.rb b/app/controllers/plannings_controller.rb index 972d2e267..ff4d45fe2 100644 --- a/app/controllers/plannings_controller.rb +++ b/app/controllers/plannings_controller.rb @@ -27,7 +27,7 @@ class PlanningsController < ApplicationController UPDATE_ACTIONS = [:update, :move, :switch, :automatic_insert, :update_stop, :active, :reverse_order, :apply_zonings, :optimize, :optimize_route] before_action :set_planning, only: [:edit, :duplicate, :destroy, :cancel_optimize, :refresh, :route_edit] + UPDATE_ACTIONS - before_action :set_planning_without_stops, only: [:data_header, :filter_routes, :modal, :sidebar, :refresh_route] + before_action :set_planning_without_stops, only: [:data_header, :filter_routes, :modal, :sidebar, :refresh_route, :statistics] before_action :set_driver_planning, only: [:driver_move] before_action :check_no_existing_job, only: [:refresh, :driver_move] + UPDATE_ACTIONS around_action :over_max_limit, only: [:create, :duplicate] @@ -171,6 +171,48 @@ def driver_move move_respond end + def statistics + stat_keys = [:size_active, :emission, :distance, :duration, :drive_time, :wait_time, :visits_duration, :revenue, :total_cost, :balance] + @cumulated_stats = {} + + tags = params['tag_ids'].present? ? current_user.customer.tags.where(id: params['tag_ids'].split(',')) : current_user.customer.tags + tags.each { |tag| + next if tag.nil? + + routes = @planning.routes + .joins(vehicle_usage: [:vehicle]) + .joins('LEFT JOIN tag_vehicles ON tag_vehicles.vehicle_id = vehicles.id') + .joins('LEFT JOIN tag_vehicle_usages ON tag_vehicle_usages.vehicle_usage_id = vehicle_usages.id') + .where('tag_vehicles.tag_id = ? OR tag_vehicle_usages.tag_id = ?', tag.id, tag.id) + .distinct + + @cumulated_stats[tag.id] = { label: tag.label, tag: tag, stats: stat_init_hash } + routes.each { |route| + stat_keys.each { |key| + value = route.send(key) + if value + @cumulated_stats[tag.id][:stats][key][:value] = 0 if @cumulated_stats[tag.id][:stats][key][:value].nil? + @cumulated_stats[tag.id][:stats][key][:value] += value + @cumulated_stats[tag.id][:stats][key][:min] = value if @cumulated_stats[tag.id][:stats][key][:min].nil? || value < @cumulated_stats[tag.id][:stats][key][:min] + @cumulated_stats[tag.id][:stats][key][:max] = value if @cumulated_stats[tag.id][:stats][key][:max].nil? || value > @cumulated_stats[tag.id][:stats][key][:max] + end + } + route.quantities.each { |du_id, value| + @cumulated_stats[tag.id][:stats][du_id][:value] = 0 if @cumulated_stats[tag.id][:stats][du_id][:value].nil? + @cumulated_stats[tag.id][:stats][du_id][:value] += value + @cumulated_stats[tag.id][:stats][du_id][:min] = value if @cumulated_stats[tag.id][:stats][du_id][:min].nil? || value < @cumulated_stats[tag.id][:stats][du_id][:min] + @cumulated_stats[tag.id][:stats][du_id][:max] = value if @cumulated_stats[tag.id][:stats][du_id][:max].nil? || value > @cumulated_stats[tag.id][:stats][du_id][:max] + } + } + @cumulated_stats[tag.id][:stats][:route_size][:value] = routes.length + } + + respond_to do |format| + format.html + format.js { render partial: 'statistics', locals: { cumulated_stats: @cumulated_stats, current_user: current_user } } + end + end + def refresh respond_to do |format| if @planning.compute_saved @@ -735,4 +777,22 @@ def format_csv(format) response.headers['Content-Disposition'] = 'attachment; filename="' + filename + '.csv"' end end + + def stat_init_hash + { + route_size: { label: t('activerecord.attributes.route.route_size'), help: t('plannings.edit.route_size_help'), icon: 'fa-truck-field', value: nil, min: nil, max: nil}, + size_active: { label: t('activerecord.attributes.route.size_active'), help: t('plannings.edit.size_active_help'), icon: 'fa-check-square', value: nil, min: nil, max: nil}, + emission: { label: t('activerecord.attributes.route.emission'), suffix: t('all.unit.kgco2e_html'), help: t('plannings.edit.emission_help'), icon: 'fa-flask', value: nil, min: nil, max: nil}, + distance: { label: t('activerecord.attributes.route.distance'), help: t('plannings.edit.route_distance_help'), icon: 'fa-road', value: nil, min: nil, max: nil}, + duration: { label: t('activerecord.attributes.route.duration'), help: t('plannings.edit.route_duration_help'), icon: 'fa-stopwatch', value: nil, min: nil, max: nil}, + drive_time: { label: t('activerecord.attributes.route.drive_time'), help: t('plannings.edit.route_duration_help'), icon: 'fa-stopwatch', value: nil, min: nil, max: nil}, + wait_time: { label: t('activerecord.attributes.route.wait_time'), help: t('plannings.edit.wait_time_help'), icon: 'fa-hourglass-half', value: nil, min: nil, max: nil}, + visits_duration: { label: t('activerecord.attributes.route.visits_duration'), help: t('plannings.edit.route_visits_duration_help'), icon: 'fa-hourglass-half', value: nil, min: nil, max: nil}, + revenue: { label: t('activerecord.attributes.route.revenue'), suffix: t("all.unit.currency_symbol.#{current_user.prefered_currency}"), help: t('plannings.edit.revenue_help'), icon: 'fa-hand-holding-dollar', value: nil, min: nil, max: nil}, + total_cost: { label: t('activerecord.attributes.route.total_cost'), suffix: t("all.unit.currency_symbol.#{current_user.prefered_currency}"), help: t('plannings.edit.cost_help'), icon: 'fa-coins', value: nil, min: nil, max: nil}, + balance: { label: t('activerecord.attributes.route.balance'), suffix: t("all.unit.currency_symbol.#{current_user.prefered_currency}"), help: t('plannings.edit.balance_help'), icon: 'fa-balance-scale', value: nil, min: nil, max: nil}, + }.merge(current_user.customer.deliverable_units.map{ |du| + [du.id, { label: du.label&.capitalize || du.id, suffix: du.label || du.id, icon: du.icon || 'fa-boxes', value: nil, min: nil, max: nil}] + }.to_h) + end end diff --git a/app/helpers/plannings_helper.rb b/app/helpers/plannings_helper.rb index fa5285ed7..d26eff818 100644 --- a/app/helpers/plannings_helper.rb +++ b/app/helpers/plannings_helper.rb @@ -162,4 +162,44 @@ def external_callback_converted_url(summary, current_user, route_hash = nil) '' end end + + def planning_stat_value(key, hash, current_user) + content_tag(:div) do + html = [] + + html << content_tag(:div, class: 'primary route-info') do + concat content_tag(:i, '', class: "fa fa-fw #{hash[:icon]}", help: hash[:help]) + concat ' ' + concat format_stat_value(key, hash, hash[:value], current_user) + end + + if hash[:min] && hash[:max] + html << content_tag(:div, class: 'info route-info') do + concat t('plannings.statistics.min') + concat ' ' + concat format_stat_value(key, hash, hash[:min], current_user) + end + + html << content_tag(:div, class: 'info route-info') do + concat t('plannings.statistics.max') + concat ' ' + concat format_stat_value(key, hash, hash[:max], current_user) + end + end + + safe_join(html) + end + end + + private + + def format_stat_value(key, hash, value, current_user) + if key == :distance + locale_distance(value, current_user.prefered_unit) + elsif key.is_a?(Symbol) && (key.end_with?('_time') || key.end_with?('duration')) + value && time_over_day(value) + else + "#{value&.round(2)} #{hash[:suffix]}".html_safe + end + end end diff --git a/app/models/route.rb b/app/models/route.rb index 5485e2647..3bf3e973b 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -120,35 +120,24 @@ def work_time_value vehicle_usage.default_work_time if vehicle_usage? && vehicle_usage.default_work_time end - def init_route_data - { - stop_distance: 0, - stop_no_path: false, - stop_out_of_drive_time: nil, - stop_out_of_work_time: nil, - out_of_max_ride_distance: nil, - out_of_max_ride_duration: nil, - emission: nil, - start: nil, - end: nil, - distance: 0, - drive_time: nil, - wait_time: nil, - visits_duration: nil, - quantities: nil, - revenue: nil, - cost_distance: nil, - cost_fixed: nil, - cost_time: nil - } - end - def is_expired? return false if planning.date.nil? || stops.only_active_stop_visits.empty? || stops.only_active_stop_visits.last.time.nil? planning.date + stops.only_active_stop_visits.last.time.seconds + 12.hour < DateTime.now end + def duration + visits_duration.to_i + wait_time.to_i + drive_time.to_i + (vehicle_usage ? vehicle_usage.default_service_time_start.to_i + vehicle_usage.default_service_time_end.to_i : 0) + end + + def balance + [revenue || 0, total_cost].compact.reduce(&:-) + end + + def total_cost + [cost_distance, cost_fixed, cost_time].compact.reduce(&:+) + end + def store_traces(geojson_tracks_store, trace, options = {}) if trace && !options[:no_geojson] geojson_tracks_store << { @@ -908,6 +897,29 @@ def assign_defaults self.locked = false end + def init_route_data + { + stop_distance: 0, + stop_no_path: false, + stop_out_of_drive_time: nil, + stop_out_of_work_time: nil, + out_of_max_ride_distance: nil, + out_of_max_ride_duration: nil, + emission: nil, + start: nil, + end: nil, + distance: 0, + drive_time: nil, + wait_time: nil, + visits_duration: nil, + quantities: nil, + revenue: nil, + cost_distance: nil, + cost_fixed: nil, + cost_time: nil + } + end + def in_optimization_context? planning&.in_optimization_context? end diff --git a/app/views/plannings/_edit.html.haml b/app/views/plannings/_edit.html.haml index ff3e06f82..d212714e7 100644 --- a/app/views/plannings/_edit.html.haml +++ b/app/views/plannings/_edit.html.haml @@ -165,6 +165,10 @@ %a.export_spreadsheet %i.fa.fa-table.fa-fw = t '.export.spreadsheet' + %li + = link_to planning_statistics_path(@planning) do + %i.fa.fa-chart-column.fa-fw + = t '.export.statistics' %li.divider{role: "separator"} %li = link_to api_planning_calendar_path(@planning, api_key: current_user.api_key) do diff --git a/app/views/plannings/_statistics.html.haml b/app/views/plannings/_statistics.html.haml new file mode 100644 index 000000000..cead9f9df --- /dev/null +++ b/app/views/plannings/_statistics.html.haml @@ -0,0 +1,12 @@ +- cumulated_stats&.each do |tag_id, data| + %div{class: 'tag-stats'} + .row + .col-xs-12 + %h3.tag-info + = tag_icon(data[:tag]) + = data[:label] + .row.route-data + - data[:stats].each do |key, hash| + .col-xs-4.col-md-3.col-lg-2 + %b= hash[:label] + = planning_stat_value(key, hash, current_user) diff --git a/app/views/plannings/_statistics.js.erb b/app/views/plannings/_statistics.js.erb new file mode 100644 index 000000000..c4a054c3f --- /dev/null +++ b/app/views/plannings/_statistics.js.erb @@ -0,0 +1,2 @@ +$('#statistics-container').html("<%= j(render partial: 'plannings/statistics.html.haml', locals: local_assigns) %>"); +var locals = <%= local_assigns.to_json.html_safe %>; diff --git a/app/views/plannings/_tag_selector.haml b/app/views/plannings/_tag_selector.haml new file mode 100644 index 000000000..fd0733955 --- /dev/null +++ b/app/views/plannings/_tag_selector.haml @@ -0,0 +1,9 @@ +.col-xs-12.multiple + %label + = t('plannings.statistics.select_tags') + = select_tag :planning_tag_ids, + options_from_collection_for_select(current_user.customer.tags, :id, :label), + { multiple: true, + class: 'form-control width_1_2 selectpicker', + 'data-live-search': true, + 'data-none-selected-text': t('plannings.statistics.select_tags_placeholder') } diff --git a/app/views/plannings/statistics.html.haml b/app/views/plannings/statistics.html.haml new file mode 100644 index 000000000..48dafc7ed --- /dev/null +++ b/app/views/plannings/statistics.html.haml @@ -0,0 +1,15 @@ +- javascript 'planning' +- content_for :title, t('.title') +.container + %h1 + = t '.title' + #route_selector.row + = render 'tag_selector' + + #statistics-container + = render partial: 'statistics', locals: { cumulated_stats: @cumulated_stats, current_user: current_user } + +:ruby + controller.js( + planning_id: @planning.id + ) diff --git a/config/locales/en.yml b/config/locales/en.yml index a5b08000a..1d39857b7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -347,8 +347,17 @@ en: customer_dashboard_url: Customer's Dashboard URL planning_dashboard_url: Planning's Dashboard URL route: + route_size: Routes + size_active: Stops distance: Distance emission: Emission + duration: Duration + drive_time: Drive time + wait_time: Wait time + visits_duration: Visits duration + revenue: Revenue + total_cost: Total cost + balance: Balance outdated: Outdated start: Start end: End @@ -1851,6 +1860,7 @@ en: success: CSV file successfully exported print: Print spreadsheet: Spreadsheet CSV + statistics: Statistics by Categories gpx_route: GPX Route gpx_track: GPX Track kmz_track: KMZ Track @@ -1873,6 +1883,7 @@ en: refresh: Refresh refresh_help: 'Plan parameters have been changed, it is required to recalculate' outdated_zoning: Sectorisation changed. Apply again to refresh. + size_active_help: Active stops stops_help: Plan stops / total duration_help: Total duration distance_help: Total distance @@ -1901,6 +1912,7 @@ en: route_distance_help: Route distance route_quantity_help: Quantity / Vehicle capacity route_quantity_loading_help: Quantity to load before the start of the route + route_size_help: Number of Routes route_speed_average_help: Vehicle's speed average route_driving_time_help: Driving time route_visits_duration_help: Visits duration @@ -2178,6 +2190,12 @@ en: default: Default show: print: Print + statistics: + title: Statistics + select_tags: Select the categories + select_tags_placeholder: Select the categories to display + min: 'min:' + max: 'max:' plannings_by_destinations: show: affect_destinations: Move visits @@ -2904,6 +2922,9 @@ en: route_selected: selected route routes_selected: selected routes search_placeholder: Search + tag_none: No selected category + tag_selected: selected category + tags_selected: selected categories placeholder: Write here dialog: close: Close diff --git a/config/locales/fr.yml b/config/locales/fr.yml index dbd39f4d6..db4c9153f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -347,7 +347,16 @@ fr: customer_dashboard_url: URL du tableau de bord du compte planning_dashboard_url: URL du tableau de bord de planification route: + route_size: Tournées + size_active: Arrêts distance: Distance + duration: Durée + drive_time: Temps de conduite + wait_time: Temps d'attente + visits_duration: Durée des visites + revenue: Recettes + total_cost: Coût total + balance: Solde emission: Émissions outdated: Obsolète start: Début @@ -1935,6 +1944,7 @@ fr: success: CSV exporté avec succès print: Imprimer spreadsheet: Tableur CSV + statistics: Statistiques par Compétences gpx_route: Route GPX gpx_track: Tracé GPX kmz_track: Tracé KMZ @@ -1959,6 +1969,7 @@ fr: Des paramètres ou des adresses utilisés par ce plan de tournées ont été modifiés, il est nécessaire de le recalculer outdated_zoning: La sectorisation a été modifiée. Appliquez à nouveau pour actualiser. + size_active_help: Arrêts planifiés stops_help: Arrêts planifiés / total duration_help: Somme des durées des tournées distance_help: Somme des distances des tournées @@ -1991,6 +2002,7 @@ fr: route_distance_help: Distance de la tournée route_quantity_help: Chargement / Capacité véhicule route_quantity_loading_help: À charger avant le début de la tournée + route_size_help: Nombre de Tournées route_speed_average_help: Vitesse moyenne du véhicule route_driving_time_help: Durée de conduite route_visits_duration_help: Durée des visites @@ -2278,6 +2290,12 @@ fr: default: Défaut show: print: Imprimer + statistics: + title: Statistiques + select_tags: "Sélectionner les Compétences" + select_tags_placeholder: "Choisir les Compétences à afficher" + min: 'min :' + max: 'max :' plannings_by_destinations: show: affect_destinations: Déplacer les visites @@ -3033,6 +3051,9 @@ fr: route_none: Aucune tournée sélectionnée route_selected: tournées sélectionnée routes_selected: tournées sélectionnées + tag_none: Aucune compétence sélectionnée + tag_selected: compétences sélectionnées + tags_selected: compétences sélectionnées search_placeholder: Rechercher placeholder: Écrire ici dialog: diff --git a/config/routes.rb b/config/routes.rb index 37da5bd51..e16ed6f5f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -133,6 +133,7 @@ get 'modal' patch 'switch' patch 'duplicate' + get 'statistics' patch ':route_id/active/:active' => 'plannings#active' patch ':route_id/reverse_order' => 'plannings#reverse_order' patch ':route_id/:stop_id' => 'plannings#update_stop' From 6d7ed0686a806ccd8b43ffd143b1ab1b2f29f122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gw=C3=A9na=C3=ABl=20Rault?= Date: Wed, 21 May 2025 09:37:05 +0200 Subject: [PATCH 2/2] TMP CI --- .github/workflows/rubyonrails.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rubyonrails.yml b/.github/workflows/rubyonrails.yml index bbb752620..27775873b 100644 --- a/.github/workflows/rubyonrails.yml +++ b/.github/workflows/rubyonrails.yml @@ -2,9 +2,9 @@ name: "Ruby on Rails CI" on: push: - branches: [master, dev] + branches: [master, dev, dev2] pull_request: - branches: [master, dev] + branches: [master, dev, dev2] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -78,7 +78,7 @@ jobs: docker: runs-on: ubuntu-latest needs: [test, lint] - if: github.ref == 'refs/heads/dev' + if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/dev2' permissions: contents: read