diff --git a/app/modules/partner_dashboard/templates/partner_dashboard/partials/clients.html b/app/modules/partner_dashboard/templates/partner_dashboard/partials/clients.html index f56985f..f45a4e5 100644 --- a/app/modules/partner_dashboard/templates/partner_dashboard/partials/clients.html +++ b/app/modules/partner_dashboard/templates/partner_dashboard/partials/clients.html @@ -45,7 +45,9 @@ {% for c in clients %} {% set row_status = (c.status or 'active')|lower %} - + {{ c.client_code }} {{ c.client_name }} {{ c.pan or c.gstin or '-' }} @@ -53,49 +55,26 @@ {{ c.task_count }} {{ c.overdue_count }} - {% if row_status == 'active' %}Active - {% elif row_status == 'archived' %}Archived - {% else %}Inactive{% endif %} + {% if row_status == 'active' %} + Active + {% elif row_status == 'archived' %} + Archived + {% else %} + Inactive + {% endif %} + + + Open - Open {% else %} No assigned clients found. {% endfor %} - No clients match the search. + + No clients match the search. + - + diff --git a/app/ui/static/js/partner_dashboard_clients.js b/app/ui/static/js/partner_dashboard_clients.js new file mode 100644 index 0000000..75b63cf --- /dev/null +++ b/app/ui/static/js/partner_dashboard_clients.js @@ -0,0 +1,107 @@ +(() => { + "use strict"; + + const ROOT_SELECTOR = "[data-partner-client-list]"; + + function initialiseRoot(root) { + if (!(root instanceof Element) || root.dataset.liveFilterReady === "1") { + return; + } + + const search = root.querySelector("[data-client-search]"); + const status = root.querySelector("[data-client-status]"); + const clear = root.querySelector("[data-client-clear]"); + const scroll = root.querySelector("[data-client-scroll]"); + const count = root.querySelector("[data-visible-count]"); + const empty = root.querySelector("[data-no-results]"); + const rows = Array.from(root.querySelectorAll("[data-client-row]")); + + if (!search || !status || !clear || !count) { + return; + } + + root.dataset.liveFilterReady = "1"; + let debounceTimer = null; + + function applyFilter() { + const term = String(search.value || "").trim().toLocaleLowerCase(); + const wantedStatus = String(status.value || "").trim().toLocaleLowerCase(); + let visible = 0; + + rows.forEach((row) => { + const searchable = String(row.dataset.search || "").toLocaleLowerCase(); + const rowStatus = String(row.dataset.status || "").toLocaleLowerCase(); + const matchesText = !term || searchable.includes(term); + const matchesStatus = !wantedStatus || rowStatus === wantedStatus; + const shouldShow = matchesText && matchesStatus; + + row.hidden = !shouldShow; + if (shouldShow) { + visible += 1; + } + }); + + count.textContent = `${visible} ${visible === 1 ? "client" : "clients"}`; + + if (empty) { + empty.hidden = visible !== 0; + } + + if (scroll) { + scroll.scrollTop = 0; + } + } + + search.addEventListener("input", () => { + window.clearTimeout(debounceTimer); + debounceTimer = window.setTimeout(applyFilter, 200); + }); + + status.addEventListener("change", applyFilter); + + clear.addEventListener("click", () => { + window.clearTimeout(debounceTimer); + search.value = ""; + status.value = "active"; + applyFilter(); + search.focus(); + }); + + applyFilter(); + } + + function initialiseWithin(scope = document) { + if (scope instanceof Element && scope.matches(ROOT_SELECTOR)) { + initialiseRoot(scope); + } + + if (typeof scope.querySelectorAll === "function") { + scope.querySelectorAll(ROOT_SELECTOR).forEach(initialiseRoot); + } + } + + function start() { + initialiseWithin(document); + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node instanceof Element) { + initialiseWithin(node); + } + }); + }); + }); + + observer.observe(document.documentElement, { + childList: true, + subtree: true, + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", start, { once: true }); + } else { + start(); + } +})();