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)
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
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/search/search.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,
hide,
toggle,
toggleExpandIcon,
isSelectElement,
isTextInputElement,
isCheckboxOrRadioInputElement
@ -34,6 +35,18 @@ suite('@utils', () => {
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', () => {
const select = document.createElement('select');
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(
el: HTMLSelectElement | HTMLInputElement | HTMLButtonElement | null
) {

View file

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

View file

@ -14,7 +14,7 @@ class Zone < ApplicationRecord
has_and_belongs_to_many :procedures, -> { order(published_at: :desc) }, inverse_of: :zone
def current_label
labels.first.name
labels.where.not(name: 'Non attribué').first.name
end
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
.procedure-admin-listing-container
= link_to "Nouvelle Démarche", new_from_existing_admin_procedures_path, id: 'new-procedure', class: 'fr-btn'
.container
.fr-container
%nav.tabs
%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.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"
= render partial: "procedures_list", locals: { procedures: @procedures_publiees }
= paginate @procedures_publiees

View file

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

View file

@ -10,10 +10,11 @@ fr:
organisation: Organisme
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)
aasm_state/brouillon: Brouillon
aasm_state/publiee: Publiée
aasm_state/close: Close
aasm_state/hidden: Suprimée
aasm_state:
brouillon: Brouillon
publiee: Publiée
close: Close
hidden: Supprimée
declarative_with_state/en_instruction: En instruction
declarative_with_state/accepte: Accepté
api_particulier_token: Jeton API Particulier

View file

@ -3,7 +3,8 @@
:time => {
:formats => {
: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 => {
:formats => {
: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
get 'new_from_existing'
post 'search'
get 'all'
end
member do

View file

@ -87,6 +87,58 @@ describe Administrateurs::ProceduresController, type: :controller do
it { expect(subject.status).to eq(200) }
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
before do
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 }
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