Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1123] Feature/search functionality in plan list view #484

Merged
merged 3 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/open_inwoner/js/components/autosumbit/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
class Autosubmit {
constructor(form) {
this.form = form
inputs = form.querySelectorAll('input')
selects = form.querySelectorAll('select')

inputs.forEach((input) => {
input.addEventListener('input', this.handle)
})
selects.forEach((select) => {
select.addEventListener('change', this.handle)
})
Expand Down
5 changes: 5 additions & 0 deletions src/open_inwoner/plans/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ class PlanListFilterForm(forms.ModelForm):
status = forms.ChoiceField(
label=_("Status"), choices=PlanStatusChoices.choices, required=False
)
query = forms.CharField(
max_length=150,
required=False,
widget=forms.TextInput(attrs={"placeholder": _("Zoeken")}),
)

class Meta:
model = Plan
Expand Down
77 changes: 60 additions & 17 deletions src/open_inwoner/plans/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ def test_plan_list_renders_expected_data(self):
self.begeleider_plan.save()

response = self.app.get(self.list_url, user=self.begeleider)
rendered_plan_title = response.pyquery("tbody .table__header")[0].text
rendered_plan_title = response.pyquery("tbody .table__header")[0].text_content()
items = response.pyquery("tbody .table__item")
rendered_contact = items[0].text
rendered_end_date = items[1].text
Expand All @@ -607,7 +607,7 @@ def test_plan_list_renders_expected_data(self):
"tbody .table__item--notification-danger"
)[0].text

self.assertEqual(rendered_plan_title, self.begeleider_plan.title)
self.assertIn(self.begeleider_plan.title, rendered_plan_title)
self.assertEqual(rendered_contact, self.contact.get_full_name())
self.assertEqual(rendered_end_date, "20-01-2022")
self.assertEqual(rendered_plan_status, _("Open"))
Expand Down Expand Up @@ -736,15 +736,15 @@ def test_plan_list_filters_status_open(self):
user=self.begeleider,
)
rows = response.pyquery("tbody tr")
rendered_plan_title = response.pyquery("tbody .table__header")[0].text
rendered_plan_title = response.pyquery("tbody .table__header")[0].text_content()
contact_name = response.pyquery("tbody .table__item")[0].text

self.assertEqual(
response.context["plans"]["plan_list"],
{self.begeleider_plan: self.contact.get_full_name()},
)
self.assertEqual(len(rows), 1)
self.assertEqual(rendered_plan_title, self.begeleider_plan.title)
self.assertIn(self.begeleider_plan.title, rendered_plan_title)
self.assertEqual(contact_name, self.contact.get_full_name())

@freeze_time("2022-01-01")
Expand All @@ -761,15 +761,15 @@ def test_plan_list_filters_status_closed(self):
user=self.begeleider,
)
rows = response.pyquery("tbody tr")
rendered_plan_title = response.pyquery("tbody .table__header")[0].text
rendered_plan_title = response.pyquery("tbody .table__header")[0].text_content()
contact_name = response.pyquery("tbody .table__item")[0].text

self.assertEqual(
response.context["plans"]["plan_list"],
{self.begeleider_plan: self.contact.get_full_name()},
)
self.assertEqual(len(rows), 1)
self.assertEqual(rendered_plan_title, self.begeleider_plan.title)
self.assertIn(self.begeleider_plan.title, rendered_plan_title)
self.assertEqual(contact_name, self.contact.get_full_name())

@freeze_time("2022-01-01")
Expand Down Expand Up @@ -805,10 +805,10 @@ def test_plans_sorting(self):
rendered_titles = response.pyquery("tbody .table__header")

self.assertEqual(len(rows), 4)
self.assertEqual(rendered_titles[0].text, open_plan1.title)
self.assertEqual(rendered_titles[1].text, open_plan2.title)
self.assertEqual(rendered_titles[2].text, closed_plan1.title)
self.assertEqual(rendered_titles[3].text, closed_plan2.title)
self.assertIn(open_plan1.title, rendered_titles[0].text_content())
self.assertIn(open_plan2.title, rendered_titles[1].text_content())
self.assertIn(closed_plan1.title, rendered_titles[2].text_content())
self.assertIn(closed_plan2.title, rendered_titles[3].text_content())

@freeze_time("2022-01-01")
def test_plans_sorting_with_filtering_status_open(self):
Expand Down Expand Up @@ -846,8 +846,8 @@ def test_plans_sorting_with_filtering_status_open(self):
rendered_titles = response.pyquery("tbody .table__header")

self.assertEqual(len(rows), 2)
self.assertEqual(rendered_titles[0].text, open_plan1.title)
self.assertEqual(rendered_titles[1].text, open_plan2.title)
self.assertIn(open_plan1.title, rendered_titles[0].text_content())
self.assertIn(open_plan2.title, rendered_titles[1].text_content())

@freeze_time("2022-01-01")
def test_plans_sorting_with_filtering_status_closed(self):
Expand Down Expand Up @@ -879,8 +879,8 @@ def test_plans_sorting_with_filtering_status_closed(self):
rendered_titles = response.pyquery("tbody .table__header")

self.assertEqual(len(rows), 2)
self.assertEqual(rendered_titles[0].text, closed_plan1.title)
self.assertEqual(rendered_titles[1].text, closed_plan2.title)
self.assertIn(closed_plan1.title, rendered_titles[0].text_content())
self.assertIn(closed_plan2.title, rendered_titles[1].text_content())

def test_plans_sorting_with_filtering_contacts(self):
self.begeleider_plan.delete()
Expand Down Expand Up @@ -918,9 +918,52 @@ def test_plans_sorting_with_filtering_contacts(self):
rendered_titles = response.pyquery("tbody .table__header")

self.assertEqual(len(rows), 3)
self.assertEqual(rendered_titles[0].text, open_plan1.title)
self.assertEqual(rendered_titles[1].text, closed_plan1.title)
self.assertEqual(rendered_titles[2].text, closed_plan2.title)
self.assertIn(open_plan1.title, rendered_titles[0].text_content())
self.assertIn(closed_plan1.title, rendered_titles[1].text_content())
self.assertIn(closed_plan2.title, rendered_titles[2].text_content())

def test_search_returns_expected_plans_when_matched_with_plan_contact(self):
self.begeleider.first_name = "expected_first_name"
self.begeleider.last_name = "expected_last_name"
self.begeleider.save()

another_contact = UserFactory(
first_name="expected_first_name", last_name="expected_last_name"
)
another_plan = PlanFactory(created_by=self.begeleider)
another_plan.plan_contacts.add(self.begeleider, another_contact)

response = self.app.get(
f"{self.list_url}?query=expected",
user=self.begeleider,
)

# response should not contain self.begeleider_plan because we don't search
# in plan_contacts=creator
self.assertEqual(
response.context["plans"]["plan_list"],
{another_plan: another_contact.get_full_name()},
)

def test_search_returns_expected_plans_when_matched_with_plan_title(self):
self.begeleider_plan.title = "expected_title"
self.begeleider_plan.save()

another_plan = PlanFactory(
created_by=self.begeleider, title="another_expected_title"
)
another_plan.plan_contacts.add(self.begeleider)

response = self.app.get(
f"{self.list_url}?query=expected",
user=self.begeleider,
)

# response should contain both plans
self.assertEqual(
response.context["plans"]["plan_list"],
{self.begeleider_plan: self.contact.get_full_name(), another_plan: ""},
)


@multi_browser()
Expand Down
26 changes: 17 additions & 9 deletions src/open_inwoner/plans/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.http import Http404, HttpResponseRedirect
from django.urls.base import reverse
from django.utils.functional import cached_property
Expand Down Expand Up @@ -39,23 +40,34 @@ def dispatch(self, request, *args, **kwargs):

class BasePlanFilter:
"""
This will filter the plans according to the user's selection.
This will filter the plans according to the user's selection or query.
"""

def get_filtered_plans(self, plans):
def get_filtered_plans(self, plans, available_contacts):
plan_contacts = self.request.GET.get("plan_contacts")
status = self.request.GET.get("status")
query = self.request.GET.get("query")
today = date.today()

if not (plan_contacts or status or query):
return plans

if plan_contacts:
plans = plans.filter(plan_contacts__uuid=plan_contacts)
if status:
if status == "open":
plans = plans.filter(end_date__gt=today)
elif status == "closed":
plans = plans.filter(end_date__lte=today)
if query:
available_contacts = available_contacts.filter(
Q(first_name__icontains=query) | Q(last_name__icontains=query)
)
plans = plans.filter(
Q(title__icontains=query) | Q(plan_contacts__in=available_contacts)
)

return plans
return plans.distinct()


class PlanListView(
Expand Down Expand Up @@ -106,12 +118,9 @@ def get_context_data(self, **kwargs):
# render the extended plan list view when a begeleider is logged in
if user.contact_type == ContactTypeChoices.begeleider:
plans["extended_plans"] = True
available_contacts = self.get_available_contacts_for_filtering(initial_qs)

# filter plans if necessary
if "plan_contacts" in self.request.GET or "status" in self.request.GET:
filtered_plans = self.get_filtered_plans(initial_qs)
else:
filtered_plans = initial_qs
filtered_plans = self.get_filtered_plans(initial_qs, available_contacts)

# sort the filtered plans based on if they are open or closed
open_plans = filtered_plans.filter(end_date__gt=date.today())
Expand All @@ -126,7 +135,6 @@ def get_context_data(self, **kwargs):
)

# instantiate filter form
available_contacts = self.get_available_contacts_for_filtering(initial_qs)
context["plan_filter_form"] = PlanListFilterForm(
data=self.request.GET, available_contacts=available_contacts
)
Expand Down
16 changes: 15 additions & 1 deletion src/open_inwoner/scss/components/Plan/PlanList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,21 @@
@media (min-width: 768px) {
display: grid;
gap: var(--spacing-giant);
grid-template-columns: 1fr 2fr 2fr 1fr;
grid-template-columns: 1fr 3fr 3fr 3fr 0.5fr 0.5fr;
}
}

&__search-container {
display: flex;
border: 1px solid var(--color-mute);
border-radius: var(--border-radius);

.input {
border: none;
}

.form__actions {
flex-grow: 1;
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/open_inwoner/templates/pages/plans/list.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends 'master.html' %}
{% load i18n button_tags string_tags pagination_tags helpers card_tags form_tags %}
{% load i18n button_tags string_tags pagination_tags helpers card_tags form_tags link_tags %}

{% block content %}
<h1 class="h1">
Expand All @@ -18,6 +18,10 @@ <h1 class="h1">
<p class="p">{% trans "Filter op:" %}</p>
{% input plan_filter_form.plan_contacts no_label=True no_help=True icon="person" %}
{% input plan_filter_form.status no_label=True no_help=True icon="expand_more" %}
<div class="plan-filter__search-container">
{% input plan_filter_form.query no_label=True no_help=True %}
{% form_actions primary_icon="search" single=True transparent=True %}
</div>
{% button text=_("Reset filters") href="plans:plan_list" icon="undo" transparent=True icon_position="before" %}
</div>
</div>
Expand All @@ -37,7 +41,7 @@ <h1 class="h1">
<tbody>
{% for plan, plan_participants in plans.plan_list.items %}
<tr>
<th class="table__header">{{plan.title}}</th>
<th class="table__header">{% link href=plan.get_absolute_url text=plan.title %}</th>
<td class="table__item">{{plan_participants|truncatechars:30}}</td>
<td class="table__item table__item--no-lb">{{plan.end_date|date:"d-m-Y"}}</td>
<td class="table__item">{{plan.get_status}}</td>
Expand All @@ -47,7 +51,7 @@ <h1 class="h1">
<div class="table__item--notification-danger">{% trans "Actie vereist" %}</div>
{% endif %}
</td>
<td class="table__item">{% button text="" href=plan.get_absolute_url icon="arrow_forward" icon_outlined=True transparent=True %}</td>
<td class="table__item">{% button text=plan.title hide_text=True href=plan.get_absolute_url icon="arrow_forward" icon_outlined=True transparent=True %}</td>
</tr>
{% empty %}
<tr>
Expand Down