Fix live search on Partner Dashboard clients
This commit is contained in:
@@ -45,7 +45,9 @@
|
|||||||
<tbody class="divide-y divide-slate-100 bg-white">
|
<tbody class="divide-y divide-slate-100 bg-white">
|
||||||
{% for c in clients %}
|
{% for c in clients %}
|
||||||
{% set row_status = (c.status or 'active')|lower %}
|
{% set row_status = (c.status or 'active')|lower %}
|
||||||
<tr data-client-row data-search="{{ (c.client_code ~ ' ' ~ c.client_name ~ ' ' ~ (c.pan or '') ~ ' ' ~ (c.gstin or ''))|lower|e }}" data-status="{{ row_status|e }}">
|
<tr data-client-row
|
||||||
|
data-search="{{ (c.client_code ~ ' ' ~ c.client_name ~ ' ' ~ (c.pan or '') ~ ' ' ~ (c.gstin or ''))|lower|e }}"
|
||||||
|
data-status="{{ row_status|e }}">
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm font-semibold text-slate-900">{{ c.client_code }}</td>
|
<td class="whitespace-nowrap px-4 py-3 text-sm font-semibold text-slate-900">{{ c.client_code }}</td>
|
||||||
<td class="px-4 py-3 text-sm text-slate-800">{{ c.client_name }}</td>
|
<td class="px-4 py-3 text-sm text-slate-800">{{ c.client_name }}</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-slate-600">{{ c.pan or c.gstin or '-' }}</td>
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-slate-600">{{ c.pan or c.gstin or '-' }}</td>
|
||||||
@@ -53,49 +55,26 @@
|
|||||||
<td class="px-4 py-3 text-center text-sm text-slate-700">{{ c.task_count }}</td>
|
<td class="px-4 py-3 text-center text-sm text-slate-700">{{ c.task_count }}</td>
|
||||||
<td class="px-4 py-3 text-center text-sm {% if c.overdue_count > 0 %}font-semibold text-red-700{% else %}text-slate-700{% endif %}">{{ c.overdue_count }}</td>
|
<td class="px-4 py-3 text-center text-sm {% if c.overdue_count > 0 %}font-semibold text-red-700{% else %}text-slate-700{% endif %}">{{ c.overdue_count }}</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||||
{% if row_status == 'active' %}<span class="rounded-full bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-700">Active</span>
|
{% if row_status == 'active' %}
|
||||||
{% elif row_status == 'archived' %}<span class="rounded-full bg-amber-100 px-2 py-1 text-xs font-medium text-amber-800">Archived</span>
|
<span class="rounded-full bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-700">Active</span>
|
||||||
{% else %}<span class="rounded-full bg-slate-200 px-2 py-1 text-xs font-medium text-slate-700">Inactive</span>{% endif %}
|
{% elif row_status == 'archived' %}
|
||||||
|
<span class="rounded-full bg-amber-100 px-2 py-1 text-xs font-medium text-amber-800">Archived</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="rounded-full bg-slate-200 px-2 py-1 text-xs font-medium text-slate-700">Inactive</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="whitespace-nowrap px-4 py-3 text-right">
|
||||||
|
<a href="{{ c.href }}" class="text-sm font-medium text-brand-700 hover:underline">Open</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-right"><a href="{{ c.href }}" class="text-sm font-medium text-brand-700 hover:underline">Open</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="8" class="px-4 py-8 text-center text-sm text-slate-500">No assigned clients found.</td></tr>
|
<tr><td colspan="8" class="px-4 py-8 text-center text-sm text-slate-500">No assigned clients found.</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr data-no-results hidden><td colspan="8" class="px-4 py-8 text-center text-sm text-slate-500">No clients match the search.</td></tr>
|
<tr data-no-results hidden>
|
||||||
|
<td colspan="8" class="px-4 py-8 text-center text-sm text-slate-500">No clients match the search.</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script src="/static/js/partner_dashboard_clients.js" defer></script>
|
||||||
(function () {
|
|
||||||
const root = document.querySelector('[data-partner-client-list]');
|
|
||||||
if (!root || root.dataset.ready === '1') return;
|
|
||||||
root.dataset.ready = '1';
|
|
||||||
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]'));
|
|
||||||
let timer;
|
|
||||||
function apply() {
|
|
||||||
const term = (search.value || '').trim().toLowerCase();
|
|
||||||
const wantedStatus = status.value;
|
|
||||||
let visible = 0;
|
|
||||||
rows.forEach((row) => {
|
|
||||||
const show = (!term || row.dataset.search.includes(term)) && (!wantedStatus || row.dataset.status === wantedStatus);
|
|
||||||
row.hidden = !show;
|
|
||||||
if (show) visible += 1;
|
|
||||||
});
|
|
||||||
count.textContent = visible + (visible === 1 ? ' client' : ' clients');
|
|
||||||
empty.hidden = visible !== 0;
|
|
||||||
scroll.scrollTop = 0;
|
|
||||||
}
|
|
||||||
search.addEventListener('input', () => { window.clearTimeout(timer); timer = window.setTimeout(apply, 250); });
|
|
||||||
status.addEventListener('change', apply);
|
|
||||||
clear.addEventListener('click', () => { search.value = ''; status.value = 'active'; apply(); search.focus(); });
|
|
||||||
apply();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user