feat(instructeur/procedure#show): enhance sort by notifications as planned by UX
Update app/javascript/controllers/checkbox_controller.ts Co-authored-by: Paul Chavard <github@paul.chavard.net>
This commit is contained in:
parent
400bc5207d
commit
aceb8996c1
13 changed files with 137 additions and 10 deletions
|
@ -103,6 +103,11 @@
|
||||||
border-color: $blue-france-500;
|
border-color: $blue-france-500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix/dsfr
|
||||||
|
.fr-checkbox-group.fix-dsfr-notified-toggle-component {
|
||||||
|
margin-top: -7px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.revision-changes {
|
ul.revision-changes {
|
||||||
|
|
|
@ -18,6 +18,18 @@
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visually-hidden {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
// text
|
// text
|
||||||
.text-center,
|
.text-center,
|
||||||
.center {
|
.center {
|
||||||
|
|
41
app/components/dossiers/notified_toggle_component.rb
Normal file
41
app/components/dossiers/notified_toggle_component.rb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
class Dossiers::NotifiedToggleComponent < ApplicationComponent
|
||||||
|
def initialize(procedure:, procedure_presentation:)
|
||||||
|
@procedure = procedure
|
||||||
|
@procedure_presentation = procedure_presentation
|
||||||
|
@current_sort = procedure_presentation.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def opposite_order
|
||||||
|
@procedure_presentation.opposite_order_for(current_table, current_column)
|
||||||
|
end
|
||||||
|
|
||||||
|
def active?
|
||||||
|
sorted_by_notifications? && order_asc?
|
||||||
|
end
|
||||||
|
|
||||||
|
def icon_class_name
|
||||||
|
active? ? 'fr-fi-checkbox' : 'fr-fi-checkbox-blank'
|
||||||
|
end
|
||||||
|
|
||||||
|
def order_asc?
|
||||||
|
current_order == 'asc'
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_order
|
||||||
|
@current_sort['order']
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_table
|
||||||
|
@current_sort['table']
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_column
|
||||||
|
@current_sort['column']
|
||||||
|
end
|
||||||
|
|
||||||
|
def sorted_by_notifications?
|
||||||
|
current_table == 'notifications' && current_column == 'notifications'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
en:
|
||||||
|
show_notified_first: Show files with notification first
|
|
@ -0,0 +1,2 @@
|
||||||
|
fr:
|
||||||
|
show_notified_first: Remonter les dossiers avec une notification
|
|
@ -0,0 +1,7 @@
|
||||||
|
= form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, table: 'notifications', column: 'notifications', order: opposite_order), method: 'GET', data: {controller: 'checkbox'} do
|
||||||
|
.fr-form-group
|
||||||
|
.fr-fieldset__content
|
||||||
|
.fr-checkbox-group.fix-dsfr-notified-toggle-component
|
||||||
|
= check_box_tag :order, opposite_order, active?, data: {action: 'change->checkbox#onChange'}
|
||||||
|
= label_tag :order, t('.show_notified_first'), class: 'fr-label'
|
||||||
|
= submit_tag t('.show_notified_first'), data: {"checkbox-target": 'submit' }, class: 'visually-hidden'
|
|
@ -121,7 +121,7 @@ module Instructeurs
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_sort
|
def update_sort
|
||||||
procedure_presentation.update_sort(params[:table], params[:column])
|
procedure_presentation.update_sort(params[:table], params[:column], params[:order])
|
||||||
|
|
||||||
redirect_back(fallback_location: instructeur_procedure_url(procedure))
|
redirect_back(fallback_location: instructeur_procedure_url(procedure))
|
||||||
end
|
end
|
||||||
|
|
8
app/javascript/controllers/checkbox_controller.ts
Normal file
8
app/javascript/controllers/checkbox_controller.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { ApplicationController } from './application_controller';
|
||||||
|
|
||||||
|
export class CheckboxController extends ApplicationController {
|
||||||
|
onChange() {
|
||||||
|
const form = this.element as HTMLFormElement;
|
||||||
|
form.requestSubmit();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
@import '@gouvfr/dsfr/dist/component/connect/connect.css';
|
@import '@gouvfr/dsfr/dist/component/connect/connect.css';
|
||||||
@import '@gouvfr/dsfr/dist/component/highlight/highlight.css';
|
@import '@gouvfr/dsfr/dist/component/highlight/highlight.css';
|
||||||
@import '@gouvfr/dsfr/dist/component/input/input.css';
|
@import '@gouvfr/dsfr/dist/component/input/input.css';
|
||||||
|
@import '@gouvfr/dsfr/dist/component/checkbox/checkbox.css';
|
||||||
@import '@gouvfr/dsfr/dist/component/logo/logo.css';
|
@import '@gouvfr/dsfr/dist/component/logo/logo.css';
|
||||||
@import '@gouvfr/dsfr/dist/component/modal/modal.css';
|
@import '@gouvfr/dsfr/dist/component/modal/modal.css';
|
||||||
@import '@gouvfr/dsfr/dist/component/navigation/navigation.css';
|
@import '@gouvfr/dsfr/dist/component/navigation/navigation.css';
|
||||||
|
|
|
@ -18,6 +18,8 @@ class ProcedurePresentation < ApplicationRecord
|
||||||
|
|
||||||
TABLE = 'table'
|
TABLE = 'table'
|
||||||
COLUMN = 'column'
|
COLUMN = 'column'
|
||||||
|
ORDER = 'order'
|
||||||
|
|
||||||
SLASH = '/'
|
SLASH = '/'
|
||||||
TYPE_DE_CHAMP = 'type_de_champ'
|
TYPE_DE_CHAMP = 'type_de_champ'
|
||||||
TYPE_DE_CHAMP_PRIVATE = 'type_de_champ_private'
|
TYPE_DE_CHAMP_PRIVATE = 'type_de_champ_private'
|
||||||
|
@ -286,18 +288,20 @@ class ProcedurePresentation < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_sort(table, column)
|
def update_sort(table, column, order)
|
||||||
order = if sort.values_at(TABLE, COLUMN) == [table, column]
|
update!(sort: {
|
||||||
|
TABLE => table,
|
||||||
|
COLUMN => column,
|
||||||
|
ORDER => opposite_order_for(table, column)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def opposite_order_for(table, column)
|
||||||
|
if sort.values_at(TABLE, COLUMN) == [table, column]
|
||||||
sort['order'] == 'asc' ? 'desc' : 'asc'
|
sort['order'] == 'asc' ? 'desc' : 'asc'
|
||||||
else
|
else
|
||||||
'asc'
|
'asc'
|
||||||
end
|
end
|
||||||
|
|
||||||
update!(sort: {
|
|
||||||
TABLE => table,
|
|
||||||
COLUMN => column,
|
|
||||||
'order' => order
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def snapshot
|
def snapshot
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
%th{ class: classname }
|
%th{ class: classname }
|
||||||
= link_to update_sort_instructeur_procedure_path(@procedure, table: field['table'], column: field['column']) do
|
= link_to update_sort_instructeur_procedure_path(@procedure, table: field['table'], column: field['column'], order: @procedure_presentation.opposite_order_for(field['table'], field['column'])) do
|
||||||
- if @procedure_presentation.sort['table'] == field['table'] && @procedure_presentation.sort['column'] == field['column']
|
- if @procedure_presentation.sort['table'] == field['table'] && @procedure_presentation.sort['column'] == field['column']
|
||||||
- if @procedure_presentation.sort['order'] == 'asc'
|
- if @procedure_presentation.sort['order'] == 'asc'
|
||||||
#{field['label']} ↑
|
#{field['label']} ↑
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
.flex
|
.flex
|
||||||
.flex-grow
|
.flex-grow
|
||||||
= render partial: "dossiers_filter", locals: { procedure: @procedure, procedure_presentation: @procedure_presentation, current_filters: @current_filters, statut: @statut, filterable_fields_for_select: @filterable_fields_for_select }
|
= render partial: "dossiers_filter", locals: { procedure: @procedure, procedure_presentation: @procedure_presentation, current_filters: @current_filters, statut: @statut, filterable_fields_for_select: @filterable_fields_for_select }
|
||||||
|
.flex-grow
|
||||||
|
= render Dossiers::NotifiedToggleComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation)
|
||||||
- if @dossiers_count > 0
|
- if @dossiers_count > 0
|
||||||
.dossiers-export
|
.dossiers-export
|
||||||
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, export_url: method(:download_export_instructeur_procedure_path))
|
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, export_url: method(:download_export_instructeur_procedure_path))
|
||||||
|
|
43
spec/system/instructeurs/procedure_sort_spec.rb
Normal file
43
spec/system/instructeurs/procedure_sort_spec.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
describe "procedure sort" do
|
||||||
|
let(:instructeur) { create(:instructeur) }
|
||||||
|
let(:procedure) { create(:procedure, :published, :with_type_de_champ, instructeurs: [instructeur]) }
|
||||||
|
let!(:new_unfollow_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) }
|
||||||
|
let!(:followed_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) }
|
||||||
|
let!(:new_unfollow_dossier_2) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
instructeur.follow(followed_dossier)
|
||||||
|
followed_dossier.champs.first.update(value: '123')
|
||||||
|
|
||||||
|
login_as(instructeur.user, scope: :user)
|
||||||
|
visit instructeur_procedure_path(procedure)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "should be able to sort with header" do
|
||||||
|
all(".dossiers-table tbody tr:nth-child(1) .number-col a", text: new_unfollow_dossier_2.id)
|
||||||
|
all(".dossiers-table tbody tr:nth-child(2) .number-col a", text: followed_dossier.id)
|
||||||
|
all(".dossiers-table tbody tr:nth-child(3) .number-col a", text: new_unfollow_dossier.id)
|
||||||
|
|
||||||
|
find("thead .number-col a").click # reverse id filter
|
||||||
|
|
||||||
|
all(".dossiers-table tbody tr:nth-child(1) .number-col a", text: new_unfollow_dossier.id)
|
||||||
|
all(".dossiers-table tbody tr:nth-child(2) .number-col a", text: followed_dossier.id)
|
||||||
|
all(".dossiers-table tbody tr:nth-child(3) .number-col a", text: new_unfollow_dossier_2.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "should be able to sort with direct link to notificaiton filter" do
|
||||||
|
# dossier sorted by id
|
||||||
|
check "Remonter les dossiers avec une notification"
|
||||||
|
|
||||||
|
# sort by notification
|
||||||
|
all(".dossiers-table tbody tr:nth-child(1) .number-col a", text: followed_dossier.id)
|
||||||
|
all(".dossiers-table tbody tr:nth-child(2) .number-col a", text: new_unfollow_dossier.id)
|
||||||
|
all(".dossiers-table tbody tr:nth-child(3) .number-col a", text: new_unfollow_dossier_2.id)
|
||||||
|
|
||||||
|
uncheck "Remonter les dossiers avec une notification"
|
||||||
|
|
||||||
|
all(".dossiers-table tbody tr:nth-child(1) .number-col a", text: new_unfollow_dossier_2.id)
|
||||||
|
all(".dossiers-table tbody tr:nth-child(2) .number-col a", text: followed_dossier.id)
|
||||||
|
all(".dossiers-table tbody tr:nth-child(3) .number-col a", text: new_unfollow_dossier.id)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue