functionality done

This commit is contained in:
Rachid Mrad 2024-09-10 13:16:41 -04:00
parent c11a282f27
commit b036b70112
No known key found for this signature in database
4 changed files with 258 additions and 28 deletions

View file

@ -1166,7 +1166,7 @@ document.addEventListener('DOMContentLoaded', function() {
const resetSearchButton = document.querySelector('.domains__reset-search');
const resetFiltersButton = document.querySelector('.domains__reset-filters');
const statusCheckboxes = document.querySelectorAll('input[name="filter-status"]');
const statusIndicator = document.querySelector('.domain__filter-indicator');
const statusIndicator = document.querySelector('.filter-indicator');
const statusToggle = document.querySelector('.usa-button--filter');
const portfolioElement = document.getElementById('portfolio-js-value');
const portfolioValue = portfolioElement ? portfolioElement.getAttribute('data-portfolio') : null;
@ -1419,10 +1419,10 @@ document.addEventListener('DOMContentLoaded', function() {
function updateStatusIndicator() {
statusIndicator.innerHTML = '';
// Even if the element is empty, it'll mess up the flex layout unless we set display none
statusIndicator.hideElement();
hideElement(statusIndicator);
if (currentStatus.length)
statusIndicator.innerHTML = '(' + currentStatus.length + ')';
statusIndicator.showElement();
showElement(statusIndicator);
}
function closeFilters() {
@ -1436,9 +1436,9 @@ document.addEventListener('DOMContentLoaded', function() {
// NOTE: We may need to evolve this as we add more filters.
document.addEventListener('focusin', function(event) {
const accordion = document.querySelector('.usa-accordion--select');
const accordionIsOpen = document.querySelector('.usa-button--filter[aria-expanded="true"]');
const openFilterAccordion = document.querySelector('.usa-button--filter[aria-expanded="true"]');
if (accordionIsOpen && !accordion.contains(event.target)) {
if (openFilterAccordion && !accordion.contains(event.target)) {
closeFilters();
}
});
@ -1447,9 +1447,9 @@ document.addEventListener('DOMContentLoaded', function() {
// NOTE: We may need to evolve this as we add more filters.
document.addEventListener('click', function(event) {
const accordion = document.querySelector('.usa-accordion--select');
const accordionIsOpen = document.querySelector('.usa-button--filter[aria-expanded="true"]');
const openFilterAccordion = document.querySelector('.usa-button--filter[aria-expanded="true"]');
if (accordionIsOpen && !accordion.contains(event.target)) {
if (openFilterAccordion && !accordion.contains(event.target)) {
closeFilters();
}
});
@ -1488,14 +1488,17 @@ document.addEventListener('DOMContentLoaded', function() {
const noDomainRequestsWrapper = document.querySelector('.domain-requests__no-data');
const noSearchResultsWrapper = document.querySelector('.domain-requests__no-search-results');
let scrollToTable = false;
let currentStatus = [];
let currentSearchTerm = '';
const domainRequestsSearchInput = document.getElementById('domain-requests__search-field');
const domainRequestsSearchSubmit = document.getElementById('domain-requests__search-field-submit');
const tableHeaders = document.querySelectorAll('.domain-requests__table th[data-sortable]');
const tableAnnouncementRegion = document.querySelector('.domain-requests__table-wrapper .usa-table__announcement-region');
const resetSearchButton = document.querySelector('.domain-requests__reset-search');
const portfolioElement = document.getElementById('portfolio-js-value');
const portfolioValue = portfolioElement ? portfolioElement.getAttribute('data-portfolio') : null;
const resetFiltersButton = document.querySelector('.domains__reset-filters');
const statusCheckboxes = document.querySelectorAll('input[name="filter-status"]');
const statusIndicator = document.querySelector('.filter-indicator');
const statusToggle = document.querySelector('.usa-button--filter');
const portfolioElement = document.getElementById('portfolio-js-value');
const portfolioValue = portfolioElement ? portfolioElement.getAttribute('data-portfolio') : null;
@ -1537,7 +1540,6 @@ document.addEventListener('DOMContentLoaded', function() {
return document.querySelector('input[name="csrfmiddlewaretoken"]').value;
}
let currentStatus = []
/**
* Loads rows in the domain requests list, as well as updates pagination around the domain requests list
* based on the supplied attributes.
@ -1547,7 +1549,7 @@ document.addEventListener('DOMContentLoaded', function() {
* @param {*} scroll - control for the scrollToElement functionality
* @param {*} searchTerm - the search term
*/
function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, scroll = scrollToTable, searchTerm = currentSearchTerm, status = currentStatus, portfolio = portfolioValue, portfolio = portfolioValue) {
function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, scroll = scrollToTable, searchTerm = currentSearchTerm, status = currentStatus, portfolio = portfolioValue) {
// fetch json of page of domain requests, given params
let baseUrl = document.getElementById("get_domain_requests_json_url");
if (!baseUrl) {
@ -1882,6 +1884,44 @@ document.addEventListener('DOMContentLoaded', function() {
resetHeaders();
});
if (statusToggle) {
statusToggle.addEventListener('click', function() {
toggleCaret(statusToggle);
});
}
// Add event listeners to status filter checkboxes
statusCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
const checkboxValue = this.value;
// Update currentStatus array based on checkbox state
if (this.checked) {
currentStatus.push(checkboxValue);
} else {
const index = currentStatus.indexOf(checkboxValue);
if (index > -1) {
currentStatus.splice(index, 1);
}
}
// Manage visibility of reset filters button
if (currentStatus.length == 0) {
hideElement(resetFiltersButton);
} else {
showElement(resetFiltersButton);
}
// Disable the auto scroll
scrollToTable = false;
// Call loadDomains with updated status
loadDomainRequests(1, 'id', 'asc');
resetHeaders();
updateStatusIndicator();
});
});
// Reset UI and accessibility
function resetHeaders() {
tableHeaders.forEach(header => {
@ -1906,34 +1946,85 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
function closeMoreActionMenu(accordionIsOpen) {
if (accordionIsOpen.getAttribute("aria-expanded") === "true") {
accordionIsOpen.click();
function closeMoreActionMenu(openFilterAccordion) {
if (openFilterAccordion.getAttribute("aria-expanded") === "true") {
openFilterAccordion.click();
}
}
function resetFilters() {
currentStatus = [];
statusCheckboxes.forEach(checkbox => {
checkbox.checked = false;
});
hideElement(resetFiltersButton);
// Disable the auto scroll
scrollToTable = false;
loadDomainRequests(1, 'id', 'asc');
resetHeaders();
updateStatusIndicator();
// No need to toggle close the filters. The focus shift will trigger that for us.
}
if (resetFiltersButton) {
resetFiltersButton.addEventListener('click', function() {
resetFilters();
});
}
function updateStatusIndicator() {
statusIndicator.innerHTML = '';
// Even if the element is empty, it'll mess up the flex layout unless we set display none
hideElement(statusIndicator);
if (currentStatus.length)
statusIndicator.innerHTML = '(' + currentStatus.length + ')';
showElement(statusIndicator);
}
function closeFilters() {
if (statusToggle.getAttribute("aria-expanded") === "true") {
statusToggle.click();
}
}
document.addEventListener('focusin', function(event) {
const accordions = document.querySelectorAll('.usa-accordion--more-actions');
const openAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]');
const openMoreActionsAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]');
openAccordions.forEach((openAccordionButton) => {
const accordion = openAccordionButton.closest('.usa-accordion--more-actions'); // Find the corresponding accordion
if (accordion && !accordion.contains(event.target)) {
closeMoreActionMenu(openAccordionButton); // Close the accordion if the focus is outside
openMoreActionsAccordions.forEach((openMoreActionsAccordionButton) => {
const moreActionsAccordion = openMoreActionsAccordionButton.closest('.usa-accordion--more-actions'); // Find the corresponding accordion
if (moreActionsAccordion && !moreActionsAccordion.contains(event.target)) {
closeMoreActionMenu(openMoreActionsAccordionButton); // Close the accordion if the focus is outside
}
});
const openFilterAccordion = document.querySelector('.usa-button--filter[aria-expanded="true"]');
const moreFilterAccordion = openFilterAccordion ? openFilterAccordion.closest('.usa-accordion--select') : undefined;
if (openFilterAccordion) {
if (!moreFilterAccordion.contains(event.target)) {
closeFilters();
}
}
});
document.addEventListener('click', function(event) {
const accordions = document.querySelectorAll('.usa-accordion--more-actions');
const openAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]');
const openMoreActionsAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]');
openAccordions.forEach((openAccordionButton) => {
const accordion = openAccordionButton.closest('.usa-accordion--more-actions'); // Find the corresponding accordion
openMoreActionsAccordions.forEach((openMoreActionsAccordionButton) => {
const accordion = openMoreActionsAccordionButton.closest('.usa-accordion--more-actions'); // Find the corresponding accordion
if (accordion && !accordion.contains(event.target)) {
closeMoreActionMenu(openAccordionButton); // Close the accordion if the click is outside
closeMoreActionMenu(openMoreActionsAccordionButton); // Close the accordion if the click is outside
}
});
const openFilterAccordion = document.querySelector('.usa-button--filter[aria-expanded="true"]');
const moreFilterAccordion = openFilterAccordion ? openFilterAccordion.closest('.usa-accordion--select') : undefined;
if (openFilterAccordion && moreFilterAccordion && !moreFilterAccordion.contains(event.target)) {
closeFilters();
}
});
// Initial load

View file

@ -23,7 +23,7 @@
</svg>
Reset
</button>
<label class="usa-sr-only" for="domain-requests__search-field">Search by domain name</label>
<label class="usa-sr-only" for="domain-requests__search-field">Search by domain name or creator</label>
<input
class="usa-input"
id="domain-requests__search-field"
@ -42,6 +42,125 @@
</section>
</div>
</div>
{% if portfolio %}
<div class="display-flex flex-align-center">
<span class="margin-right-2 margin-top-neg-1 usa-prose text-base-darker">Filter by</span>
<div class="usa-accordion usa-accordion--select margin-right-2">
<div class="usa-accordion__heading">
<button
type="button"
class="usa-button usa-button--small padding--8-8-9 usa-button--outline usa-button--filter usa-accordion__button"
aria-expanded="false"
aria-controls="filter-status"
>
<span class="filter-indicator text-bold display-none"></span> Status
<svg class="usa-icon top-2px" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#expand_more"></use>
</svg>
</button>
</div>
<div id="filter-status" class="usa-accordion__content usa-prose shadow-1">
<h2>Status</h2>
<fieldset class="usa-fieldset margin-top-0">
<legend class="usa-legend">Select to apply <span class="sr-only">status</span> filter</legend>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="filter-status-started"
type="checkbox"
name="filter-status"
value="started"
/>
<label class="usa-checkbox__label" for="filter-status-started"
>Started</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="filter-status-submitted"
type="checkbox"
name="filter-status"
value="submitted"
/>
<label class="usa-checkbox__label" for="filter-status-submitted"
>Submitted</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="filter-status-in-review"
type="checkbox"
name="filter-status"
value="in review"
/>
<label class="usa-checkbox__label" for="filter-status-in-review"
>In review</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="filter-status-action-needed"
type="checkbox"
name="filter-status"
value="action needed"
/>
<label class="usa-checkbox__label" for="filter-status-action-needed"
>Action needed</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="filter-status-rejected"
type="checkbox"
name="filter-status"
value="rejected"
/>
<label class="usa-checkbox__label" for="filter-status-rejected"
>Rejected</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="filter-status-withdrawn"
type="checkbox"
name="filter-status"
value="withdrawn"
/>
<label class="usa-checkbox__label" for="filter-status-withdrawn"
>Withdrawn</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="filter-status-ineligible"
type="checkbox"
name="filter-status"
value="ineligible"
/>
<label class="usa-checkbox__label" for="filter-status-ineligible"
>Ineligible</label
>
</div>
</fieldset>
</div>
</div>
<button
type="button"
class="usa-button usa-button--small padding--8-12-9-12 usa-button--outline usa-button--filter domains__reset-filters display-none"
>
Clear filters
<svg class="usa-icon top-1px" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#close"></use>
</svg>
</button>
</div>
{% endif %}
<div class="domain-requests__table-wrapper display-none usa-table-container--scrollable margin-top-0" tabindex="0">
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked domain-requests__table">
<caption class="sr-only">Your domain requests</caption>

View file

@ -64,7 +64,7 @@
aria-expanded="false"
aria-controls="filter-status"
>
<span class="domain__filter-indicator text-bold display-none"></span> Status
<span class="filter-indicator text-bold display-none"></span> Status
<svg class="usa-icon top-2px" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#expand_more"></use>
</svg>

View file

@ -18,6 +18,7 @@ def get_domain_requests_json(request):
unfiltered_total = objects.count()
objects = apply_search(objects, request)
objects = apply_status_filter(objects, request)
objects = apply_sorting(objects, request)
paginator = Paginator(objects, 10)
@ -62,6 +63,7 @@ def get_domain_request_ids_from_request(request):
def apply_search(queryset, request):
search_term = request.GET.get("search_term")
is_portfolio = request.GET.get("portfolio")
if search_term:
search_term_lower = search_term.lower()
@ -73,12 +75,30 @@ def apply_search(queryset, request):
if search_term_lower in new_domain_request_text:
queryset = queryset.filter(
Q(requested_domain__name__icontains=search_term) | Q(requested_domain__isnull=True)
)
)
elif is_portfolio:
queryset = queryset.filter(Q(requested_domain__name__icontains=search_term) | Q(creator__first_name__icontains=search_term) | Q(creator__last_name__icontains=search_term) | Q(creator__email__icontains=search_term))
# For non org users
else:
queryset = queryset.filter(Q(requested_domain__name__icontains=search_term))
return queryset
def apply_status_filter(queryset, request):
status_param = request.GET.get("status")
if status_param:
status_list = status_param.split(",")
statuses = [status for status in status_list if status in DomainRequest.DomainRequestStatus.values]
# Construct Q objects for statuses that can be queried through ORM
status_query = Q()
if statuses:
status_query |= Q(status__in=statuses)
# Apply the combined query
queryset = queryset.filter(status_query)
return queryset
def apply_sorting(queryset, request):
sort_by = request.GET.get("sort_by", "id") # Default to 'id'
order = request.GET.get("order", "asc") # Default to 'asc'