implement new filter to services page

This commit is contained in:
Jordan Blasenhauer 2024-05-14 14:48:35 +02:00
parent 75bf0a99a1
commit bf7017d256
3 changed files with 93 additions and 360 deletions

View file

@ -8,6 +8,10 @@ import {
SettingsAdvanced,
} from "./utils/settings.js";
import {
Filter
} from "./utils/dashboard.js";
class SettingsService {
constructor() {
this.prefix = "services";
@ -549,130 +553,8 @@ class Dropdown {
}
}
class Filter {
constructor(prefix = "services") {
this.prefix = prefix;
this.container =
document.querySelector(`[data-${this.prefix}-filter]`) || null;
this.keyInp = document.querySelector("input#service-name-keyword");
this.stateValue = "all";
this.methodValue = "all";
this.initHandler();
}
initHandler() {
if (!this.container) return;
//STATE HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"state"
) {
setTimeout(() => {
const value = document
.querySelector(
`[data-${this.prefix}-setting-select-text="state"]`,
)
.textContent.trim()
.toLowerCase();
this.stateValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
//METHOD HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"method"
) {
setTimeout(() => {
const value = document
.querySelector(
`[data-${this.prefix}-setting-select-text="method"]`,
)
.textContent.trim()
.toLowerCase();
this.methodValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
//KEYWORD HANDLER
this.keyInp.addEventListener("input", (e) => {
this.filter();
});
}
filter() {
const services = document.querySelectorAll(`[data-${this.prefix}-card]`);
if (services.length === 0) return;
//reset
for (let i = 0; i < services.length; i++) {
const el = services[i];
el.classList.remove("hidden");
}
//filter type
this.setFilterState(services);
this.setFilterMethod(services);
this.setFilterKeyword(services);
}
setFilterState(services) {
if (this.stateValue === "all") return;
for (let i = 0; i < services.length; i++) {
const el = services[i];
const type = el
.querySelector(`[data-${this.prefix}-state]`)
.getAttribute(`data-${this.prefix}-state`)
.trim()
.toLowerCase();
if (type !== this.stateValue) el.classList.add("hidden");
}
}
setFilterMethod(services) {
if (this.methodValue === "all") return;
for (let i = 0; i < services.length; i++) {
const el = services[i];
const type = el
.querySelector(`[data-${this.prefix}-method]`)
.getAttribute(`data-${this.prefix}-method`)
.trim()
.toLowerCase();
if (type !== this.methodValue) el.classList.add("hidden");
}
}
setFilterKeyword(jobs) {
const keyword = this.keyInp.value.trim().toLowerCase();
if (!keyword) return;
for (let i = 0; i < jobs.length; i++) {
const el = jobs[i];
const name = el
.querySelector(`[data-${this.prefix}-name]`)
.textContent.trim()
.toLowerCase();
if (!name.includes(keyword)) el.classList.add("hidden");
}
}
}
const setDropdown = new Dropdown();
const setFilter = new Filter();
const setTabsSelect = new TabsSelect(
document.querySelector("[data-services-tabs-select]"),
document.querySelector("[data-advanced][data-services-modal-form]"),
@ -713,22 +595,33 @@ const checkServiceModalSelect = new CheckNoMatchFilter(
document.querySelector("[data-services-nomatch]"),
);
try {
const checkServiceCardKeyword = new CheckNoMatchFilter(
document.querySelectorAll("input#service-name-keyword"),
"input",
document.querySelectorAll("[data-services-card]"),
false,
document.querySelector("[data-services-nomatch-card]"),
);
const checkServiceCardSelect = new CheckNoMatchFilter(
document.querySelectorAll(
"button[data-services-setting-select-dropdown-btn]",
),
"select",
document.querySelectorAll("[data-services-card]"),
false,
document.querySelector("[data-services-nomatch-card]"),
);
} catch (e) {}
const filterContainer = document.querySelector(`[data-services-filter]`);
if(filterContainer) {
const noMatchEl = document.querySelector("[data-services-nomatch-card]");
const filterEls = document.querySelectorAll(`[data-services-card]`);
const keywordFilter = {
"handler": document.querySelector("input#service-name-keyword"),
"handlerType" : "input",
"value" : document.querySelector("input#service-name-keyword").value,
"filterEls": filterEls,
"filterAtt" : "data-services-name",
"filterType" : "keyword",
};
const methodFilter = {
"handler": document.querySelector("[data-services-setting-select-dropdown='method']"),
"handlerType" : "select",
"value" : document.querySelector("[data-services-setting-select-text='method']").textContent.trim().toLowerCase(),
"filterEls": filterEls,
"filterAtt" : "data-services-method",
"filterType" : "match",
};
const stateFilter = {
"handler": document.querySelector("[data-services-setting-select-dropdown='state']"),
"handlerType" : "select",
"value" : document.querySelector("[data-services-setting-select-text='state']").textContent.trim().toLowerCase(),
"filterEls": filterEls,
"filterAtt" : "data-services-state",
"filterType" : "match",
};
new Filter("services", [keywordFilter, methodFilter, stateFilter], null, noMatchEl);
}

View file

@ -9,7 +9,7 @@
// noMatchEl : if exists, will be displayed when no match is found
// containerEl : container of the filter elements, case noMatchEl exists, it will be hidden when no match is found
class Filter {
constructor(prefix, filters, containerEl, noMatchEl) {
constructor(prefix, filters, containerEl = null, noMatchEl = null) {
this.prefix = prefix;
this.filters = filters;
this.containerEl = containerEl;
@ -38,11 +38,9 @@ init() {
setSelectHandler(handler) {
handler.addEventListener("click", (e) => {
try {
const handlerName = handler.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`);
const value = document
.querySelector(`[data-${this.prefix}-setting-select-text="${handlerName}"]`)
.textContent.trim();
this.updateValue(handlerName, value);
if(!e.target.closest("button").hasAttribute('data-services-setting-select-dropdown-btn')) return;
const value = e.target.closest("button").getAttribute('value');
this.updateValue(handler, value);
this.filter();
} catch(err) {}
@ -79,56 +77,53 @@ resetFilter() {
});
if(this.noMatchEl) this.noMatchEl.classList.add("hidden");
this.containerEl.classList.remove("hidden");
if(this.containerEl)this.containerEl.classList.remove("hidden");
}
filter() {
// Start by resetting the filter
this.resetFilter();
// Then apply all filters
let isAtLeastOneMatch = false;
this.filters.forEach((filter) => {
const [filterType, value, filterEls, filterAtt] = this.getFilterData(filter);
// keyword filter means that el filter value must contains the keyword
if(filterType === "keyword") {
isAtLeastOneMatch = this.filterKeyword(value, filterEls, filterAtt) ? true : isAtLeastOneMatch;
this.filterKeyword(value, filterEls, filterAtt);
}
// match filter means that el filter value must be equal to the filter value
if(filterType === "match") {
isAtLeastOneMatch = this.filterMatch(value, filterEls, filterAtt) ? true : isAtLeastOneMatch;
this.filterMatch(value, filterEls, filterAtt);
}
// bool filter means that el filter value must be equal to bool value
if(filterType === "bool") {
isAtLeastOneMatch = this.filterBool(value, filterEls, filterAtt) ? true : isAtLeastOneMatch;
this.filterBool(value, filterEls, filterAtt);
}
// lower than filter means that el filter value must be lower than the filter value
if(filterType === "lowerThan") {
isAtLeastOneMatch = this.filterLowerThan(value, filterEls, filterAtt) ? true : isAtLeastOneMatch;
this.filterLowerThan(value, filterEls, filterAtt);
}
// higher than filter means that el filter value must be higher than the filter value
if(filterType === "higherThan") {
isAtLeastOneMatch = this.filterHigherThan(value, filterEls, filterAtt) ? true : isAtLeastOneMatch;
this.filterHigherThan(value, filterEls, filterAtt);
}
});
// If no match is found, hide the container and display the no match element
if(!isAtLeastOneMatch) {
this.containerEl.classList.add("hidden");
if(this.noMatchEl) this.noMatchEl.classList.remove("hidden");
}
setTimeout(() => {
if(!this.isAtLeastOneMatch()) {
if(this.containerEl) this.containerEl.classList.add("hidden");
if(this.noMatchEl) this.noMatchEl.classList.remove("hidden");
}
}, 50);
}
filterKeyword(value, filterEls, filterAtt) {
let isAtLeastOneMatch = false;
const keyword = value.trim().toLowerCase();
if (!keyword) return false;
if (!keyword) return;
for (let i = 0; i < filterEls.length; i++) {
const el = filterEls[i];
@ -137,17 +132,13 @@ filterKeyword(value, filterEls, filterAtt) {
el.classList.add("hidden");
continue;
}
isAtLeastOneMatch = true;
}
return isAtLeastOneMatch;
return;
}
filterMatch(value, filterEls, filterAtt) {
if(!value || value === "all") return false;
let isAtLeastOneMatch = false;
if(!value || value === "all") return;
for (let i = 0; i < filterEls.length; i++) {
const el = filterEls[i];
@ -156,18 +147,15 @@ filterMatch(value, filterEls, filterAtt) {
el.classList.add("hidden");
continue;
}
isAtLeastOneMatch = true;
}
return isAtLeastOneMatch;
return;
}
filterBool(value, filterEls, filterAtt) {
// Check if value is undefined or null
if(value === undefined || value === null) return false;
let isAtLeastOneMatch = false;
if(value === undefined || value === null) return;
for (let i = 0; i < filterEls.length; i++) {
const el = filterEls[i];
@ -176,7 +164,6 @@ filterBool(value, filterEls, filterAtt) {
const isValueTruthy = truthyValues.includes(elValue);
if(value && isValueTruthy || !value && !isValueTruthy) {
isAtLeastOneMatch = true;
continue;
}
@ -184,14 +171,13 @@ filterBool(value, filterEls, filterAtt) {
}
return isAtLeastOneMatch;
return ;
}
filterLowerThan(value, filterEls, filterAtt) {
// Check if value is undefined or null
if(!value) return false;
if(!value) return;
let isAtLeastOneMatch = false;
for (let i = 0; i < filterEls.length; i++) {
const el = filterEls[i];
@ -208,18 +194,14 @@ filterLowerThan(value, filterEls, filterAtt) {
el.classList.add("hidden");
continue;
}
isAtLeastOneMatch = true;
}
return isAtLeastOneMatch;
return;
}
filterHigherThan(value, filterEls, filterAtt) {
// Check if value is undefined or null
if(!value) return false;
let isAtLeastOneMatch = false;
if(!value) return;
for (let i = 0; i < filterEls.length; i++) {
const el = filterEls[i];
@ -237,10 +219,27 @@ filterLowerThan(value, filterEls, filterAtt) {
continue;
}
isAtLeastOneMatch = true;
}
return isAtLeastOneMatch;
return;
}
isAtLeastOneMatch() {
// loop on each filterEls and check if at least one is not hidden
let isOneMatch = false;
for(let i = 0; i < this.filters.length; i++) {
const filter = this.filters[i];
const filterEls = filter.filterEls;
filterEls.forEach((el) => {
if(!el.classList.contains("hidden")) {
isOneMatch = true;
return;
}
});
if(isOneMatch) break;
}
return isOneMatch;
}
updateValue(handler, value) {
@ -255,182 +254,20 @@ filterLowerThan(value, filterEls, filterAtt) {
}
getFilterElValue(el, filterAtt) {
return filterAtt === "textContent" ? el.textContent.trim().toLowerCase() : el.getAttribute(`data-${this.prefix}-${filterAtt}`).trim().toLowerCase();
return filterAtt === "textContent" ? el.textContent.trim().toLowerCase() : el.getAttribute(filterAtt).trim().toLowerCase();
}
}
class Dropdown {
constructor(prefix = "bans") {
this.prefix = prefix;
this.container = document.querySelector("main");
this.lastDrop = "";
this.initDropdown();
}
initDropdown() {
this.container.addEventListener("click", (e) => {
//SELECT BTN LOGIC
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
}
this.toggleSelectBtn(e);
}
} catch (err) {}
//SELECT DROPDOWN BTN LOGIC
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-setting-select-dropdown-btn`)
) {
const btn = e.target.closest("button");
const btnValue = btn.getAttribute("value");
const btnSetting = btn.getAttribute(
`data-${this.prefix}-setting-select-dropdown-btn`,
);
//stop if same value to avoid new fetching
const isSameVal = this.isSameValue(btnSetting, btnValue);
if (isSameVal) return this.hideDropdown(btnSetting);
//else, add new value to custom
this.setSelectNewValue(btnSetting, btnValue);
//close dropdown and change style
this.hideDropdown(btnSetting);
if (
!e.target.closest("button").hasAttribute(`data-${this.prefix}-file`)
) {
this.changeDropBtnStyle(btnSetting, btn);
}
//show / hide filter
if (btnSetting === "instances") {
this.hideFilterOnLocal(btn.getAttribute("data-_type"));
}
}
} catch (err) {}
});
}
closeAllDrop() {
const drops = document.querySelectorAll(
`[data-${this.prefix}-setting-select-dropdown]`,
);
drops.forEach((drop) => {
drop.classList.add("hidden");
drop.classList.remove("flex");
document
.querySelector(
`svg[data-${this.prefix}-setting-select="${drop.getAttribute(
`data-${this.prefix}-setting-select-dropdown`,
)}"]`,
)
.classList.remove("rotate-180");
});
}
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`,
);
const currVal = selectCustom.textContent;
return currVal === value ? true : false;
}
setSelectNewValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[data-${this.prefix}-setting-select="${btnSetting}"]`,
);
selectCustom.querySelector(
`[data-${this.prefix}-setting-select-text]`,
).textContent = value;
}
hideDropdown(btnSetting) {
//hide dropdown
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`,
);
dropdownEl.classList.add("hidden");
dropdownEl.classList.remove("flex");
//svg effect
const dropdownChevron = document.querySelector(
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`,
);
dropdownChevron.classList.remove("rotate-180");
}
changeDropBtnStyle(btnSetting, selectedBtn) {
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`,
);
//reset dropdown btns
const btnEls = dropdownEl.querySelectorAll("button");
btnEls.forEach((btn) => {
btn.classList.remove(
"bg-primary",
"dark:bg-primary",
"text-gray-300",
"text-gray-300",
);
btn.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700");
});
//highlight clicked btn
selectedBtn.classList.remove(
"bg-white",
"dark:bg-slate-700",
"text-gray-700",
);
selectedBtn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
}
toggleSelectBtn(e) {
const attribute = e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select`);
//toggle dropdown
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${attribute}"]`,
);
const dropdownChevron = document.querySelector(
`svg[data-${this.prefix}-setting-select="${attribute}"]`,
);
dropdownEl.classList.toggle("hidden");
dropdownEl.classList.toggle("flex");
dropdownChevron.classList.toggle("rotate-180");
}
//hide date filter on local
hideFilterOnLocal(type) {
if (type === "local") {
this.hideInp(`input#from-date`);
this.hideInp(`input#to-date`);
constructor()
{
this.init();
}
if (type !== "local") {
this.showInp(`input#from-date`);
this.showInp(`input#to-date`);
init(){
}
}
showInp(selector) {
document.querySelector(selector).closest("div").classList.add("flex");
document.querySelector(selector).closest("div").classList.remove("hidden");
}
hideInp(selector) {
document.querySelector(selector).closest("div").classList.add("hidden");
document.querySelector(selector).closest("div").classList.remove("flex");
}
}
export { Filter, Dropdown };

View file

@ -106,7 +106,10 @@
{% for service in services %}
{% set id_server_name = service["SERVER_NAME"]['value'].replace(".", "-") %}
<div data-{{attribute_name}}-card
data-settings="{{ service['settings'] }}"
data-{{attribute_name}}-name="{{ service["SERVER_NAME"]['value'] }}"
data-{{attribute_name}}-method="{{ service["SERVER_NAME"]['method'] }}"
data-{{attribute_name}}-state="{{ "draft" if service.get('IS_DRAFT', "no") == "yes" else "online" }}"
data-settings="{{ service['settings'] }}"
data-{{attribute_name}}-service="{{ service['SERVER_NAME']['value'] }}"
class="flex flex-col justify-between dark:brightness-110 overflow-hidden hover:scale-102 transition col-span-12 lg:col-span-6 3xl:col-span-4 p-4 w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
@ -121,11 +124,11 @@
data-value="{{ service['SERVER_NAME']['method'] }}"></div>
<div class="flex justify-between items-start">
<div class="flex flex-col">
<h5 data-{{attribute_name}}-name="{{ service["SERVER_NAME"]['value'] }}"
<h5
class="break-all transition duration-300 ease-in-out text-center sm:text-left mb-1 mr-2 font-bold dark:text-white/90">
{{ service["SERVER_NAME"]['value'] }}
</h5>
<h6 data-{{attribute_name}}-method="{{ service["SERVER_NAME"]['method'] }}"
<h6
class="text-left sm:mb-2 font-semibold text-gray-600 dark:text-white/80">
{{ service["SERVER_NAME"]['method'] }}
</h6>