Skip to content

Commit

Permalink
Merge pull request #2166 from rhian-cs/1071-add-filter-for-case-contacts
Browse files Browse the repository at this point in the history
[#1071] Add filters for case contacts
  • Loading branch information
compwron authored Jun 23, 2021
2 parents e7d3428 + aa5a8b6 commit 697edc4
Show file tree
Hide file tree
Showing 11 changed files with 538 additions and 43 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ gem "skylight" # automated performance testing https://www.skylight.io/
gem "webpacker", "~> 5.4" # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem "image_processing", "~> 1.12" # Set of higher-level helper methods for image processing.
gem "lograge" # log less so heroku papertrail quits rate limiting our logs
gem "filterrific" # filtering and sorting of models

gem "bootsnap", ">= 1.4.2", require: false # Reduces boot times through caching; required in config/boot.rb
gem "bugsnag" # tracking errors in prod
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ GEM
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
filterrific (5.2.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
html_tokenizer (0.0.7)
Expand Down Expand Up @@ -450,6 +451,7 @@ DEPENDENCIES
erb_lint
factory_bot_rails
faker
filterrific
image_processing (~> 1.12)
jbuilder (~> 2.11)
letter_opener
Expand Down
29 changes: 28 additions & 1 deletion app/controllers/case_contacts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@ class CaseContactsController < ApplicationController

def index
authorize CaseContact
@presenter = CaseContactPresenter.new

@current_organization_groups = current_organization_groups

@filterrific = initialize_filterrific(
all_case_contacts,
params[:filterrific],
select_options: {
sorted_by: CaseContact.options_for_sorted_by
}
) || return

case_contacts = @filterrific.find.group_by(&:casa_case_id)

@presenter = CaseContactPresenter.new(case_contacts)
end

def new
Expand Down Expand Up @@ -133,4 +146,18 @@ def update_case_contact_params
.new(params)
.with_converted_duration_minutes(params[:case_contact][:duration_hours].to_i)
end

def current_organization_groups
current_organization.contact_type_groups
.joins(:contact_types)
.where(contact_types: {active: true})
.uniq
end

def all_case_contacts
policy_scope(
current_organization.case_contacts.grab_all(current_user)
.includes(:creator, contact_types: :contact_type_group)
)
end
end
79 changes: 76 additions & 3 deletions app/models/case_contact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,35 @@ class CaseContact < ApplicationRecord
scope :occurred_between, ->(start_date = nil, end_date = nil) {
where("occurred_at BETWEEN ? AND ?", start_date, end_date) if start_date.present? && end_date.present?
}
scope :occurred_starting_at, ->(start_date = nil) {
where("occurred_at >= ?", start_date) if start_date.present?
}
scope :occurred_ending_at, ->(end_date = nil) {
where("occurred_at <= ?", end_date) if end_date.present?
}
scope :contact_made, ->(contact_made = nil) {
where(contact_made: contact_made) if contact_made == true || contact_made == false
where(contact_made: contact_made) if /true|false/.match?(contact_made.to_s)
}
scope :has_transitioned, ->(has_transitioned = nil) {
joins(:casa_case).where(casa_cases: {transition_aged_youth: has_transitioned}) if has_transitioned == true || has_transitioned == false
if /true|false/.match?(has_transitioned.to_s)
joins(:casa_case).where(casa_cases: {transition_aged_youth: has_transitioned})
end
}
scope :want_driving_reimbursement, ->(want_driving_reimbursement = nil) {
where(want_driving_reimbursement: want_driving_reimbursement) if want_driving_reimbursement == true || want_driving_reimbursement == false
if /true|false/.match?(want_driving_reimbursement.to_s)
where(want_driving_reimbursement: want_driving_reimbursement)
end
}
scope :contact_type, ->(contact_type_ids = nil) {
includes(:contact_types).where("contact_types.id": [contact_type_ids]) if contact_type_ids.present?
}
scope :contact_types, ->(contact_type_id_list = nil) {
contact_type_id_list.reject! { |id| id.blank? }

return if contact_type_id_list.blank?

includes(:contact_types).where("contact_types.id": contact_type_id_list)
}
scope :contact_type_groups, ->(contact_type_group_ids = nil) {
# to handle case when passing ids == [''] && ids == nil
if contact_type_group_ids&.join&.length&.positive?
Expand All @@ -64,6 +81,41 @@ class CaseContact < ApplicationRecord
with_deleted if current_user.is_a?(CasaAdmin)
}

scope :contact_medium, ->(medium_type) {
where(medium_type: medium_type) if medium_type.present?
}

scope :sorted_by, ->(sort_option) {
direction = /desc$/.match?(sort_option) ? "desc" : "asc"

case sort_option.to_s
when /^occurred_at/
order(occurred_at: direction)
when /^contact_type/
joins(:contact_types).order(name: direction)
when /^medium_type/
order(medium_type: direction)
when /^want_driving_reimbursement/
order(want_driving_reimbursement: direction)
when /^contact_made/
order(contact_made: direction)
else
raise(ArgumentError, "Invalid sort option: #{sort_option.inspect}")
end
}

filterrific(
available_filters: [
:sorted_by,
:occurred_starting_at,
:occurred_ending_at,
:contact_type,
:contact_made,
:contact_medium,
:want_driving_reimbursement
]
)

IN_PERSON = "in-person".freeze
TEXT_EMAIL = "text/email".freeze
VIDEO = "video".freeze
Expand Down Expand Up @@ -125,6 +177,27 @@ def contact_groups_with_types
def requested_followup
followups.requested.first
end

def self.options_for_sorted_by
sorted_by_params.map do |option|
[I18n.t("models.case_contact.options_for_sorted_by.#{option}"), option]
end
end

private_class_method def self.sorted_by_params
%i[
occurred_at_asc
occurred_at_desc
contact_type_asc
contact_type_desc
medium_type_asc
medium_type_desc
want_driving_reimbursement_asc
want_driving_reimbursement_desc
contact_made_asc
contact_made_desc
]
end
end

# == Schema Information
Expand Down
29 changes: 12 additions & 17 deletions app/presenters/case_contact_presenter.rb
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
class CaseContactPresenter < BasePresenter
def casa_cases
@casa_cases ||= policy_scope(org_cases).group_by(&:id).transform_values(&:first)
end
attr_reader :case_contacts
attr_reader :casa_cases

def case_contacts
@case_contacts ||= case_contact_with_deleted.sort_by do |contact|
[contact.casa_case_id, Time.current - contact.occurred_at]
end.group_by(&:casa_case_id)
def initialize(case_contacts)
@case_contacts = case_contacts
@casa_cases = policy_scope(org_cases).group_by(&:id).transform_values(&:first)
end

def display_case_number(casa_case_id)
"#{casa_cases[casa_case_id].decorate.transition_aged_youth_icon} #{casa_cases[casa_case_id].case_number}"
end

private

def case_contact_with_deleted
policy_scope(
current_organization.case_contacts
.grab_all(current_user)
.includes(:creator, contact_types: :contact_type_group)
)
.order("contact_types.id asc")
.decorate
def boolean_select_options
[
[I18n.t("common.yes_text"), true],
[I18n.t("common.no_text"), false]
]
end

private

def org_cases
CasaOrg.includes(:casa_cases)
.references(:casa_cases)
Expand Down
111 changes: 109 additions & 2 deletions app/views/case_contacts/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,113 @@
</div>
</div>

<%= form_for_filterrific @filterrific do |f| %>
<div class="card my-4">
<div class="card-header d-flex justify-content-between align-items-end">
<h3 class="d-inline"><%= t(".filter.title") %></h3>
<button href="#"
class="btn btn-lg btn-link"
type="button"
data-toggle="collapse"
data-target="#filter-card-body"
aria-expanded="<%= params[:filterrific].present? %>>"
aria-controls="filter-card-body">
<%= t(".filter.show_hide") %>
</button>
</div>

<% collapse_class = params[:filterrific] ? "" : "collapse" %>
<div class="card-body <%= collapse_class %>" id="filter-card-body">
<div class="row mb-4">
<div class="col-12">
<h3><label><%= t(".filter.contact_date") %></label></h3>
</div>
<div class="col-sm-6">
<%= f.label t(".filter.start_date"), for: :occurred_starting_at %>
<%= f.text_field(:occurred_starting_at, data: {provide: "datepicker", date_format: "yyyy/mm/dd"},
class: "form-control") %>
</div>
<div class="col-sm-6">
<%= f.label t(".filter.end_date"), for: :occurred_until %>
<%= f.text_field(:occurred_ending_at, data: {provide: "datepicker", date_format: "yyyy/mm/dd"},
class: "form-control") %>
</div>
</div>

<div class="row mb-4">
<div class="col-12">
<h3><label><%= t(".filter.contact_types") %></label></h3>

<div id="contact-type-form" class="field contact-type form-group">
<div class="row">
<% @current_organization_groups.each do |group| %>
<div class="col-md-4 justify-content-start mb-4">
<h5> <%= group.name %> </h5>
<% group.contact_types.each do |contact_type| %>
<div class="form-check">
<%=
f.check_box :contact_type,
{multiple: true, class: "form-check-input case-contact-contact-type"},
contact_type.id,
nil
%>
<label class="form-check-label" for="filterrific_contact_type_<%= contact_type.id %>">
<%= contact_type.name %>
</label>
</div>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
</div>

<div class="row mb-4 align-items-end">
<div class="col-12">
<h3><label><%= t(".filter.other") %></label></h3>
</div>

<div class="col-md-6">
<%= f.label :contact_medium %>
<%= f.select(:contact_medium, options_from_collection_for_select(contact_mediums, "value", "label"),
{include_blank: t(".filter.display_all")},
{class: "form-control"}) %>
</div>
<div class="col-md-3">
<%= f.label :want_driving_reimbursement %>
<%= f.select(:want_driving_reimbursement, @presenter.boolean_select_options,
{include_blank: t(".filter.display_all")},
{class: "form-control"}) %>
</div>

<div class="col-md-3">
<%= f.label :contact_made %>
<%= f.select(:contact_made, @presenter.boolean_select_options,
{include_blank: t(".filter.display_all")},
{class: "form-control"}) %>
</div>
</div>

<div class="row">
<div class="col-md-6">
<%= f.label :sorted_by %>
<%= f.select(:sorted_by, @filterrific.select_options[:sorted_by], {}, {class: "form-control"}) %>
</div>
</div>

<div class="mt-4 btn-group">
<%= f.submit(t(".filter.submit"), class: "btn btn-primary") %>
<%= link_to(
t(".filter.reset"),
reset_filterrific_url,
class: "btn btn-outline-primary"
) %>
</div>
</div>
</div>
<% end %>

<% @presenter.case_contacts.each do |casa_case_id, data| %>
<div class="card card-container">
<div class="card-body">
Expand All @@ -13,11 +120,11 @@
</div>
</div>
<% end %>

<% unless @presenter.case_contacts.any? %>
<div class="card card-container">
<div class="card-body">
You have no case contacts for this case. Please click New Case Contact button above to create a case contact for
your youth!
<%= params[:filterrific] ? t('.no_contacts_found') : t('.no_contacts_present') %>
</div>
</div>
<% end %>
14 changes: 14 additions & 0 deletions config/locales/models.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
en:
models:
case_contact:
options_for_sorted_by:
occurred_at_asc: Date of contact (oldest first)
occurred_at_desc: Date of contact (newest first)
contact_type_asc: Contact type (A-z)
contact_type_desc: Contact type (z-A)
medium_type_asc: Contact medium (A-z)
medium_type_desc: Contact medium (z-A)
want_driving_reimbursement_asc: Want driving reimbursement ('no' first)
want_driving_reimbursement_desc: Want driving reimbursement ('yes' first)
contact_made_asc: Contact made ('no' first)
contact_made_desc: Contact made ('yes' first)
13 changes: 13 additions & 0 deletions config/locales/views.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ en:
index:
title: Case Contacts
new: New Case Contact
filter:
title: Filter by
show_hide: Show / Hide
submit: Filter
reset: Reset filters
display_all: Display all
contact_types: Contact types
other: Other filters
contact_date: Date of contact
start_date: Starting from
end_date: Ending at
no_contacts_found: No case contacts have been found.
no_contacts_present: You have no case contacts for this case. Please click New Case Contact button above to create a case contact for your youth!
edit:
title: Editing Case Contact
new:
Expand Down
Loading

0 comments on commit 697edc4

Please sign in to comment.