From ce0752addb100d412bc85fa8f7d12efc095c72bf Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 7 Apr 2022 15:29:17 +0100 Subject: [PATCH] Cldc 1010 filter (#449) * Add a filter for status * Add styling, separate filter tab into a file * Add mobile styling * use session instead of cookies * Get statuses from case_log * remove explicit builder set * set filter from within get index * style new log button * refactor tests --- app/controllers/case_logs_controller.rb | 16 ++- app/frontend/assets/images/icon-cross.svg | 3 + app/frontend/controllers/filter_controller.js | 16 +++ app/frontend/controllers/index.js | 3 + app/frontend/styles/_filter.scss | 123 ++++++++++++++++++ app/frontend/styles/application.scss | 11 +- app/helpers/filters_helper.rb | 14 ++ app/models/case_log.rb | 1 + app/views/case_logs/_log_filters.erb | 32 +++++ app/views/case_logs/_log_list.html.erb | 3 +- app/views/case_logs/index.html.erb | 31 +++-- spec/helpers/filters_helper_spec.rb | 26 ++++ spec/requests/case_logs_controller_spec.rb | 35 +++++ 13 files changed, 302 insertions(+), 12 deletions(-) create mode 100644 app/frontend/assets/images/icon-cross.svg create mode 100644 app/frontend/controllers/filter_controller.js create mode 100644 app/frontend/styles/_filter.scss create mode 100644 app/helpers/filters_helper.rb create mode 100644 app/views/case_logs/_log_filters.erb create mode 100644 spec/helpers/filters_helper_spec.rb diff --git a/app/controllers/case_logs_controller.rb b/app/controllers/case_logs_controller.rb index 03000dfca..767e33046 100644 --- a/app/controllers/case_logs_controller.rb +++ b/app/controllers/case_logs_controller.rb @@ -7,7 +7,9 @@ class CaseLogsController < ApplicationController before_action :find_resource, except: %i[create index edit] def index - @pagy, @case_logs = pagy(current_user.case_logs) + set_session_filters if params[:status].present? + + @pagy, @case_logs = pagy(filtered_case_logs) respond_to do |format| format.html @@ -117,4 +119,16 @@ private def find_resource @case_log = CaseLog.find_by(id: params[:id]) end + + def filtered_case_logs + user_case_logs = current_user.case_logs + status_filter = JSON.parse(session[:case_logs_filters])["status"] if session[:case_logs_filters].present? + return user_case_logs unless status_filter + + user_case_logs.filter_by_status(status_filter) + end + + def set_session_filters + session[:case_logs_filters] = { status: params[:status] }.to_json + end end diff --git a/app/frontend/assets/images/icon-cross.svg b/app/frontend/assets/images/icon-cross.svg new file mode 100644 index 000000000..645525f8c --- /dev/null +++ b/app/frontend/assets/images/icon-cross.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/frontend/controllers/filter_controller.js b/app/frontend/controllers/filter_controller.js new file mode 100644 index 000000000..ec55b72b5 --- /dev/null +++ b/app/frontend/controllers/filter_controller.js @@ -0,0 +1,16 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + toggleFilter() { + let filter_panel = document.getElementById("filter-panel"); + let toggle_filter_button = document.getElementById("toggle-filter-button"); + + if (filter_panel.style.display === "none" || !filter_panel.style.display) { + filter_panel.style.display = "block"; + toggle_filter_button.innerText = "Hide filters"; + } else { + filter_panel.style.display = "none"; + toggle_filter_button.innerText = "Show filters"; + } + } +} diff --git a/app/frontend/controllers/index.js b/app/frontend/controllers/index.js index ee3be6037..2227bea58 100644 --- a/app/frontend/controllers/index.js +++ b/app/frontend/controllers/index.js @@ -14,3 +14,6 @@ application.register("govukfrontend", GovukfrontendController) import NumericQuestionController from "./numeric_question_controller.js" application.register("numeric-question", NumericQuestionController) + +import FilterController from "./filter_controller.js" +application.register("filter", FilterController) diff --git a/app/frontend/styles/_filter.scss b/app/frontend/styles/_filter.scss new file mode 100644 index 000000000..46783534f --- /dev/null +++ b/app/frontend/styles/_filter.scss @@ -0,0 +1,123 @@ +.app-filter { + background-color: govuk-colour("light-grey"); + margin-bottom: govuk-spacing(2); +} + +.app-filter__header { + background-color: govuk-colour("light-grey"); + display: flex; + justify-content: space-between; + padding: govuk-spacing(2) govuk-spacing(3); + position: sticky; + top: 0; + z-index: 10; + + @include govuk-media-query($from: desktop) { + position: static; + } + + [class^="govuk-heading-"] { + margin-bottom: 0; + } +} + +.app-filter__content { + padding: govuk-spacing(1) govuk-spacing(3) govuk-spacing(4); +} + +.app-filter__close { + @include govuk-font(19); + + -webkit-appearance: none; + background-color: transparent; + border: none; + border-radius: 0; + color: $govuk-text-colour; + cursor: pointer; + margin: #{govuk-spacing(1) * -1}; + padding: govuk-spacing(1); + + &:focus { + background-color: $govuk-focus-colour; + color: $govuk-focus-text-colour; + box-shadow: 0 -2px $govuk-focus-colour, 0 4px $govuk-focus-text-colour; + outline: none; + } + + // Fix unwanted button padding in Firefox + &::-moz-focus-inner { + padding: 0; + border: 0; + } + + &::before { + background-image: url("../assets/images/icon-cross.svg"); + content: ""; + display: inline-block; + height: 14px; + margin-right: govuk-spacing(1); + position: relative; + top: -2px; // Alignment tweak + vertical-align: middle; + width: 14px; + } + + @include govuk-media-query(desktop) { + display: none; + } +} + +.app-filter-layout { + @include govuk-clearfix; +} + +.app-filter-toggle__button { + min-width: 128px; + @include govuk-media-query(desktop) { + display: none !important; + } +} + +.app-filter-layout__filter { + @include govuk-media-query(desktop) { + float: left; + min-width: govuk-grid-width("one-quarter"); + } + display: none; + + @include govuk-media-query(desktop) { + display: block; + } +} + +.js-enabled .app-filter-layout__filter { + outline: 0 none; + + @include govuk-media-query($until: desktop) { + background-color: govuk-colour("light-grey"); + bottom: govuk-spacing(1); + border: 1px solid govuk-colour("mid-grey"); + max-width: 310px; + min-width: 260px; + width: 100%; + overflow-y: scroll; + position: fixed; + right: govuk-spacing(1); + top: govuk-spacing(1); + z-index: 100; + + &:focus { + outline: $govuk-focus-width solid $govuk-focus-colour; + } + } +} + +.app-filter-layout__content { + @include govuk-media-query(desktop) { + float: right; + max-width: calc( + #{govuk-grid-width("three-quarters")} - #{govuk-spacing(6)} + ); + width: 100%; + } +} diff --git a/app/frontend/styles/application.scss b/app/frontend/styles/application.scss index a89fa2cce..3d01074ec 100644 --- a/app/frontend/styles/application.scss +++ b/app/frontend/styles/application.scss @@ -1,9 +1,9 @@ @function frontend-font-url($filename) { - @return url("~assets/fonts/" + $filename); + @return url("~assets/fonts/"+$filename); } @function frontend-image-url($filename) { - @return url("~assets/images/" + $filename); + @return url("~assets/images/"+$filename); } $govuk-font-url-function: frontend-font-url; @@ -24,6 +24,7 @@ $govuk-global-styles: true; @import "template"; @import "pagination"; @import "panel"; +@import "filter"; // App utilities .app-\!-colour-muted { @@ -42,3 +43,9 @@ $govuk-global-styles: true; .govuk-tag { white-space: nowrap; } + +.button_to { + @include govuk-media-query($until: tablet) { + width: 100%; + } +} diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb new file mode 100644 index 000000000..aaee74fb6 --- /dev/null +++ b/app/helpers/filters_helper.rb @@ -0,0 +1,14 @@ +module FiltersHelper + def filter_selected?(filter) + return true unless session[:case_logs_filters] + + selected_filters = JSON.parse(session[:case_logs_filters]) + selected_filters["status"].present? && selected_filters["status"].include?(filter.to_s) + end + + def status_filters + statuses = {} + CaseLog.statuses.keys.map { |status| statuses[status] = status.humanize } + statuses + end +end diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 6b602362f..aa926d5b4 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -34,6 +34,7 @@ class CaseLog < ApplicationRecord belongs_to :managing_organisation, class_name: "Organisation" scope :for_organisation, ->(org) { where(owning_organisation: org).or(where(managing_organisation: org)) } + scope :filter_by_status, ->(status) { where status: status } AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze OPTIONAL_FIELDS = %w[postcode_known la_known first_time_property_let_as_social_housing tenant_code propcode].freeze diff --git a/app/views/case_logs/_log_filters.erb b/app/views/case_logs/_log_filters.erb new file mode 100644 index 000000000..4e5593a62 --- /dev/null +++ b/app/views/case_logs/_log_filters.erb @@ -0,0 +1,32 @@ +
+
+
+

Filters

+ +
+
+
+ <%= form_with url: "/logs", html: { method: :get } do |f| %> + <%= f.govuk_check_boxes_fieldset :status, legend: { text: "Status", size: "s"} do %> +
+ <% statuses = status_filters %> + <% statuses.map do |key, option| %> + <%= f.govuk_check_box "status", "#{key}", + label: { text: option }, + checked: filter_selected?(key), + size: "s" %> + <% end %> +
+ <% end %> + <%= f.govuk_submit "Apply filters", class: "govuk-!-margin-top-4" %> + <% end %> +
+
+
+
diff --git a/app/views/case_logs/_log_list.html.erb b/app/views/case_logs/_log_list.html.erb index 3cb30289f..a2fe5645c 100644 --- a/app/views/case_logs/_log_list.html.erb +++ b/app/views/case_logs/_log_list.html.erb @@ -51,7 +51,7 @@ <%= log.managing_organisation.name %> - + <% end %> <% end %> @@ -59,3 +59,4 @@ + diff --git a/app/views/case_logs/index.html.erb b/app/views/case_logs/index.html.erb index b1d40c173..8060f3c99 100644 --- a/app/views/case_logs/index.html.erb +++ b/app/views/case_logs/index.html.erb @@ -3,13 +3,28 @@

<%= content_for(:title) %>

- -
- <%= govuk_button_to "Create a new lettings log", case_logs_path %> - <%#= govuk_link_to "Upload logs", bulk_upload_case_logs_path %> +
+
+ + <%= govuk_button_to "Create a new lettings log", case_logs_path %> + <%#= govuk_link_to "Upload logs", bulk_upload_case_logs_path %> +
+ + <%= render partial: "log_filters"%> + <% if @case_logs.present? %> +
+ <%= render partial: "log_list", locals: { case_logs: @case_logs, title: "Logs", pagy: @pagy } %> + <%== render partial: 'pagy/nav', locals: { pagy: @pagy, item_name: "logs" } %> +
+ <% end %>
-<% if @case_logs.present? %> - <%= render partial: "log_list", locals: { case_logs: @case_logs, title: "Logs", pagy: @pagy } %> - <%== render partial: 'pagy/nav', locals: { pagy: @pagy, item_name: "logs" } %> -<% end %> diff --git a/spec/helpers/filters_helper_spec.rb b/spec/helpers/filters_helper_spec.rb new file mode 100644 index 000000000..d79f8dfcd --- /dev/null +++ b/spec/helpers/filters_helper_spec.rb @@ -0,0 +1,26 @@ +require "rails_helper" + +RSpec.describe FiltersHelper do + describe "#filter_selected?" do + context "when no filters are selected" do + it "returns true for all filters" do + expect(filter_selected?("completed")).to be_truthy + expect(filter_selected?("in_progress")).to be_truthy + end + end + + context "when one filter is selected" do + before do + session[:case_logs_filters] = { "status": "in_progress" }.to_json + end + + it "returns false for non selected filters" do + expect(filter_selected?("completed")).to be_falsey + end + + it "returns true for selected filter" do + expect(filter_selected?("in_progress")).to be_truthy + end + end + end +end diff --git a/spec/requests/case_logs_controller_spec.rb b/spec/requests/case_logs_controller_spec.rb index 0526ee7ed..1337cd650 100644 --- a/spec/requests/case_logs_controller_spec.rb +++ b/spec/requests/case_logs_controller_spec.rb @@ -172,6 +172,41 @@ RSpec.describe CaseLogsController, type: :request do expect(page).to have_content("Owning organisation") expect(page).to have_content("Managing organisation") end + + context "when filtering" do + let!(:in_progress_case_log) do + FactoryBot.create(:case_log, :in_progress, + owning_organisation: organisation, + managing_organisation: organisation) + end + let!(:completed_case_log) do + FactoryBot.create(:case_log, :completed, + owning_organisation: organisation, + managing_organisation: organisation) + end + + it "shows case logs for multiple selected statuses" do + get "/logs?status[]=in_progress&status[]=completed", headers: headers, params: {} + expect(page).to have_link(in_progress_case_log.id.to_s) + expect(page).to have_link(completed_case_log.id.to_s) + end + + it "shows case logs for one selected status" do + get "/logs?status[]=in_progress", headers: headers, params: {} + expect(page).to have_link(in_progress_case_log.id.to_s) + expect(page).not_to have_link(completed_case_log.id.to_s) + end + + it "does not reset the filters" do + get "/logs?status[]=in_progress", headers: headers, params: {} + expect(page).to have_link(in_progress_case_log.id.to_s) + expect(page).not_to have_link(completed_case_log.id.to_s) + + get "/logs", headers: headers, params: {} + expect(page).to have_link(in_progress_case_log.id.to_s) + expect(page).not_to have_link(completed_case_log.id.to_s) + end + end end context "when the user is not a customer support user" do