diff --git a/app/assets/stylesheets/dsfr.scss b/app/assets/stylesheets/dsfr.scss
index bd3100ce6..b412f7981 100644
--- a/app/assets/stylesheets/dsfr.scss
+++ b/app/assets/stylesheets/dsfr.scss
@@ -39,6 +39,13 @@ input[type="radio"] {
}
// scss-lint:enable DuplicateProperty
+// remove additional calendar icon on date input already handle by Firefox navigator
+@-moz-document url-prefix() {
+ .fr-input[type="date"] {
+ background-image: none;
+ }
+}
+
// remove pointer cursor on textarea
textarea {
cursor: auto;
@@ -66,3 +73,15 @@ fieldset {
color: $light-red;
box-shadow: 0px 0px 0px 1px $light-red;
}
+
+// on utilise le dropdown de sélecteur de langue pour un autre usage donc on veut retirer l'icone
+.fr-translate .fr-translate__btn.custom-fr-translate-no-icon::before {
+ display: none;
+}
+
+// on veut ferrer à droite le dropdown de sélecteur de langue
+@media (min-width: 62em) {
+ .fr-nav__item.custom-fr-translate-flex-end {
+ align-items: flex-end;
+ }
+}
diff --git a/app/components/dossiers/filter_component.rb b/app/components/dossiers/instructeur_filter_component.rb
similarity index 89%
rename from app/components/dossiers/filter_component.rb
rename to app/components/dossiers/instructeur_filter_component.rb
index 1f4c9a54f..5f55441b1 100644
--- a/app/components/dossiers/filter_component.rb
+++ b/app/components/dossiers/instructeur_filter_component.rb
@@ -1,4 +1,4 @@
-class Dossiers::FilterComponent < ApplicationComponent
+class Dossiers::InstructeurFilterComponent < ApplicationComponent
def initialize(procedure:, procedure_presentation:, statut:, field_id: nil)
@procedure = procedure
@procedure_presentation = procedure_presentation
diff --git a/app/components/dossiers/filter_component/filter_component.en.yml b/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.en.yml
similarity index 100%
rename from app/components/dossiers/filter_component/filter_component.en.yml
rename to app/components/dossiers/instructeur_filter_component/instructeur_filter_component.en.yml
diff --git a/app/components/dossiers/filter_component/filter_component.fr.yml b/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.fr.yml
similarity index 100%
rename from app/components/dossiers/filter_component/filter_component.fr.yml
rename to app/components/dossiers/instructeur_filter_component/instructeur_filter_component.fr.yml
diff --git a/app/components/dossiers/filter_component/filter_component.html.haml b/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.html.haml
similarity index 100%
rename from app/components/dossiers/filter_component/filter_component.html.haml
rename to app/components/dossiers/instructeur_filter_component/instructeur_filter_component.html.haml
diff --git a/app/components/dossiers/user_filter_component.rb b/app/components/dossiers/user_filter_component.rb
new file mode 100644
index 000000000..8f7d731c5
--- /dev/null
+++ b/app/components/dossiers/user_filter_component.rb
@@ -0,0 +1,23 @@
+class Dossiers::UserFilterComponent < ApplicationComponent
+ include DossierHelper
+
+ def initialize(statut:, filter:)
+ @statut = statut
+ @filter = filter
+ end
+
+ attr_reader :statut, :filter
+
+ def render?
+ ['en-cours', 'traites'].include?(@statut)
+ end
+
+ def states_collection(statut)
+ case statut
+ when 'en-cours'
+ Dossier.states.values - Dossier::TERMINE
+ when 'traites'
+ Dossier::TERMINE
+ end
+ end
+end
diff --git a/app/components/dossiers/user_filter_component/user_filter_component.en.yml b/app/components/dossiers/user_filter_component/user_filter_component.en.yml
new file mode 100644
index 000000000..bc72f08e2
--- /dev/null
+++ b/app/components/dossiers/user_filter_component/user_filter_component.en.yml
@@ -0,0 +1,16 @@
+en:
+ legend:
+ states: States
+ created_at: Creation date
+ depose_at: Submission date
+ button:
+ apply_filters: Apply filters
+ reset_filters: Reset filters
+ select_filters: Select a filter
+ tag:
+ active_filters:
+ one: "1 active filter"
+ other: "%{count} active filters"
+ active_filters_link_title:
+ one: Remove the active filter
+ other: "Remove the %{count} active filters"
diff --git a/app/components/dossiers/user_filter_component/user_filter_component.fr.yml b/app/components/dossiers/user_filter_component/user_filter_component.fr.yml
new file mode 100644
index 000000000..83348118c
--- /dev/null
+++ b/app/components/dossiers/user_filter_component/user_filter_component.fr.yml
@@ -0,0 +1,16 @@
+fr:
+ legend:
+ states: États
+ created_at: Date de création
+ depose_at: Date de dépot
+ button:
+ apply_filters: Appliquer les filtres
+ reset_filters: Réinitialiser les filtres
+ select_filters: Sélectionner un filtre
+ tag:
+ active_filters:
+ one: "1 filtre actif"
+ other: "%{count} filtres actifs"
+ active_filters_link_title:
+ one: Retirer le filtre actif
+ other: "Retirer les %{count} filtres actifs"
diff --git a/app/components/dossiers/user_filter_component/user_filter_component.html.haml b/app/components/dossiers/user_filter_component/user_filter_component.html.haml
new file mode 100644
index 000000000..5a6c92e51
--- /dev/null
+++ b/app/components/dossiers/user_filter_component/user_filter_component.html.haml
@@ -0,0 +1,37 @@
+.fr-grid-row
+ .fr-col-12
+ %nav.fr-translate.fr-nav
+ .fr-nav__item.custom-fr-translate-flex-end
+ %button.fr-translate__btn.translate-no-icon.fr-btn.fr-btn--tertiary.custom-fr-translate-no-icon{ "aria-controls" => "filters", "aria-expanded" => "false", title: t('.button.select_filters') }
+ = t('.button.select_filters')
+ #filters.fr-collapse.fr-translate__menu.fr-menu
+ %ul.fr-menu__list.fr-p-3w
+ = form_with(url: dossiers_path(), method: :get ) do |f|
+ = f.hidden_field :statut, value: @statut
+
+ %fieldset#checkboxes.fr-fieldset{ "aria-labelledby" => "checkboxes-legend" }
+ %legend#checkboxes-legend.fr-fieldset__legend--regular.fr-fieldset__legend
+ = t('.legend.states')
+ = f.collection_check_boxes :states, states_collection(@statut), :to_s, :to_s, include_hidden: false do |b|
+ .fr-fieldset__element
+ .fr-checkbox-group.fr-ml-2w.fr-py-1w
+ = b.check_box(checked: filter.states_filtered?(b.value))
+ = b.label(class: 'fr-label') { dossier_display_state(b.text) }
+
+
+ .fr-input-group
+ = f.label 'from_created_at_date', t('.legend.created_at'), class: 'fr-label'
+ = f.date_field 'from_created_at_date', value: @filter.from_created_at_date, class: 'fr-input'
+
+
+ .fr-input-group
+ = f.label 'from_depose_at_date', t('.legend.depose_at'), class: 'fr-label'
+ = f.date_field 'from_depose_at_date', value: @filter.from_depose_at_date, class: 'fr-input'
+
+ .fr-my-2w
+ = f.submit t('.button.apply_filters'), class: 'fr-btn fr-btn--sm'
+ = link_to t('.button.reset_filters'), dossiers_path(statut: @statut), class: 'fr-btn fr-btn--sm fr-btn--tertiary-no-outline'
+
+ - if @filter.filter_params.present?
+ .fr-col-12.text-right
+ = link_to t('.tag.active_filters', count: @filter.filter_params_count), dossiers_path(statut: @statut), title: t('.tag.active_filters_link_title', count: @filter.filter_params_count), class: 'fr-tag fr-tag--sm fr-tag--dismiss'
diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb
index d4d4d1469..8af994e31 100644
--- a/app/controllers/users/dossiers_controller.rb
+++ b/app/controllers/users/dossiers_controller.rb
@@ -49,6 +49,9 @@ module Users
end.page(page)
@first_brouillon_recently_updated = current_user.dossiers.visible_by_user.brouillons_recently_updated.first
+
+ @filter = DossiersFilter.new(current_user, params)
+ @dossiers = @filter.filter_procedures(@dossiers).page(page)
end
def show
diff --git a/app/models/dossiers_filter.rb b/app/models/dossiers_filter.rb
new file mode 100644
index 000000000..ab1c4deb1
--- /dev/null
+++ b/app/models/dossiers_filter.rb
@@ -0,0 +1,53 @@
+class DossiersFilter
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params.permit(:page, :from_created_at_date, :from_depose_at_date, states: [])
+ end
+
+ def filter_params
+ params[:from_created_at_date].presence || params[:from_depose_at_date].presence || params[:states].presence
+ end
+
+ def filter_params_count
+ count = 0
+ count += 1 if params[:from_created_at_date].presence
+ count += 1 if params[:from_depose_at_date].presence
+ count += params[:states].count if params[:states].presence
+ count
+ end
+
+ def filter_procedures(dossiers)
+ return dossiers if filter_params.blank?
+ dossiers_result = dossiers
+ dossiers_result = dossiers_result.where(state: states) if states.present?
+ dossiers_result = dossiers_result.where('created_at >= ?', from_created_at_date) if from_created_at_date.present?
+ dossiers_result = dossiers_result.where('depose_at >= ?', from_depose_at_date) if from_depose_at_date.present?
+ dossiers_result
+ end
+
+ def states
+ params[:states].compact_blank if params[:states].present?
+ end
+
+ def states_filtered?(state)
+ states&.include?(state)
+ end
+
+ def from_created_at_date
+ return if params[:from_created_at_date].blank?
+
+ Date.parse(params[:from_created_at_date])
+ rescue Date::Error
+ nil
+ end
+
+ def from_depose_at_date
+ return if params[:from_depose_at_date].blank?
+
+ Date.parse(params[:from_depose_at_date])
+ rescue Date::Error
+ nil
+ end
+end
diff --git a/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml b/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml
index 7106f0d58..4bcafb3ed 100644
--- a/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml
+++ b/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml
@@ -3,5 +3,4 @@
= t('views.instructeurs.dossiers.filters.title')
- menu.with_form do
- = render Dossiers::FilterComponent.new(procedure: procedure, procedure_presentation: @procedure_presentation, statut: statut)
-
+ = render Dossiers::InstructeurFilterComponent.new(procedure: procedure, procedure_presentation: @procedure_presentation, statut: statut)
diff --git a/app/views/instructeurs/procedures/update_filter.turbo_stream.haml b/app/views/instructeurs/procedures/update_filter.turbo_stream.haml
index 05598d95b..a5cd42c91 100644
--- a/app/views/instructeurs/procedures/update_filter.turbo_stream.haml
+++ b/app/views/instructeurs/procedures/update_filter.turbo_stream.haml
@@ -1,2 +1,2 @@
= turbo_stream.replace 'filter-component' do
- = render Dossiers::FilterComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation, statut: @statut, field_id: @field)
+ = render Dossiers::InstructeurFilterComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation, statut: @statut, field_id: @field)
diff --git a/app/views/users/dossiers/_dossiers_list.html.haml b/app/views/users/dossiers/_dossiers_list.html.haml
index b62773984..7e4bccd89 100644
--- a/app/views/users/dossiers/_dossiers_list.html.haml
+++ b/app/views/users/dossiers/_dossiers_list.html.haml
@@ -46,9 +46,16 @@
= paginate dossiers, views_prefix: 'shared'
- else
- .blank-tab
- %h2.empty-text Aucun dossier
- %p.empty-text-details
- Pour remplir une démarche, contactez votre administration en lui demandant le lien de la démarche.
- %br
- Celui ci doit ressembler à #{APPLICATION_BASE_URL}/commencer/xxx.
+ - if filter.filter_params.present?
+ .blank-tab
+ %h2.empty-text= t('views.users.dossiers.dossiers_list.no_result_title')
+ %p.empty-text-details
+ = t('views.users.dossiers.dossiers_list.no_result_text_with_filter')
+ %br
+ = link_to t('views.users.dossiers.dossiers_list.no_result_reset_filter'), dossiers_path(statut: statut), class: 'fr-btn fr-btn--sm fr-mt-2w'
+
+ - else
+ .blank-tab
+ %h2.empty-text= t('views.users.dossiers.dossiers_list.no_result_title')
+ %p.empty-text-details
+ = t('views.users.dossiers.dossiers_list.no_result_text_html')
diff --git a/app/views/users/dossiers/index.html.haml b/app/views/users/dossiers/index.html.haml
index d29def972..8fcbe4fba 100644
--- a/app/views/users/dossiers/index.html.haml
+++ b/app/views/users/dossiers/index.html.haml
@@ -68,6 +68,8 @@
= render partial: "dossiers_list", locals: { dossiers: @dossiers }
- else
+ = render Dossiers::UserFilterComponent.new(statut: @statut, filter: @filter)
+
- if @statut == "en-cours"
- if @first_brouillon_recently_updated.present?
= render Dsfr::CalloutComponent.new(title: t('users.dossiers.header.callout.first_brouillon_recently_updated_title'), heading_level: 'h2') do |c|
@@ -76,13 +78,13 @@
= t('users.dossiers.header.callout.first_brouillon_recently_updated_text', time_ago: time_ago_in_words(@first_brouillon_recently_updated.created_at), libelle: @first_brouillon_recently_updated.procedure.libelle )
= link_to t('users.dossiers.header.callout.first_brouillon_recently_updated_button'), url_for_dossier(@first_brouillon_recently_updated), class: 'fr-btn'
- = render partial: "dossiers_list", locals: { dossiers: @dossiers }
+ = render partial: "dossiers_list", locals: { dossiers: @dossiers, filter: @filter, statut: @statut }
- if @statut == "traites"
- = render partial: "dossiers_list", locals: { dossiers: @dossiers }
+ = render partial: "dossiers_list", locals: { dossiers: @dossiers, filter: @filter, statut: @statut }
- if @statut == "dossiers-invites"
- = render partial: "dossiers_list", locals: { dossiers: @dossiers }
+ = render partial: "dossiers_list", locals: { dossiers: @dossiers, filter: @filter, statut: @statut }
- if @statut == "dossiers-supprimes-recemment"
= render partial: "hidden_dossiers_list", locals: { hidden_dossiers: @dossiers }
@@ -95,4 +97,4 @@
= render partial: "transfered_dossiers_list", locals: { dossier_transfers: @dossiers }
- if @statut == "dossiers-expirant"
- = render partial: "dossiers_list", locals: { dossiers: @dossiers }
+ = render partial: "dossiers_list", locals: { dossiers: @dossiers, filter: @filter, statut: @statut }
diff --git a/config/locales/en.yml b/config/locales/en.yml
index d043d4674..5b11d6a75 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -461,6 +461,10 @@ en:
status: "Status"
updated: "Updated"
actions: "Actions"
+ no_result_title: No files
+ no_result_text_html: "To fill a procedure, contact your administration asking for the procedure link.
It should look like #{APPLICATION_BASE_URL}/commencer/xxx."
+ no_result_text_with_filter: found with selected filters
+ no_result_reset_filter: Reset filters
transfers:
sender_demande_en_cours: "A transfer request is pending on file Nº %{id} to %{email}"
receiver_demande_en_cours: "Transfer request on file Nº %{id} sent by %{email}"
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index f59b3d6bb..672179445 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -462,6 +462,10 @@ fr:
status: "Statut"
updated: "Mis à jour"
actions: "Actions"
+ no_result_title: Aucun dossier
+ no_result_text_html: "Pour remplir une démarche, contactez votre administration en lui demandant le lien de la démarche.
Celui ci doit ressembler à #{APPLICATION_BASE_URL}/commencer/xxx."
+ no_result_text_with_filter: ne correspond aux filtres sélectionnés
+ no_result_reset_filter: Réinitialiser les filtres
dossier_action:
edit_dossier: "Modifier le dossier"
start_other_dossier: "Commencer un autre dossier vide"
diff --git a/spec/system/users/list_dossiers_spec.rb b/spec/system/users/list_dossiers_spec.rb
index d44eb0711..c692f5279 100644
--- a/spec/system/users/list_dossiers_spec.rb
+++ b/spec/system/users/list_dossiers_spec.rb
@@ -4,6 +4,7 @@ describe 'user access to the list of their dossiers', js: true do
let!(:dossier_en_construction) { create(:dossier, :with_populated_champs, :en_construction, user: user) }
let!(:dossier_en_instruction) { create(:dossier, :en_instruction, user: user) }
let!(:dossier_traite) { create(:dossier, :accepte, user: user) }
+ let!(:dossier_refuse) { create(:dossier, :refuse, user: user) }
let!(:dossier_archived) { create(:dossier, :en_instruction, :archived, user: user) }
let(:dossiers_per_page) { 25 }
let(:last_updated_dossier) { dossier_en_construction }
@@ -28,7 +29,7 @@ describe 'user access to the list of their dossiers', js: true do
expect(page).to have_content(dossier_en_instruction.procedure.libelle)
expect(page).to have_content(dossier_archived.procedure.libelle)
expect(page).to have_text('4 en cours')
- expect(page).to have_text('1 traité')
+ expect(page).to have_text('2 traités')
end
it 'the list must be ordered by last updated' do
@@ -51,7 +52,82 @@ describe 'user access to the list of their dossiers', js: true do
page.click_link("Suivant")
expect(page).to have_content(dossier_en_instruction.procedure.libelle)
expect(page).to have_text('4 en cours')
- expect(page).to have_text('1 traité')
+ expect(page).to have_text('2 traités')
+ end
+ end
+
+ context 'when user uses filter' do
+ scenario 'user filters state on tab "en-cours"' do
+ expect(page).to have_text('4 en cours')
+ expect(page).to have_text('2 traités')
+ expect(page).to have_text('4 sur 4 dossiers')
+ click_on('Sélectionner un filtre')
+ expect(page).to have_text('Brouillon')
+ expect(page).to have_text('En construction')
+ expect(page).to have_text('En instruction')
+ find("label", text: "Brouillon").click
+ click_on('Appliquer les filtres')
+ expect(page).to have_text('1 dossier')
+ expect(page).to have_checked_field('Brouillon')
+ end
+
+ scenario 'user filters state on tab "traité"' do
+ visit dossiers_path(statut: 'traites')
+ expect(page).to have_text('4 en cours')
+ expect(page).to have_text('2 traités')
+ expect(page).to have_text('2 sur 2 dossiers')
+
+ click_on('Sélectionner un filtre')
+ expect(page).to have_text('Accepté')
+ expect(page).to have_text('Refusé')
+ expect(page).to have_text('Classé sans suite')
+ find("label", text: "Refusé").click
+ click_on('Appliquer les filtres')
+ expect(page).to have_text('1 dossier')
+ expect(page).to have_checked_field('Refusé')
+
+ click_on('Sélectionner un filtre')
+ click_on('Réinitialiser les filtres')
+ expect(page).to have_text('2 sur 2 dossiers')
+ expect(page).to have_unchecked_field('Refusé')
+ end
+
+ scenario 'user filters by created_at' do
+ dossier_en_construction.update!(created_at: Date.yesterday)
+
+ expect(page).to have_text('4 sur 4 dossiers')
+ click_on('Sélectionner un filtre')
+ fill_in 'from_created_at_date', with: Date.today
+ click_on('Appliquer les filtres')
+ expect(page).to have_text('3 sur 3 dossiers')
+ end
+
+ scenario 'user uses multiple filters' do
+ dossier_en_construction.update!(created_at: Date.yesterday)
+ dossier_en_instruction.update!(depose_at: Date.yesterday)
+
+ expect(page).to have_text('4 sur 4 dossiers')
+ click_on('Sélectionner un filtre')
+ fill_in 'from_created_at_date', with: Date.today
+ click_on('Appliquer les filtres')
+ expect(page).to have_text('3 sur 3 dossiers')
+ expect(page).to have_text('1 filtre actif')
+
+ click_on('Sélectionner un filtre')
+ find("label", text: "En construction").click
+ find("label", text: "En instruction").click
+ click_on('Appliquer les filtres')
+ expect(page).to have_text('2 sur 2 dossiers')
+ expect(page).to have_text('3 filtres actifs')
+
+ click_on('Sélectionner un filtre')
+ fill_in 'from_depose_at_date', with: Date.today
+ click_on('Appliquer les filtres')
+ expect(page).to have_text('1 dossier')
+ expect(page).to have_text('4 filtres actifs')
+ click_on('4 filtres actifs')
+ expect(page).to have_text('4 sur 4 dossiers')
+ expect(page).not_to have_text('4 filtres actifs')
end
end
diff --git a/spec/views/users/dossiers/index.html.haml_spec.rb b/spec/views/users/dossiers/index.html.haml_spec.rb
index 8b7678f1c..8cb30522f 100644
--- a/spec/views/users/dossiers/index.html.haml_spec.rb
+++ b/spec/views/users/dossiers/index.html.haml_spec.rb
@@ -6,6 +6,7 @@ describe 'users/dossiers/index', type: :view do
let(:user_dossiers) { [dossier_brouillon, dossier_en_construction, dossier_termine] }
let(:dossiers_invites) { [] }
let(:statut) { 'en-cours' }
+ let(:filter) { DossiersFilter.new(user, ActionController::Parameters.new(random_param: 'random_param')) }
before do
allow(view).to receive(:new_demarche_url).and_return('#')
@@ -19,6 +20,7 @@ describe 'users/dossiers/index', type: :view do
assign(:dossiers_close_to_expiration, Kaminari.paginate_array([]).page(1))
assign(:dossiers, Kaminari.paginate_array(user_dossiers).page(1))
assign(:statut, statut)
+ assign(:filter, filter)
render
end