From e5e40a12b2301fae701b3407ff2e9afe98d9ed63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Gaya?= <sebastien.gaya@gmail.com> Date: Mon, 7 Feb 2022 10:42:59 +0100 Subject: [PATCH] filter users --- Gemfile | 1 + Gemfile.lock | 4 ++ app/controllers/admin/users_controller.rb | 6 ++- app/models/user.rb | 10 +++++ app/services/filters/base.rb | 39 +++++++++++++++++ app/services/filters/user.rb | 9 ++++ app/views/admin/application/_filters.html.erb | 43 +++++++++++++++++++ app/views/admin/users/index.html.erb | 3 ++ config/locales/en.yml | 9 ++++ config/locales/fr.yml | 9 ++++ .../20220207093702_add_unaccent_extension.rb | 5 +++ db/schema.rb | 3 +- 12 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 app/services/filters/base.rb create mode 100644 app/services/filters/user.rb create mode 100644 app/views/admin/application/_filters.html.erb create mode 100644 db/migrate/20220207093702_add_unaccent_extension.rb diff --git a/Gemfile b/Gemfile index 7342df7bc..ff36280ae 100644 --- a/Gemfile +++ b/Gemfile @@ -37,6 +37,7 @@ gem 'two_factor_authentication', git: 'https://github.com/noesya/two_factor_auth # gem 'two_factor_authentication', path: '../two_factor_authentication' gem 'curation'#, path: '../../arnaudlevy/curation' gem "cocoon", "~> 1.2" +gem "has_scope", "~> 0.8.0" # Front gem 'jquery-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 164634b66..cb51e670c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -204,6 +204,9 @@ GEM sassc-rails globalid (1.0.0) activesupport (>= 5.0) + has_scope (0.8.0) + actionpack (>= 5.2) + activesupport (>= 5.2) http-cookie (1.0.4) domain_name (~> 0.5) i18n (1.9.1) @@ -437,6 +440,7 @@ DEPENDENCIES figaro front_matter_parser gdpr + has_scope (~> 0.8.0) image_processing jbuilder jquery-rails diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 1d00d143e..36cb5ed0b 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,8 +1,12 @@ class Admin::UsersController < Admin::ApplicationController load_and_authorize_resource through: :current_university + has_scope :for_role + has_scope :for_search_term + def index - @users = @users.ordered.page(params[:page]) + @filters = ::Filters::User.new(current_user).list + @users = apply_scopes(@users).ordered.page(params[:page]) breadcrumb end diff --git a/app/models/user.rb b/app/models/user.rb index 602fa6c6f..e5d355cea 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -63,6 +63,16 @@ class User < ApplicationRecord belongs_to :language scope :ordered, -> { order(:last_name, :first_name) } + scope :for_search_term, -> (term) { + where(" + unaccent(concat(users.first_name, ' ', users.last_name)) ILIKE unaccent(:term) OR + unaccent(concat(users.last_name, ' ', users.first_name)) ILIKE unaccent(:term) OR + unaccent(users.first_name) ILIKE unaccent(:term) OR + unaccent(users.last_name) ILIKE unaccent(:term) OR + unaccent(users.email) ILIKE unaccent(:term) OR + unaccent(users.mobile_phone) ILIKE unaccent(:term) + ", term: "%#{sanitize_sql_like(term)}%") + } def to_s "#{first_name} #{last_name}" diff --git a/app/services/filters/base.rb b/app/services/filters/base.rb new file mode 100644 index 000000000..b3a1a5674 --- /dev/null +++ b/app/services/filters/base.rb @@ -0,0 +1,39 @@ +module Filters + class Base + attr_accessor :list + + def initialize(user) + @user = user + @list = [] + end + + protected + + def add(scope_name, choices, label, multiple = false) + @list << { + scope_name: scope_name, + choices: choices, + label: label, + multiple: multiple + } + end + + def add_if(condition, args) + add *args if condition + end + + def add_search + add :for_search_term, nil, I18n.t('search') + end + + def add_date_filter(objects, attribute) + dates = objects.map { |obj| + { + to_s: I18n.l(obj[attribute], format: "%B %Y"), + id: I18n.l(obj[attribute], format: "%Y-%m") + } + }.uniq + add :for_date, dates, t('filters.attributes.date') + end + end +end diff --git a/app/services/filters/user.rb b/app/services/filters/user.rb new file mode 100644 index 000000000..a71e11eff --- /dev/null +++ b/app/services/filters/user.rb @@ -0,0 +1,9 @@ +module Filters + class User < Base + def initialize(user) + super + add_search + add :for_role, ::User.roles.keys.map { |r| { to_s: r.humanize, id: r } }, I18n.t('filters.attributes.role') + end + end +end diff --git a/app/views/admin/application/_filters.html.erb b/app/views/admin/application/_filters.html.erb new file mode 100644 index 000000000..4c2238c74 --- /dev/null +++ b/app/views/admin/application/_filters.html.erb @@ -0,0 +1,43 @@ +<% +collapsable = true if collapsable.nil? # not ||= because collapsable can be "false" +should_be_open = false +filters.each { |filter| should_be_open = true if params.has_key?(filter[:scope_name]) } +%> +<div class="row"> + <% if collapsable %> + <div class="col-md-2"> + <a class="btn btn-primary" + data-bs-toggle="collapse" + href="#collapseFilters" + role="button" + aria-expanded="false" + aria-controls="collapseFilters"> + <%= t('filters.buttons.expand') %> + </a> + </div> + <% end %> + <div class="col-md-<%= collapsable ? '10' : '12' %>"> + <div class="collapse <%= (!collapsable || should_be_open) ? 'show' : '' %>" id="collapseFilters"> + <%= form_tag current_path, method: :get, class: 'do-not-unlock' do |f| %> + <div class="row"> + <div class="col-md-4"> + <% filters.each do |filter| %> + <% if filter[:scope_name] == :for_search_term %> + <%= text_field_tag filter[:scope_name], params[filter[:scope_name]], placeholder: filter[:label], class: 'form-control mb-1 filter' %> + <% else %> + <% choices = filter[:choices].map { |elmt| elmt.is_a?(String) ? [elmt, elmt] : [elmt.is_a?(Hash) ? elmt[:to_s] : elmt.to_s, elmt[:id]] } %> + <% field_name = filter[:multiple] ? "#{filter[:scope_name]}[]" : filter[:scope_name] %> + <%= select_tag field_name, options_for_select(choices, params[filter[:scope_name]]), include_blank: filter[:label], class: 'form-select mb-1 filter' %> + <% end %> + <% end %> + </div> + <div class="col-md-3"> + <%= submit_tag t('filters.buttons.submit'), class: 'btn btn-primary btn-submit' %> + <%= link_to t('reset'), current_path, class: 'btn btn-warning' %> + </div> + </div> + <% end %> + </div> + </div> +</div> +<br> diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index b161dcbcd..af80ff5b3 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -1,5 +1,8 @@ <% content_for :title, "#{User.model_name.human(count: 2)} (#{@users.total_count})" %> +<%= render 'admin/application/filters', + current_path: admin_users_path, + filters: @filters if @filters.any? %> <table class="table"> <thead> diff --git a/config/locales/en.yml b/config/locales/en.yml index 9153a8cc3..e535a3d13 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -88,6 +88,13 @@ en: edit: Edit empty_folder: Empty folder false: No + filters: + attributes: + date: Filter by Date + role: Filter by Role + buttons: + expand: Filter table + submit: Filter gdpr: privacy_policy: https://osuny.org/politique-de-confidentialite hello: "Hello %{name}!" @@ -113,7 +120,9 @@ en: privacy_policy_url: https://osuny.org/politique-de-confidentialite quit: Quit remove: Remove + reset: Reset save: Save + search: Search select_language: Select language simple_form: error_notification: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ca2c06384..f16346f8d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -88,6 +88,13 @@ fr: edit: Modifier empty_folder: Dossier vide false: Non + filters: + attributes: + date: Filtrer par Date + role: Filtrer par Rôle + buttons: + expand: Filtrer le tableau + submit: Filtrer gdpr: privacy_policy: https://osuny.org/politique-de-confidentialite hello: "Bonjour %{name} !" @@ -113,7 +120,9 @@ fr: privacy_policy_url: https://osuny.org/politique-de-confidentialite quit: Quitter remove: Retirer + reset: Réinitialiser save: Enregistrer + search: Rechercher select_language: Sélectionnez une langue simple_form: error_notification: diff --git a/db/migrate/20220207093702_add_unaccent_extension.rb b/db/migrate/20220207093702_add_unaccent_extension.rb new file mode 100644 index 000000000..f1d34afad --- /dev/null +++ b/db/migrate/20220207093702_add_unaccent_extension.rb @@ -0,0 +1,5 @@ +class AddUnaccentExtension < ActiveRecord::Migration[6.1] + def change + enable_extension "unaccent" + end +end diff --git a/db/schema.rb b/db/schema.rb index 69faf1dd0..6fa4a6e61 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,12 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_02_03_160802) do +ActiveRecord::Schema.define(version: 2022_02_07_093702) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" + enable_extension "unaccent" create_table "action_text_rich_texts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "name", null: false -- GitLab