Merge pull request #7976 from demarches-simplifiees/list-all-demarches

ETQ Administrateur, je peux voir la liste de toutes les démarches.
Filtrées par zone et/ou statut
This commit is contained in:
krichtof 2022-10-31 16:23:47 +01:00 committed by GitHub
commit a7a422810d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 310 additions and 13 deletions

View file

@ -0,0 +1,3 @@
#all-demarches .procedure {
cursor: pointer;
}

View file

@ -0,0 +1,26 @@
.sidebar-filter {
ul {
list-style: none;
padding-inline-start: 0;
border-top: 1px solid var(--border-plain-grey);
}
li {
border-bottom: 1px solid var(--border-plain-grey);
}
button {
font-size: 1rem;
line-height: 1.5rem;
}
legend {
width: 100%;
display: flex;
justify-content: space-between;
}
.reinit a {
background-image: none;
}
}

View file

@ -325,6 +325,19 @@ module Administrateurs
@procedure = Procedure.includes(draft_revision: { revision_types_de_champ_public: :type_de_champ }).find(@procedure.id) @procedure = Procedure.includes(draft_revision: { revision_types_de_champ_public: :type_de_champ }).find(@procedure.id)
end end
def all
@admin_zones = current_administrateur.zones
@other_zones = Zone.all - @admin_zones
@zone_ids = params[:zone_ids].filter(&:present?) if params[:zone_ids]
@selected_zones = @zone_ids.map { |id| Zone.find(id) } if @zone_ids.present?
@statuses = params[:statuses].filter(&:present?) if params[:statuses]
@procedures = Procedure.joins(:procedures_zones).publiees_ou_closes
@procedures = @procedures.where(procedures_zones: { zone_id: @zone_ids }) if @zone_ids.present?
@procedures = @procedures.where(aasm_state: @statuses) if @statuses.present?
@procedures = @procedures.page(params[:page]).per(ITEMS_PER_PAGE).order(published_at: :desc)
end
private private
def draft_valid? def draft_valid?

View file

@ -0,0 +1,18 @@
import { ApplicationController } from './application_controller';
import { toggle } from '@utils';
export class AutosubmitController extends ApplicationController {
static targets = ['form', 'spinner'];
declare readonly formTarget: HTMLFormElement;
declare readonly spinnerTarget: HTMLElement;
submit() {
this.formTarget.requestSubmit();
}
connect() {
this.onGlobal('turbo:submit-start', () => {
toggle(this.spinnerTarget);
});
}
}

View file

@ -0,0 +1,15 @@
import { ApplicationController } from './application_controller';
import { toggle, toggleExpandIcon } from '@utils';
export class ExpandController extends ApplicationController {
static targets = ['content', 'icon'];
declare readonly contentTarget: HTMLElement;
declare readonly iconTarget: HTMLElement;
toggle(event: Event) {
event.preventDefault();
toggle(this.contentTarget);
toggleExpandIcon(this.iconTarget);
}
}

View file

@ -28,3 +28,4 @@
@import '@gouvfr/dsfr/dist/component/footer/footer.css'; @import '@gouvfr/dsfr/dist/component/footer/footer.css';
@import '@gouvfr/dsfr/dist/component/search/search.css'; @import '@gouvfr/dsfr/dist/component/search/search.css';
@import '@gouvfr/dsfr/dist/component/translate/translate.css'; @import '@gouvfr/dsfr/dist/component/translate/translate.css';
@import '@gouvfr/dsfr/dist/component/pagination/pagination.css';

View file

@ -4,6 +4,7 @@ import {
show, show,
hide, hide,
toggle, toggle,
toggleExpandIcon,
isSelectElement, isSelectElement,
isTextInputElement, isTextInputElement,
isCheckboxOrRadioInputElement isCheckboxOrRadioInputElement
@ -34,6 +35,18 @@ suite('@utils', () => {
expect(input.classList.contains('hidden')).toBeFalsy(); expect(input.classList.contains('hidden')).toBeFalsy();
}); });
test('toggleExpandIcon', () => {
const icon = document.createElement('icon');
icon.classList.add('fr-icon-add-line');
toggleExpandIcon(icon);
expect(icon.classList.contains('fr-icon-subtract-line')).toBeTruthy();
expect(icon.classList.contains('fr-icon-add-line')).toBeFalsy();
toggleExpandIcon(icon);
expect(icon.classList.contains('fr-icon-add-line')).toBeTruthy();
expect(icon.classList.contains('fr-icon-subtract-line')).toBeFalsy();
});
test('isSelectElement', () => { test('isSelectElement', () => {
const select = document.createElement('select'); const select = document.createElement('select');
const input = document.createElement('input'); const input = document.createElement('input');

View file

@ -83,6 +83,11 @@ export function toggle(el: Element | null, force?: boolean) {
} }
} }
export function toggleExpandIcon(icon: Element | null) {
icon?.classList.toggle('fr-icon-add-line');
icon?.classList?.toggle('fr-icon-subtract-line');
}
export function enable( export function enable(
el: HTMLSelectElement | HTMLInputElement | HTMLButtonElement | null el: HTMLSelectElement | HTMLInputElement | HTMLButtonElement | null
) { ) {

View file

@ -148,6 +148,10 @@ class Administrateur < ApplicationRecord
end end
end end
def zones
procedures.joins(:zones).flat_map(&:zones).uniq
end
# required to display feature flags field in manager # required to display feature flags field in manager
def features def features
end end

View file

@ -14,7 +14,7 @@ class Zone < ApplicationRecord
has_and_belongs_to_many :procedures, -> { order(published_at: :desc) }, inverse_of: :zone has_and_belongs_to_many :procedures, -> { order(published_at: :desc) }, inverse_of: :zone
def current_label def current_label
labels.first.name labels.where.not(name: 'Non attribué').first.name
end end
def label_at(date) def label_at(date)

View file

@ -0,0 +1,2 @@
%li
= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote, class: 'fr-pagination__link fr-pagination__link--first', 'aria-disabled': true, title: 'Première page', role: 'link'

View file

@ -0,0 +1,2 @@
%li
= link_to '...', '#', class: 'fr-pagination__link fr-displayed-lg'

View file

@ -0,0 +1,2 @@
%li
= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, rel: 'next', remote: remote, class: 'fr-pagination__link fr-pagination__link--last', 'aria-disabled': true, title: "Dernière page", role: 'link'

View file

@ -0,0 +1,2 @@
%li
= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote, class: 'fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label', 'aria-disabled': true, role: 'link'

View file

@ -0,0 +1,3 @@
%li
= link_to_unless page.current?, page, url, rel: page.rel, remote: remote, class: 'fr-pagination__link', 'aria-current': page.current? ? 'page' : nil, title: "Page #{page}", role: 'link' do
= content_tag 'a', page, 'aria-current': 'page', title: "Page #{page}", class: 'fr-pagination__link'

View file

@ -0,0 +1,12 @@
= paginator.render do
%nav.fr-pagination{ role: 'navigation', 'aria-label': 'Pagination' }
%ul.fr-pagination__list
= first_page_tag unless current_page.first?
= prev_page_tag unless current_page.first?
- each_page do |page|
- if page.display_tag?
= page_tag page
- elsif !page.was_truncated?
= gap_tag
= next_page_tag unless current_page.last?
= last_page_tag unless current_page.last?

View file

@ -0,0 +1,2 @@
%li
= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote, class: 'fr-pagination__link fr-pagination__link--prev fr-pagination__link--lg-label', 'aria-disabled': true, role: 'link'

View file

@ -0,0 +1,5 @@
.fr-container
%nav#header-navigation.fr-nav{ role: 'navigation', 'aria-label': 'Menu principal administrateur' }
%ul.fr-nav__list
%li.fr-nav__item= link_to 'Mes démarches', admin_procedures_path, class:'fr-nav__link', 'aria-current': current_page?(admin_procedures_path) ? 'page' : nil
%li.fr-nav__item= link_to 'Toutes les démarches', all_admin_procedures_path(zone_ids: current_administrateur.zones), class:'fr-nav__link', 'aria-current': current_page?(all_admin_procedures_path) ? 'page' : nil

View file

@ -0,0 +1,99 @@
= render 'main_menu'
.fr-container
%h1.fr-my-4w Toutes les démarches
.fr-container--fluid
.fr-grid-row.fr-grid-row--gutters
.fr-col-8
.fr-highlight.fr-mb-4w
%p Ce tableau de bord permet de consulter les informations sur les démarches simplifiées pour toutes les zones. Filtrez par zone et statut. Consultez la liste des démarches et cliquez sur une démarche pour voir la zone et quels sont les administrateurs.
.fr-container--fluid{ 'data-turbo': 'true', 'data-controller': 'autosubmit' }
.fr-grid-row.fr-grid-row--gutters
.fr-col-3
= form_with(url: all_admin_procedures_path, method: :get, html: {'data-autosubmit-target': 'form'}) do |f|
%fieldset.sidebar-filter
%legend
.title.font-weight-bold.fr-pl-2w
%span.fr-icon-filter-fill.fr-icon--sm.fr-mr-1w{ 'aria-hidden': 'true' }
Filtrer
.reinit
= link_to all_admin_procedures_path(zone_ids: current_administrateur.zones) do
%span.fr-icon-arrow-go-back-line Réinitialiser
%ul
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
.fr-mb-1w{ 'data-action': 'click->expand#toggle' }
%button
%span.fr-icon-add-line.fr-icon--sm.fr-mr-1w.fr-text-action-high--blue-france{ 'aria-hidden': 'true', 'data-expand-target': 'icon' }
Mes zones
.fr-ml-1w{ 'data-expand-target': 'content' }
= f.collection_check_boxes :zone_ids, @admin_zones, :id, :current_label, include_hidden: false do |b|
.fr-checkbox-group.fr-ml-2w
= b.check_box(checked: @zone_ids&.map(&:to_i)&.include?(b.value), 'data-action': 'autosubmit#submit')
= b.label(class: 'fr-label') { b.text }
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
.fr-mb-1w{ 'data-action': 'click->expand#toggle' }
%button
%span.fr-icon-add-line.fr-icon--sm.fr-mr-1w.fr-text-action-high--blue-france{ 'aria-hidden': 'true', 'data-expand-target': 'icon' }
Autres zones
.fr-ml-1w.hidden{ 'data-expand-target': 'content' }
= f.collection_check_boxes :zone_ids, @other_zones, :id, :current_label, include_hidden: false do |b|
.fr-checkbox-group.fr-ml-2w
= b.check_box(checked: @zone_ids&.map(&:to_i)&.include?(b.value), 'data-action': 'autosubmit#submit')
= b.label(class: 'fr-label') { b.text }
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
.fr-mb-1w{ 'data-action': 'click->expand#toggle' }
%button
%span.fr-icon-add-line.fr-icon--sm.fr-mr-1w.fr-text-action-high--blue-france{ 'aria-hidden': 'true', 'data-expand-target': 'icon' }
Statut
.fr-ml-1w.hidden{ 'data-expand-target': 'content' }
= f.collection_check_boxes :statuses, ['publiee', 'close'], :to_s, :to_s, include_hidden: false do |b|
.fr-checkbox-group.fr-ml-2w
= b.check_box(checked: @statuses&.include?(b.value), 'data-action': 'autosubmit#submit')
= b.label(class: 'fr-label') { t b.text, scope: 'activerecord.attributes.procedure.aasm_state' }
.fr-col-9
.fr-table.fr-table--bordered
%table#all-demarches
%caption
= "#{@procedures.total_count} démarches"
%span.hidden.fr-icon-ball-pen-fill{ 'aria-hidden': 'true', 'data-autosubmit-target': 'spinner' }
- if @selected_zones
.selected-zones.fr-mb-2w
- @selected_zones.each do |zone|
= link_to zone.current_label, all_admin_procedures_path(zone_ids: @selected_zones.map(&:id) - [zone.id]), class: 'fr-tag fr-tag--dismiss fr-mb-1w'
- if @statuses
.selected-statuses.fr-mb-2w
- @statuses.each do |status|
%p.fr-tag.fr-mb-1w.fr--background-alt-blue-france= status
= paginate @procedures, views_prefix: 'administrateurs'
%thead
%tr
%th{ scope: 'col' }
%th{ scope: 'col' } Démarche
%th{ scope: 'col' } N°
%th{ scope: 'col' } Administrateurs
%th{ scope: 'col' } Statut
%th{ scope: 'col' } Date
- @procedures.each do |procedure|
%tbody{ 'data-controller': 'expand' }
%tr.procedure{ 'data-action': 'click->expand#toggle' }
%td
%button.fr-icon-add-line.fr-icon--sm.fr-mr-1w.fr-mb-1w.fr-text-action-high--blue-france{ 'aria-hidden': 'true', 'data-expand-target': 'icon' }
%td= procedure.libelle
%td= procedure.id
%td= procedure.administrateurs.count
%td= t procedure.aasm_state, scope: 'activerecord.attributes.procedure.aasm_state'
%td= l(procedure.published_at, format: :message_date_without_time)
%tr.hidden{ 'data-expand-target': 'content' }
%td.fr-highlight--beige-gris-galet{ colspan: '6' }
.fr-container
.fr-grid-row
.fr-col-6
- procedure.zones.uniq.each do |zone|
= zone.label_at(procedure.published_or_created_at)
.fr-col-6
- procedure.administrateurs.uniq.each do |admin|
= admin.email
.fr-mt-2w= paginate @procedures, views_prefix: 'administrateurs'

View file

@ -1,7 +1,9 @@
= render 'main_menu'
.sub-header .sub-header
.procedure-admin-listing-container .procedure-admin-listing-container
= link_to "Nouvelle Démarche", new_from_existing_admin_procedures_path, id: 'new-procedure', class: 'fr-btn' = link_to "Nouvelle Démarche", new_from_existing_admin_procedures_path, id: 'new-procedure', class: 'fr-btn'
.container .fr-container
%nav.tabs %nav.tabs
%ul %ul
@ -10,7 +12,7 @@
= tab_item(t('pluralize.closed', count: @procedures_closed.count), admin_procedures_path(statut: 'archivees'), active: @statut == 'archivees', badge: number_with_html_delimiter(@procedures_closed_count)) = tab_item(t('pluralize.closed', count: @procedures_closed.count), admin_procedures_path(statut: 'archivees'), active: @statut == 'archivees', badge: number_with_html_delimiter(@procedures_closed_count))
= tab_item(t('pluralize.deleted', count: @procedures_deleted.count), admin_procedures_path(statut: 'supprimees'), active: @statut === 'supprimees', badge: number_with_html_delimiter(@procedures_deleted_count)) = tab_item(t('pluralize.deleted', count: @procedures_deleted.count), admin_procedures_path(statut: 'supprimees'), active: @statut === 'supprimees', badge: number_with_html_delimiter(@procedures_deleted_count))
.container#procedures{ data: { item_count: @statut === "publiees" ? @procedures_publiees_count : @statut === "brouillons" ? @procedures_draft_count : @procedures_closed_count } } .fr-container#procedures{ data: { item_count: @statut === "publiees" ? @procedures_publiees_count : @statut === "brouillons" ? @procedures_draft_count : @procedures_closed_count } }
- if @statut === "publiees" - if @statut === "publiees"
= render partial: "procedures_list", locals: { procedures: @procedures_publiees } = render partial: "procedures_list", locals: { procedures: @procedures_publiees }
= paginate @procedures_publiees = paginate @procedures_publiees

View file

@ -10,10 +10,11 @@ en:
organisation: Service organisation: Service
duree_conservation_dossiers_dans_ds: Duration files will be kept duree_conservation_dossiers_dans_ds: Duration files will be kept
max_duree_conservation_dossiers_dans_ds: Max duration allowed to keep files max_duree_conservation_dossiers_dans_ds: Max duration allowed to keep files
aasm_state/brouillon: Draft aasm_state:
aasm_state/publiee: Published brouillon: Draft
aasm_state/close: Close publiee: Published
aasm_state/hidden: Destroyed close: Closed
hidden: Destroyed
declarative_with_state/en_instruction: Instruction declarative_with_state/en_instruction: Instruction
declarative_with_state/accepte: Accepted declarative_with_state/accepte: Accepted
api_particulier_token: Token API Particulier api_particulier_token: Token API Particulier

View file

@ -10,10 +10,11 @@ fr:
organisation: Organisme organisation: Organisme
duree_conservation_dossiers_dans_ds: Durée de conservation des dossiers sur demarches-simplifiees.fr (choisi par un usager) duree_conservation_dossiers_dans_ds: Durée de conservation des dossiers sur demarches-simplifiees.fr (choisi par un usager)
max_duree_conservation_dossiers_dans_ds: Durée de conservation des dossiers maximum (autorisé par un super admin de DS) max_duree_conservation_dossiers_dans_ds: Durée de conservation des dossiers maximum (autorisé par un super admin de DS)
aasm_state/brouillon: Brouillon aasm_state:
aasm_state/publiee: Publiée brouillon: Brouillon
aasm_state/close: Close publiee: Publiée
aasm_state/hidden: Suprimée close: Close
hidden: Supprimée
declarative_with_state/en_instruction: En instruction declarative_with_state/en_instruction: En instruction
declarative_with_state/accepte: Accepté declarative_with_state/accepte: Accepté
api_particulier_token: Jeton API Particulier api_particulier_token: Jeton API Particulier

View file

@ -3,7 +3,8 @@
:time => { :time => {
:formats => { :formats => {
:message_date => lambda { |time, _| "%B #{time.day.ordinalize} at %H:%M" }, :message_date => lambda { |time, _| "%B #{time.day.ordinalize} at %H:%M" },
:message_date_with_year => lambda { |time, _| "%B #{time.day.ordinalize} %Y at %H:%M" } :message_date_with_year => lambda { |time, _| "%B #{time.day.ordinalize} %Y at %H:%M" },
:message_date_without_time => lambda { |_time, _| "%Y/%m/%d" }
} }
} }
} }

View file

@ -3,7 +3,8 @@
:time => { :time => {
:formats => { :formats => {
:message_date => lambda { |time, _| "le #{time.day == 1 ? '1er' : time.day} %B à %H h %M" }, :message_date => lambda { |time, _| "le #{time.day == 1 ? '1er' : time.day} %B à %H h %M" },
:message_date_with_year => lambda { |time, _| "le #{time.day == 1 ? '1er' : time.day} %B %Y à %H h %M" } :message_date_with_year => lambda { |time, _| "le #{time.day == 1 ? '1er' : time.day} %B %Y à %H h %M" },
:message_date_without_time => lambda { |_time, _| "%d/%m/%Y" }
} }
} }
} }

View file

@ -428,6 +428,7 @@ Rails.application.routes.draw do
collection do collection do
get 'new_from_existing' get 'new_from_existing'
post 'search' post 'search'
get 'all'
end end
member do member do

View file

@ -87,6 +87,58 @@ describe Administrateurs::ProceduresController, type: :controller do
it { expect(subject.status).to eq(200) } it { expect(subject.status).to eq(200) }
end end
describe 'GET #all' do
let!(:draft_procedure) { create(:procedure) }
let!(:published_procedure) { create(:procedure_with_dossiers, :published, dossiers_count: 2) }
let!(:closed_procedure) { create(:procedure, :closed) }
subject { get :all }
it { expect(subject.status).to eq(200) }
it 'display published or closed procedures' do
subject
expect(assigns(:procedures)).to include(published_procedure)
expect(assigns(:procedures)).to include(closed_procedure)
end
it 'doesnt display draft procedures' do
subject
expect(assigns(:procedures)).not_to include(draft_procedure)
end
context "for specific zones" do
let(:zone1) { create(:zone) }
let(:zone2) { create(:zone) }
let!(:procedure1) { create(:procedure, :published, zones: [zone1]) }
let!(:procedure2) { create(:procedure, :published, zones: [zone1, zone2]) }
subject { get :all, params: { zone_ids: [zone2.id] } }
it 'display only procedures for specified zones' do
subject
expect(assigns(:procedures)).to include(procedure2)
expect(assigns(:procedures)).not_to include(procedure1)
end
end
context 'for specific status' do
let!(:procedure1) { create(:procedure, :published) }
let!(:procedure2) { create(:procedure, :closed) }
it 'display only published procedures' do
get :all, params: { statuses: ['publiee'] }
expect(assigns(:procedures)).to include(procedure1)
expect(assigns(:procedures)).not_to include(procedure2)
end
it 'display only closed procedures' do
get :all, params: { statuses: ['close'] }
expect(assigns(:procedures)).to include(procedure2)
expect(assigns(:procedures)).not_to include(procedure1)
end
end
end
describe 'POST #search' do describe 'POST #search' do
before do before do
stub_const("Administrateurs::ProceduresController::SIGNIFICANT_DOSSIERS_THRESHOLD", 2) stub_const("Administrateurs::ProceduresController::SIGNIFICANT_DOSSIERS_THRESHOLD", 2)

View file

@ -213,4 +213,15 @@ describe Administrateur, type: :model do
it { is_expected.to be_empty } it { is_expected.to be_empty }
end end
end end
describe 'zones' do
let(:admin) { create(:administrateur) }
let(:zone1) { create(:zone) }
let(:zone2) { create(:zone) }
let!(:procedure) { create(:procedure, administrateurs: [admin], zones: [zone1, zone2]) }
it 'return zones of procedures that the admin is associated' do
expect(admin.zones).to eq [zone1, zone2]
end
end
end end