all systems functional

This commit is contained in:
Rachid Mrad 2024-06-21 18:05:52 -04:00
parent 811f06f511
commit 8268779eae
No known key found for this signature in database
7 changed files with 215 additions and 97 deletions

View file

@ -51,6 +51,20 @@ function makeVisible(el) {
el.style.visibility = "visible";
}
/* Toggles expand_more / expand_more svgs in buttons or anchors */
function toggleCaret(element) {
// Get a reference to the use element inside the button
const useElement = element.querySelector('use');
// Check if the span element text is 'Hide'
if (useElement.getAttribute('xlink:href') === '/public/img/sprite.svg#expand_more') {
// Update the xlink:href attribute to expand_more
useElement.setAttribute('xlink:href', '/public/img/sprite.svg#expand_less');
} else {
// Update the xlink:href attribute to expand_less
useElement.setAttribute('xlink:href', '/public/img/sprite.svg#expand_more');
}
}
/** Creates and returns a live region element. */
function createLiveRegion(id) {
const liveRegion = document.createElement("div");
@ -1039,11 +1053,11 @@ function updatePagination(itemName, paginationSelector, counterSelector, headerA
* A helper that toggles content/ no content/ no search results
*
*/
const updateDisplay = (data, dataWrapper, noDataWrapper, noSearchResultsWrapper, searchTermHolder, currentSearchTerm) => {
const updateDisplay = (data, dataWrapper, noDataWrapper, noSearchResultsWrapper) => {
const { unfiltered_total, total } = data;
if (searchTermHolder)
searchTermHolder.innerHTML = '';
// if (searchTermHolder)
// searchTermHolder.innerHTML = '';
if (unfiltered_total) {
if (total) {
@ -1051,8 +1065,8 @@ const updateDisplay = (data, dataWrapper, noDataWrapper, noSearchResultsWrapper,
hideElement(noSearchResultsWrapper);
hideElement(noDataWrapper);
} else {
if (searchTermHolder)
searchTermHolder.innerHTML = currentSearchTerm;
// if (searchTermHolder)
// searchTermHolder.innerHTML = currentSearchTerm;
hideElement(dataWrapper);
showElement(noSearchResultsWrapper);
hideElement(noDataWrapper);
@ -1091,13 +1105,38 @@ document.addEventListener('DOMContentLoaded', function() {
const noDomainsWrapper = document.querySelector('.domains__no-data');
const noSearchResultsWrapper = document.querySelector('.domains__no-search-results');
let hasLoaded = false;
let currentSearchTerm = ''
let currentStatus = [];
let currentSearchTerm = '';
const domainsSearchInput = document.getElementById('domains__search-field');
const domainsSearchSubmit = document.getElementById('domains__search-field-submit');
const tableHeaders = document.querySelectorAll('.domains__table th[data-sortable]');
const tableAnnouncementRegion = document.querySelector('.domains__table-wrapper .usa-table__announcement-region');
const searchTermHolder = document.querySelector('.domains__search-term');
const resetButton = document.querySelector('.domains__reset-button');
// const searchTermHolder = document.querySelector('.domains__search-term');
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 statusToggle = document.querySelector('.usa-button--filter');
// Initialize the filters, but only if we're on the org domains page
const portfolioSidenav = document.querySelector('.usa-sidenav--portfolio');
if (portfolioSidenav) {
statusCheckboxes.forEach(checkbox => {
const checkboxValue = checkbox.value;
// Update currentStatus array based on checkbox state
if (checkbox.checked) {
currentStatus.push(checkboxValue);
} else {
const index = currentStatus.indexOf(checkboxValue);
if (index > -1) {
currentStatus.splice(index, 1);
}
}
});
} else {
// For non-portfolio users, disable filtering
currentStatus = ['unknown', 'ready', 'on hold', 'expired', 'deleted'];
}
/**
* Loads rows in the domains list, as well as updates pagination around the domains list
@ -1108,9 +1147,9 @@ document.addEventListener('DOMContentLoaded', function() {
* @param {*} loaded - control for the scrollToElement functionality
* @param {*} searchTerm - the search term
*/
function loadDomains(page, sortBy = currentSortBy, order = currentOrder, loaded = hasLoaded, searchTerm = currentSearchTerm) {
function loadDomains(page, sortBy = currentSortBy, order = currentOrder, loaded = hasLoaded, status = currentStatus, searchTerm = currentSearchTerm) {
//fetch json of page of domains, given page # and sort
fetch(`/get-domains-json/?page=${page}&sort_by=${sortBy}&order=${order}&search_term=${searchTerm}`)
fetch(`/get-domains-json/?page=${page}&sort_by=${sortBy}&order=${order}&status=${status}&search_term=${searchTerm}`)
.then(response => response.json())
.then(data => {
if (data.error) {
@ -1119,7 +1158,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
// handle the display of proper messaging in the event that no domains exist in the list or search returns no results
updateDisplay(data, domainsWrapper, noDomainsWrapper, noSearchResultsWrapper, searchTermHolder, currentSearchTerm);
updateDisplay(data, domainsWrapper, noDomainsWrapper, noSearchResultsWrapper, currentSearchTerm);
// identify the DOM element where the domain list will be inserted into the DOM
const domainList = document.querySelector('.domains__table tbody');
@ -1209,18 +1248,59 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
domainsSearchInput.addEventListener('focus', function(e) {
console.log('focus');
closeFilters();
});
domainsSearchSubmit.addEventListener('click', function(e) {
e.preventDefault();
currentSearchTerm = domainsSearchInput.value;
// If the search is blank, we match the resetSearch functionality
if (currentSearchTerm) {
showElement(resetButton);
showElement(resetSearchButton);
} else {
hideElement(resetButton);
hideElement(resetSearchButton);
}
loadDomains(1, 'id', 'asc');
resetHeaders();
})
});
statusToggle.addEventListener('click', function() {
console.log('clicked')
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);
}
}
console.log(currentStatus)
if (currentStatus.length == statusCheckboxes.length) {
console.log('fully selected');
hideElement(resetFiltersButton);
} else {
showElement(resetFiltersButton);
}
// Call loadDomains with updated status
loadDomains(1, 'id', 'asc');
resetHeaders();
updateStatusIndicator();
});
});
// Reset UI and accessibility
function resetHeaders() {
@ -1235,17 +1315,48 @@ document.addEventListener('DOMContentLoaded', function() {
function resetSearch() {
domainsSearchInput.value = '';
currentSearchTerm = '';
hideElement(resetButton);
loadDomains(1, 'id', 'asc', hasLoaded, '');
hideElement(resetSearchButton);
loadDomains(1, 'id', 'asc');
resetHeaders();
}
if (resetButton) {
resetButton.addEventListener('click', function() {
if (resetSearchButton) {
resetSearchButton.addEventListener('click', function() {
resetSearch();
});
}
function resetFilters() {
currentStatus = [];
statusCheckboxes.forEach(checkbox => {
checkbox.checked = true;
const checkboxValue = checkbox.value;
currentStatus.push(checkboxValue);
});
hideElement(resetFiltersButton);
loadDomains(1, 'id', 'asc');
resetHeaders();
updateStatusIndicator();
closeFilters();
}
if (resetFiltersButton) {
resetFiltersButton.addEventListener('click', function() {
resetFilters();
});
}
function updateStatusIndicator() {
statusIndicator.innerHTML = '';
statusIndicator.innerHTML = '(' + currentStatus.length + ')';
}
function closeFilters() {
if (statusToggle.getAttribute("aria-expanded") === "true") {
statusToggle.click();
}
}
// Load the first page initially
loadDomains(1);
}
@ -1280,13 +1391,13 @@ document.addEventListener('DOMContentLoaded', function() {
const noDomainRequestsWrapper = document.querySelector('.domain-requests__no-data');
const noSearchResultsWrapper = document.querySelector('.domain-requests__no-search-results');
let hasLoaded = false;
let currentSearchTerm = ''
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 searchTermHolder = document.querySelector('.domain-requests__search-term');
const resetButton = document.querySelector('.domain-requests__reset-button');
// const searchTermHolder = document.querySelector('.domain-requests__search-term');
const resetSearchButton = document.querySelector('.domain-requests__reset-search');
/**
* Delete is actually a POST API that requires a csrf token. The token will be waiting for us in the template as a hidden input.
@ -1342,7 +1453,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
// handle the display of proper messaging in the event that no requests exist in the list or search returns no results
updateDisplay(data, domainRequestsWrapper, noDomainRequestsWrapper, noSearchResultsWrapper, searchTermHolder, currentSearchTerm);
updateDisplay(data, domainRequestsWrapper, noDomainRequestsWrapper, noSearchResultsWrapper, currentSearchTerm);
// identify the DOM element where the domain request list will be inserted into the DOM
const tbody = document.querySelector('.domain-requests__table tbody');
@ -1565,13 +1676,13 @@ document.addEventListener('DOMContentLoaded', function() {
currentSearchTerm = domainRequestsSearchInput.value;
// If the search is blank, we match the resetSearch functionality
if (currentSearchTerm) {
showElement(resetButton);
showElement(resetSearchButton);
} else {
hideElement(resetButton);
hideElement(resetSearchButton);
}
loadDomainRequests(1, 'id', 'asc');
resetHeaders();
})
});
// Reset UI and accessibility
function resetHeaders() {
@ -1586,13 +1697,13 @@ document.addEventListener('DOMContentLoaded', function() {
function resetSearch() {
domainRequestsSearchInput.value = '';
currentSearchTerm = '';
hideElement(resetButton);
loadDomainRequests(1, 'id', 'asc', hasLoaded, '');
hideElement(resetSearchButton);
loadDomainRequests(1, 'id', 'asc');
resetHeaders();
}
if (resetButton) {
resetButton.addEventListener('click', function() {
if (resetSearchButton) {
resetSearchButton.addEventListener('click', function() {
resetSearch();
});
}

View file

@ -4,17 +4,10 @@
display: inline-block;
width: auto;
position: relative;
.usa-accordion__button {
background: color('white');
border: solid 1px color('base-lighter');
border-radius: 4px;
padding: units(1) units(2);
color: color('primary-darker');
font-weight: font-weight('normal');
font-size: size('ui', 'xs');
}
.usa-accordion__button[aria-expanded=false],
.usa-accordion__button[aria-expanded=false]:hover {
.usa-accordion__button[aria-expanded=false]:hover,
.usa-accordion__button[aria-expanded=true],
.usa-accordion__button[aria-expanded=true]:hover {
background-image: none;
}
.usa-accordion__content {

View file

@ -161,3 +161,16 @@ a.usa-button--unstyled:visited {
margin-left: units(2);
}
}
.usa-button--filter {
border: solid 1px color('base-light') !important;
padding: units(1);
color: color('primary-darker') !important;
font-weight: font-weight('normal');
font-size: size('ui', 'xs');
box-shadow: none;
&:hover {
box-shadow: none;
}
}

View file

@ -11,7 +11,7 @@
<section aria-label="Domain requests search component" class="flex-6 margin-y-2">
<form class="usa-search usa-search--small" method="POST" role="search">
{% csrf_token %}
<button class="usa-button usa-button--unstyled margin-right-2 domain-requests__reset-button display-none" type="button">
<button class="usa-button usa-button--unstyled margin-right-2 domain-requests__reset-search display-none" type="button">
Reset
</button>
<label class="usa-sr-only" for="domain-requests__search-field">Search</label>
@ -58,7 +58,7 @@
<p>You haven't requested any domains.</p>
</div>
<div class="domain-requests__no-search-results display-none">
<p>No results found for "<span class="domain-requests__search-term"></span>"</p>
<p>No results found</p>
</div>
</section>
<nav aria-label="Pagination" class="usa-pagination flex-justify" id="domain-requests-pagination">

View file

@ -11,7 +11,7 @@
<section aria-label="Domains search component" class="flex-6 margin-y-2">
<form class="usa-search usa-search--small" method="POST" role="search">
{% csrf_token %}
<button class="usa-button usa-button--unstyled margin-right-2 domains__reset-button display-none" type="button">
<button class="usa-button usa-button--unstyled margin-right-2 domains__reset-search display-none" type="button">
Reset
</button>
<label class="usa-sr-only" for="domains__search-field">Search</label>
@ -34,107 +34,102 @@
</div>
</div>
{% if portfolio %}
<div class="usa-accordion usa-accordion--select">
<span class="margin-right-2 text-base">Filter by</span>
<div class="usa-accordion usa-accordion--select margin-right-2">
<div class="usa-accordion__heading">
<button
type="button"
class="usa-accordion__button"
class="usa-button usa-button--small usa-button--outline usa-button--filter usa-accordion__button"
aria-expanded="false"
aria-controls="a1"
aria-controls="filter-status"
>
Status
<span class="position-relative top-1px">
<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>
</span>
<span class="domain__filter-indicator text-bold">(5)</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="a1" class="usa-accordion__content usa-prose shadow-1">
<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 width-145">Select to apply <span class="sr-only">status</span> filter</legend>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="check-historical-truth"
id="filter-status-dns-needed"
type="checkbox"
name="historical-figures"
value="sojourner-truth"
name="filter-status"
value="unknown"
checked="checked"
/>
<label class="usa-checkbox__label" for="check-historical-truth"
<label class="usa-checkbox__label" for="filter-status-dns-needed"
>DNS Needed</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="check-historical-douglass"
id="filter-status-ready"
type="checkbox"
name="historical-figures"
value="frederick-douglass"
name="filter-status"
value="ready"
checked="checked"
/>
<label class="usa-checkbox__label" for="check-historical-douglass"
<label class="usa-checkbox__label" for="filter-status-ready"
>Ready</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="check-historical-washington"
id="filter-status-on-hold"
type="checkbox"
name="historical-figures"
value="booker-t-washington"
name="filter-status"
value="on hold"
checked="checked"
/>
<label class="usa-checkbox__label" for="check-historical-washington"
<label class="usa-checkbox__label" for="filter-status-on-hold"
>On hold</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="check-historical-carver"
id="filter-status-expired"
type="checkbox"
name="historical-figures"
value="george-washington-carver"
name="filter-status"
value="expired"
checked="checked"
/>
<label class="usa-checkbox__label" for="check-historical-carver"
<label class="usa-checkbox__label" for="filter-status-expired"
>Expired</label
>
</div>
<div class="usa-checkbox">
<input
class="usa-checkbox__input"
id="check-historical-carver"
id="filter-status-deleted"
type="checkbox"
name="historical-figures"
value="george-washington-carver"
name="filter-status"
value="deleted"
checked="checked"
/>
<label class="usa-checkbox__label" for="check-historical-carver"
<label class="usa-checkbox__label" for="filter-status-deleted"
>Deleted</label
>
</div>
</fieldset>
<!-- The button width will determine the dropdown width -->
<button
type="button"
class="usa-button"
>
Apply filter
</button>
<button
type="button"
class="usa-button usa-button--unstyled padding-2"
>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#close"></use>
</svg>
Clear <span class="sr-only">selection. This will not reset the filters on the page.</span>
</button>
</div>
</div>
<button
type="button"
class="usa-button usa-button--small 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>
{% endif %}
<div class="domains__table-wrapper display-none">
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked domains__table">
@ -173,7 +168,7 @@
</p>
</div>
<div class="domains__no-search-results display-none">
<p>No results found for "<span class="domains__search-term"></span>"</p>
<p>No results found</p>
</div>
</section>
<nav aria-label="Pagination" class="usa-pagination flex-justify" id="domains-pagination">

View file

@ -3,7 +3,7 @@
<div class="margin-bottom-4 tablet:margin-bottom-0">
<nav aria-label="">
<h2 class="margin-top-0 text-semibold">{{ portfolio.organization_name }}</h2>
<ul class="usa-sidenav">
<ul class="usa-sidenav usa-sidenav--portfolio">
<li class="usa-sidenav__item">
{% url 'organization-domains' portfolio.id as url %}
<a href="{{ url }}" {% if request.path == url %}class="usa-current"{% endif %}>

View file

@ -31,19 +31,23 @@ def get_domains_json(request):
if status_param:
status_list = status_param.split(",")
print(status_list)
# Split the status list into normal states and custom states
normal_states = [state for state in status_list if state in Domain.State.values]
custom_states = [state for state in status_list if state == "expired"]
# Construct Q objects for normal states that can be queried through ORM
state_query = Q()
if normal_states:
state_query |= Q(state__in=normal_states)
for state in normal_states:
state_query |= Q(state=state)
# Handle custom states in Python, as expired can not be queried through ORM
if "expired" in custom_states:
expired_domains = [domain.id for domain in objects if domain.state_display() == "Expired"]
state_query |= Q(id__in=expired_domains)
expired_domain_ids = [domain.id for domain in objects if domain.state_display() == "Expired"]
state_query |= Q(id__in=expired_domain_ids)
print(state_query)
# Apply the combined query
objects = objects.filter(state_query)
@ -51,8 +55,10 @@ def get_domains_json(request):
# If there are filtered states, and expired is not one of them, domains with
# state_display of 'Expired' must be removed
if "expired" not in custom_states:
expired_domains = [domain.id for domain in objects if domain.state_display() == "Expired"]
objects = objects.exclude(id__in=expired_domains)
expired_domain_ids = [domain.id for domain in objects if domain.state_display() == "Expired"]
objects = objects.exclude(id__in=expired_domain_ids)
else:
objects = Domain.objects.none() # Return no objects if no status_param is provided
if sort_by == "state_display":
# Fetch the objects and sort them in Python