Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ETQ instructeur, je peux passer au dossiers suivant ou au dossier précédent lorsque je visualise un dossiers d'un des onglet (a-suivre, tous, ...) #10898

Merged
merged 7 commits into from
Dec 11, 2024
4 changes: 4 additions & 0 deletions app/assets/stylesheets/buttons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,7 @@ ul.dropdown-items {
content: none !important;
}
}

.back-btn {
line-height: 1.75rem;
}
4 changes: 3 additions & 1 deletion app/components/instructeurs/back_button_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ def initialize(to:)
end

def call
link_to "", @to, class: 'back-btn fr-btn fr-btn--secondary fr-btn--sm fr-mr-2w fr-icon-arrow-left-line', title: t('.back')
link_to @to, class: 'back-btn fr-btn fr-btn--secondary fr-btn--sm fr-mr-2w fr-px-1v', title: t('.back') do
dsfr_icon("fr-icon-arrow-left-line")
end
end
end
45 changes: 45 additions & 0 deletions app/components/instructeurs/dossiers_navigation_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

class Instructeurs::DossiersNavigationComponent < ApplicationComponent
attr_reader :dossier, :statut

def initialize(dossier:, procedure_presentation:, statut:)
@dossier = dossier
@cache = Cache::ProcedureDossierPagination.new(procedure_presentation: procedure_presentation, statut:)
@statut = statut
end

def back_url_options
options = { statut: }
options = options.merge(page: @cache.incoming_page) if @cache.incoming_page
options
end

def link_next
if has_next?
html_tag = :a
options = { class: "fr-link no-wrap fr-ml-3w", href: next_instructeur_dossier_path(dossier:, statut:) }

Check warning on line 21 in app/components/instructeurs/dossiers_navigation_component.rb

View check run for this annotation

Codecov / codecov/patch

app/components/instructeurs/dossiers_navigation_component.rb#L21

Added line #L21 was not covered by tests
else
html_tag = :span
options = { class: "fr-link no-wrap fr-ml-3w fr-text-mention--grey" }
end

tag.send(html_tag, t('.next').html_safe + tag.span(class: 'fr-icon-arrow-right-line fr-ml-1w'), **options)
end

def link_previous
if has_previous?
html_tag = :a
options = { class: "fr-link no-wrap", href: previous_instructeur_dossier_path(dossier:, statut:) }

Check warning on line 33 in app/components/instructeurs/dossiers_navigation_component.rb

View check run for this annotation

Codecov / codecov/patch

app/components/instructeurs/dossiers_navigation_component.rb#L33

Added line #L33 was not covered by tests
else
html_tag = :span
options = { class: "fr-link no-wrap fr-text-mention--grey" }
end

tag.send(html_tag, tag.span(class: 'fr-icon-arrow-left-line fr-mr-1w') + t('.previous'), **options)
end

def has_next? = @has_next ||= @cache.next_dossier_id(from_id: dossier.id).present?

def has_previous? = @has_previous ||= @cache.previous_dossier_id(from_id: dossier.id).present?
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
en:
next: Next file
previous: Previous file
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
fr:
next: Dossier suivant
previous: Dossier précédent
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.flex.fr-mb-1w.align-start
= render Instructeurs::BackButtonComponent.new(to: instructeur_procedure_path(dossier.procedure, **back_url_options))

%h1.fr-h3.fr-mb-0
= t('show_dossier', scope: [:layouts, :breadcrumb], dossier_id: dossier.id, owner_name: dossier.owner_name)

.fr.ml-auto.align-center.flex.fr-mt-1v
= link_previous
= link_next

= link_to dossier.procedure.libelle.truncate_words(10), instructeur_procedure_path(dossier.procedure), title: dossier.procedure.libelle, class: "fr-link"
11 changes: 11 additions & 0 deletions app/controllers/concerns/instructeur_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module InstructeurConcern
extend ActiveSupport::Concern

included do
def retrieve_procedure_presentation
@procedure_presentation ||= current_instructeur.procedure_presentation_for_procedure_id(params[:procedure_id])
end
end
end
2 changes: 2 additions & 0 deletions app/controllers/instructeurs/commentaires_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

module Instructeurs
class CommentairesController < ApplicationController
include InstructeurConcern
before_action :authenticate_instructeur_or_expert!
after_action :mark_messagerie_as_read

def destroy
retrieve_procedure_presentation if current_instructeur
mfo marked this conversation as resolved.
Show resolved Hide resolved
if commentaire.sent_by?(current_instructeur) || commentaire.sent_by?(current_expert)
commentaire.soft_delete!

Expand Down
37 changes: 35 additions & 2 deletions app/controllers/instructeurs/dossiers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ class DossiersController < ProceduresController
include CreateAvisConcern
include DossierHelper
include TurboChampsConcern

include InstructeurConcern
include ActionController::Streaming
include Zipline

before_action :redirect_on_dossier_not_found, only: :show
before_action :redirect_on_dossier_in_batch_operation, only: [:archive, :unarchive, :follow, :unfollow, :passer_en_instruction, :repasser_en_construction, :repasser_en_instruction, :terminer, :restore, :destroy, :extend_conservation]
before_action :set_gallery_attachments, only: [:show, :pieces_jointes, :annotations_privees, :avis, :messagerie, :personnes_impliquees, :reaffectation]
after_action :mark_demande_as_read, only: :show
before_action :retrieve_procedure_presentation, only: [:annotations_privees, :avis_new, :avis, :messagerie, :personnes_impliquees, :pieces_jointes, :reaffectation, :show, :dossier_labels, :passer_en_instruction, :repasser_en_construction, :repasser_en_instruction, :terminer, :pending_correction, :create_avis, :create_commentaire]

after_action :mark_demande_as_read, only: :show
after_action :mark_messagerie_as_read, only: [:messagerie, :create_commentaire, :pending_correction]
after_action :mark_avis_as_read, only: [:avis, :create_avis]
after_action :mark_annotations_privees_as_read, only: [:annotations_privees, :update_annotations]
Expand Down Expand Up @@ -390,8 +391,40 @@ def pieces_jointes
@pieces_jointes_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.pieces_jointes_seen_at
end

def next
navigate_through_dossiers_list do |cache|
cache.next_dossier_id(from_id: params[:dossier_id])
end
end

def previous
navigate_through_dossiers_list do |cache|
cache.previous_dossier_id(from_id: params[:dossier_id])
end
end

private

def navigate_through_dossiers_list
dossier = dossier_scope.find(params[:dossier_id])
procedure_presentation = current_instructeur.procedure_presentation_for_procedure_id(dossier.procedure.id)
cache = Cache::ProcedureDossierPagination.new(procedure_presentation:, statut: params[:statut])

next_or_previous_dossier_id = yield(cache)

if next_or_previous_dossier_id
redirect_to instructeur_dossier_path(procedure_id: procedure.id, dossier_id: next_or_previous_dossier_id, statut: params[:statut])
else
redirect_back fallback_location: instructeur_dossier_path(procedure_id: procedure.id, dossier_id: dossier.id, statut: params[:statut]), alert: "Une erreur est survenue"
end
rescue ActiveRecord::RecordNotFound
Sentry.capture_message(
"Navigation through dossier failed => ActiveRecord::RecordNotFound",
extra: { dossier_id: params[:dossier_id] }
)
redirect_to instructeur_procedure_path(procedure_id: procedure.id), alert: "Une erreur est survenue"
end

def dossier_scope
if action_name == 'update_annotations'
Dossier
Expand Down
8 changes: 8 additions & 0 deletions app/controllers/instructeurs/procedures_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ def show
.where(groupe_instructeurs: current_instructeur.groupe_instructeurs.where(procedure_id: @procedure.id))
.where(seen_at: nil)
.distinct

cache_show_procedure_state # don't move in callback, inherited by Instructeurs::DossiersController
end

def deleted_dossiers
Expand Down Expand Up @@ -392,5 +394,11 @@ def cookies_export_key
def ordered_procedure_ids_params
params.require(:ordered_procedure_ids)
end

def cache_show_procedure_state
cache = Cache::ProcedureDossierPagination.new(procedure_presentation:, statut:)

cache.save_context(ids: @filtered_sorted_paginated_ids, incoming_page: params[:page])
end
end
end
102 changes: 102 additions & 0 deletions app/models/cache/procedure_dossier_pagination.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

class Cache::ProcedureDossierPagination
HALF_WINDOW = 50
TRESHOLD_BEFORE_REFRESH = 1
CACHE_EXPIRACY = 8.hours

attr_reader :procedure_presentation, :statut, :cache
delegate :procedure, :instructeur, to: :procedure_presentation

def initialize(procedure_presentation:, statut:)
@procedure_presentation = procedure_presentation
@statut = statut
@cache = Kredis.json(cache_key, expires_in: CACHE_EXPIRACY)
end

def save_context(ids:, incoming_page:)
value = { ids: }
value[:incoming_page] = incoming_page if incoming_page
write_cache(value)
end

def next_dossier_id(from_id:)
index = ids&.index(from_id.to_i)

return nil if index.nil? # not found

if refresh_cache_after?(from_id:)
renew_ids(from_id:)
index = ids.index(from_id.to_i)
end
return nil if index.blank?
return nil if index + 1 > ids.size # out of bound end

ids[index + 1]
end

def previous_dossier_id(from_id:)
index = ids&.index(from_id.to_i)

return nil if index.nil? # not found

if refresh_cache_before?(from_id:)
renew_ids(from_id:)
index = ids.index(from_id.to_i)
end
return nil if index.blank?
return nil if index - 1 < 0 # out of bound start

ids[index - 1]
end

def incoming_page
read_cache[:incoming_page]
end

private

def cache_key
[procedure.id, instructeur.id, statut].join(":")
end

def write_cache(value)
cache.value = value
@read_cache = nil
end

def read_cache
@read_cache ||= Hash(cache.value).with_indifferent_access
end

def ids = read_cache[:ids]

def refresh_cache_after?(from_id:) = from_id.in?(ids.last(TRESHOLD_BEFORE_REFRESH))

def refresh_cache_before?(from_id:) = from_id.in?(ids.first(TRESHOLD_BEFORE_REFRESH))

def renew_ids(from_id:)
value = read_cache
value[:ids] = fetch_ids_around(from_id:)

write_cache(value)
end

def fetch_all_ids
dossiers = Dossier.where(groupe_instructeur_id: GroupeInstructeur.joins(:instructeurs, :procedure).where(procedure: procedure, instructeurs: [instructeur]).select(:id))
DossierFilterService.filtered_sorted_ids(dossiers, statut, procedure_presentation.filters_for(statut), procedure_presentation.sorted_column, instructeur, count: 0)

Check warning on line 87 in app/models/cache/procedure_dossier_pagination.rb

View check run for this annotation

Codecov / codecov/patch

app/models/cache/procedure_dossier_pagination.rb#L86-L87

Added lines #L86 - L87 were not covered by tests
end

def fetch_ids_around(from_id:)
all_ids = fetch_all_ids
from_id_at = all_ids.index(from_id)

if from_id_at.present?
new_page_starts_at = [0, from_id_at - HALF_WINDOW].max # avoid index below 0
new_page_ends_at = [from_id_at + HALF_WINDOW, all_ids.size].min # avoid index above all_ids.size
all_ids.slice(new_page_starts_at, new_page_ends_at)
else
[]
end
end
end
19 changes: 14 additions & 5 deletions app/models/instructeur.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,14 @@ def update_instructeur_procedures_positions(ordered_procedure_ids)
end
end

def procedure_presentation_for_procedure_id(procedure_id)
assign_to = assign_to_for_procedure_id(procedure_id)
assign_to.procedure_presentation || assign_to.create_procedure_presentation!
end

def procedure_presentation_and_errors_for_procedure_id(procedure_id)
assign_to
.joins(:groupe_instructeur)
.includes(:instructeur, :procedure)
.find_by(groupe_instructeurs: { procedure_id: procedure_id })
.procedure_presentation_or_default_and_errors
assign_to = assign_to_for_procedure_id(procedure_id)
assign_to.procedure_presentation_or_default_and_errors
end

def notifications_for_dossier(dossier)
Expand Down Expand Up @@ -355,4 +357,11 @@ def notifications_for(condition)
.merge(followed_dossiers)
.with_notifications
end

def assign_to_for_procedure_id(procedure_id)
assign_to
.joins(:groupe_instructeur)
.includes(:instructeur, :procedure)
.find_by(groupe_instructeurs: { procedure_id: procedure_id })
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
= render Dossiers::MessageComponent.new(commentaire: @commentaire, connected_user: @commentaire.instructeur || @commentaire.expert)

- if current_user.instructeur? && @commentaire.dossier_correction.present?
= turbo_stream.replace 'header-top', partial: 'instructeurs/dossiers/header_top', locals: { dossier: @commentaire.dossier }
= turbo_stream.replace 'header-top', partial: 'instructeurs/dossiers/header_top', locals: { dossier: @commentaire.dossier, procedure_presentation: @procedure_presentation }
2 changes: 1 addition & 1 deletion app/views/instructeurs/dossiers/_header.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
locals: { steps: [[t('show_procedure', scope: [:layouts, :breadcrumb], libelle: dossier.procedure.libelle.truncate(22)), instructeur_procedure_path(dossier.procedure)],
[t('show_dossier', scope: [:layouts, :breadcrumb], dossier_id: dossier.id, owner_name: dossier.owner_name)]] }

= render partial: 'instructeurs/dossiers/header_top', locals: { dossier: }
= render partial: 'instructeurs/dossiers/header_top', locals: { dossier:, procedure_presentation: }
= render partial: 'instructeurs/dossiers/header_bottom', locals: { dossier:, gallery_attachments: }

.fr-container
Expand Down
13 changes: 4 additions & 9 deletions app/views/instructeurs/dossiers/_header_top.html.haml
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
#header-top.fr-container
.flex
%div
.flex.fr-mb-1w
= render Instructeurs::BackButtonComponent.new(to: instructeur_procedure_path(dossier.procedure, statut: params[:statut]))
%h1.fr-h3.fr-mb-1w
= t('show_dossier', scope: [:layouts, :breadcrumb], dossier_id: dossier.id, owner_name: dossier.owner_name)

= render Instructeurs::DossiersNavigationComponent.new(dossier:, procedure_presentation:, statut: params[:statut])

= link_to dossier.procedure.libelle.truncate_words(10), instructeur_procedure_path(dossier.procedure), title: dossier.procedure.libelle, class: "fr-link"
.fr-mt-2w.fr-badge-group
.flex.fr-mb-3w
%div
.fr-mt-2w.badge-group
= procedure_badge(dossier.procedure)

= status_badge(dossier.state)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- content_for(:title, "Annotations privées · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")

= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments }
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }

#dossier-annotations-privees
.fr-container
Expand Down
2 changes: 1 addition & 1 deletion app/views/instructeurs/dossiers/avis.html.haml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- content_for(:title, "Avis · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")

= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments }
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }

.container
.fr-grid-row
Expand Down
2 changes: 1 addition & 1 deletion app/views/instructeurs/dossiers/avis_new.html.haml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- content_for(:title, "Avis · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")

= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments }
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }

.container
.fr-grid-row
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
= turbo_stream.replace 'header-top', partial: 'header_top', locals: { dossier: @dossier }
= turbo_stream.replace 'header-top', partial: 'header_top', locals: { dossier: @dossier, procedure_presentation: @procedure_presentation }
2 changes: 1 addition & 1 deletion app/views/instructeurs/dossiers/messagerie.html.haml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
- content_for(:title, "Messagerie · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")

= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments }
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }

= render partial: "shared/dossiers/messagerie", locals: { dossier: @dossier, connected_user: current_instructeur, messagerie_seen_at: @messagerie_seen_at , new_commentaire: @commentaire, form_url: commentaire_instructeur_dossier_path(@dossier.procedure, @dossier, statut: params[:statut]) }
Loading
Loading