Add client quick actions and live search

This commit is contained in:
A R R R Associates
2026-08-26 21:53:16 +05:30
parent 436a0e0766
commit a01b95eab6
2 changed files with 83 additions and 15 deletions
@@ -9,7 +9,7 @@
{% if can_edit %}
<div class="grid gap-5 xl:grid-cols-3">
<form method="post" action="/clients/{{ client.id }}/business-units" class="rounded-2xl bg-white p-5 shadow-soft space-y-3">
<form id="add-business" data-quick-action="business" method="post" action="/clients/{{ client.id }}/business-units" class="rounded-2xl bg-white p-5 shadow-soft space-y-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<h3 class="font-semibold text-slate-900">Add Business Unit</h3>
<input name="business_code" required placeholder="Business code" class="w-full rounded-xl border px-3 py-2 text-sm">
@@ -20,12 +20,12 @@
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm text-white">Add Business Unit</button>
</form>
<form method="post" action="/clients/{{ client.id }}/branches" class="rounded-2xl bg-white p-5 shadow-soft space-y-3">
<form id="add-branch" data-quick-action="branch" method="post" action="/clients/{{ client.id }}/branches" class="rounded-2xl bg-white p-5 shadow-soft space-y-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<h3 class="font-semibold text-slate-900">Add Client Branch</h3>
<select name="business_unit_id" required class="w-full rounded-xl border px-3 py-2 text-sm">
<option value="">Select Business Unit</option>
{% for row in businesses if row.is_active %}<option value="{{ row.id }}">{{ row.business_name }}</option>{% endfor %}
{% for row in businesses if row.is_active %}<option value="{{ row.id }}" {% if request.query_params.get('business_unit_id') == row.id|string %}selected{% endif %}>{{ row.business_name }}</option>{% endfor %}
</select>
<input name="branch_code" required placeholder="Branch code" class="w-full rounded-xl border px-3 py-2 text-sm">
<input name="branch_name" required placeholder="Client Branch name" class="w-full rounded-xl border px-3 py-2 text-sm">
@@ -38,11 +38,11 @@
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm text-white">Add Client Branch</button>
</form>
<form method="post" action="/clients/{{ client.id }}/registrations" class="rounded-2xl bg-white p-5 shadow-soft space-y-3">
<form id="add-registration" data-quick-action="registration" method="post" action="/clients/{{ client.id }}/registrations" class="rounded-2xl bg-white p-5 shadow-soft space-y-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<h3 class="font-semibold text-slate-900">Add Registration</h3>
<select name="business_unit_id" class="w-full rounded-xl border px-3 py-2 text-sm"><option value="">Client level / derive from Branch</option>{% for row in businesses if row.is_active %}<option value="{{ row.id }}">{{ row.business_name }}</option>{% endfor %}</select>
<select name="client_branch_id" class="w-full rounded-xl border px-3 py-2 text-sm"><option value="">No Client Branch</option>{% for row in branches if row.is_active %}<option value="{{ row.id }}">{{ row.branch_name }}</option>{% endfor %}</select>
<select name="business_unit_id" class="w-full rounded-xl border px-3 py-2 text-sm"><option value="">Client level / derive from Branch</option>{% for row in businesses if row.is_active %}<option value="{{ row.id }}" {% if request.query_params.get('business_unit_id') == row.id|string %}selected{% endif %}>{{ row.business_name }}</option>{% endfor %}</select>
<select name="client_branch_id" class="w-full rounded-xl border px-3 py-2 text-sm"><option value="">No Client Branch</option>{% for row in branches if row.is_active %}<option value="{{ row.id }}" {% if request.query_params.get('client_branch_id') == row.id|string %}selected{% endif %}>{{ row.branch_name }}</option>{% endfor %}</select>
<select name="registration_type_id" required class="w-full rounded-xl border px-3 py-2 text-sm">
<option value="">Select Registration Type</option>
{% for row in registration_types %}<option value="{{ row.id }}">{{ row.name }} ({{ row.code }})</option>{% endfor %}
@@ -74,4 +74,21 @@
<tbody>{% for row in registrations %}<tr class="border-t"><td class="p-3">{{ registration_type_codes.get(row.id, "-") }}</td><td class="p-3 font-medium">{{ row.registration_number }}</td><td class="p-3">{{ row.trade_name or row.legal_name or '-' }}</td><td class="p-3">{{ row.state or '-' }}</td><td class="p-3 text-center">{{ row.status|replace('_',' ')|title }}</td><td class="p-3">{% if can_edit %}<form method="post" action="/clients/{{ client.id }}/business-structure/registration/{{ row.id }}/toggle"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><button class="text-xs text-brand-700">{{ 'Deactivate' if row.status in ['active','valid','registered'] else 'Activate' }}</button></form>{% endif %}</td></tr>{% else %}<tr><td colspan="6" class="p-6 text-center text-slate-500">No registrations added.</td></tr>{% endfor %}</tbody></table>
</section>
</div>
<script>
(function () {
const action = new URLSearchParams(window.location.search).get('action');
if (!action) return;
const target = document.querySelector('[data-quick-action="' + action + '"]');
if (!target) return;
target.classList.add('ring-2', 'ring-brand-400', 'ring-offset-2');
window.setTimeout(function () {
target.scrollIntoView({behavior: 'smooth', block: 'center'});
const first = target.querySelector('input:not([type="hidden"]), select, textarea');
if (first) first.focus({preventScroll: true});
}, 80);
})();
</script>
{% endblock %}
@@ -25,11 +25,11 @@
{% endfor %}
</div>
<form method="get" action="/clients/management" class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
<form id="client-management-filter" method="get" action="/clients/management" class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-6">
<div class="xl:col-span-2">
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Search</label>
<input name="q" value="{{ q }}" placeholder="Client, trade name, PAN, GSTIN, TAN, branch, email..." class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
<input id="client-management-search" name="q" value="{{ q }}" autocomplete="off" {% if q %}autofocus{% endif %} placeholder="Client, trade name, PAN, GSTIN, TAN, branch, email..." class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
</div>
<div>
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Client status</label>
@@ -80,7 +80,7 @@
{% for node in hierarchy %}
{% set client = node.client %}
<details class="group border-b border-slate-100 last:border-b-0">
<details class="group border-b border-slate-100 last:border-b-0" {% if q %}open{% endif %}>
<summary class="cursor-pointer list-none px-4 py-4 hover:bg-slate-50">
<div class="grid items-center gap-3 lg:grid-cols-[minmax(280px,1.7fr)_140px_110px_120px_210px]">
<div class="flex min-w-0 items-start gap-3">
@@ -97,10 +97,15 @@
<div><span class="rounded-full bg-slate-100 px-2.5 py-1 text-xs font-medium text-slate-700">{{ client.client_type }}</span></div>
<div class="text-sm"><span class="font-semibold">{{ node.business_count }}</span><span class="ml-1 text-xs text-slate-500">business</span></div>
<div class="text-sm"><span class="font-semibold">{{ node.branch_count }}</span><span class="ml-1 text-xs text-slate-500">branch</span></div>
<div class="flex justify-end gap-2" onclick="event.stopPropagation()">
<a href="/clients/{{ client.id }}" class="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-semibold text-slate-700">View</a>
<a href="/clients/{{ client.id }}/business-structure" class="rounded-lg border border-brand-200 bg-brand-50 px-3 py-1.5 text-xs font-semibold text-brand-700">Manage Structure</a>
{% if can_edit %}<a href="/clients/{{ client.id }}/edit" class="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-semibold text-slate-700">Edit</a>{% endif %}
<div class="flex flex-wrap justify-end gap-1.5" onclick="event.stopPropagation()">
{% if can_edit %}
<a href="/clients/{{ client.id }}/business-structure?action=business#add-business" class="rounded-lg border border-emerald-200 bg-emerald-50 px-2.5 py-1.5 text-xs font-semibold text-emerald-700">+ Business</a>
<a href="/clients/{{ client.id }}/business-structure?action=branch#add-branch" class="rounded-lg border border-blue-200 bg-blue-50 px-2.5 py-1.5 text-xs font-semibold text-blue-700">+ Branch</a>
<a href="/clients/{{ client.id }}/business-structure?action=registration#add-registration" class="rounded-lg border border-violet-200 bg-violet-50 px-2.5 py-1.5 text-xs font-semibold text-violet-700">+ Registration</a>
{% endif %}
<a href="/clients/{{ client.id }}" class="rounded-lg border border-slate-300 px-2.5 py-1.5 text-xs font-semibold text-slate-700">View</a>
<a href="/clients/{{ client.id }}/business-structure" class="rounded-lg border border-brand-200 bg-brand-50 px-2.5 py-1.5 text-xs font-semibold text-brand-700">Manage</a>
{% if can_edit %}<a href="/clients/{{ client.id }}/edit" class="rounded-lg border border-slate-300 px-2.5 py-1.5 text-xs font-semibold text-slate-700">Edit</a>{% endif %}
</div>
</div>
</summary>
@@ -120,7 +125,13 @@
</div>
<div class="mt-0.5 text-xs text-slate-500">{{ business.business_code }}{% if business.trade_name %} · Trade: {{ business.trade_name }}{% endif %}{% if business.nature_of_business %} · {{ business.nature_of_business }}{% endif %}</div>
</div>
<a href="/clients/{{ client.id }}/business-structure" class="text-xs font-semibold text-brand-700">Manage business</a>
<div class="flex flex-wrap gap-2">
{% if can_edit %}
<a href="/clients/{{ client.id }}/business-structure?action=branch&business_unit_id={{ business.id }}#add-branch" class="rounded-lg border border-blue-200 bg-blue-50 px-2.5 py-1 text-xs font-semibold text-blue-700">+ Branch</a>
<a href="/clients/{{ client.id }}/business-structure?action=registration&business_unit_id={{ business.id }}#add-registration" class="rounded-lg border border-violet-200 bg-violet-50 px-2.5 py-1 text-xs font-semibold text-violet-700">+ Registration</a>
{% endif %}
<a href="/clients/{{ client.id }}/business-structure" class="rounded-lg border border-slate-200 px-2.5 py-1 text-xs font-semibold text-brand-700">Manage business</a>
</div>
</div>
{% for branch_node in business_node.branches %}
@@ -132,7 +143,12 @@
<span class="ml-2 text-xs text-slate-500">{{ branch.branch_code }}{% if branch.city %} · {{ branch.city }}{% endif %}{% if branch.state %}, {{ branch.state }}{% endif %}</span>
{% if branch.is_primary %}<span class="ml-2 rounded-full bg-blue-50 px-2 py-0.5 text-[11px] font-semibold text-blue-700">Primary Branch</span>{% endif %}
</div>
<div class="text-xs text-slate-500">{{ branch_node.registrations|length }} registration{{ '' if branch_node.registrations|length == 1 else 's' }}</div>
<div class="flex items-center gap-2">
<span class="text-xs text-slate-500">{{ branch_node.registrations|length }} registration{{ '' if branch_node.registrations|length == 1 else 's' }}</span>
{% if can_edit %}
<a href="/clients/{{ client.id }}/business-structure?action=registration&business_unit_id={{ business.id }}&client_branch_id={{ branch.id }}#add-registration" class="rounded-lg border border-violet-200 bg-violet-50 px-2.5 py-1 text-xs font-semibold text-violet-700">+ Registration</a>
{% endif %}
</div>
</div>
{% if branch_node.registrations %}
<div class="mt-2 grid gap-2 xl:grid-cols-2">
@@ -213,4 +229,39 @@
</div>
{% endif %}
</div>
<script>
(function () {
const form = document.getElementById('client-management-filter');
const search = document.getElementById('client-management-search');
if (!form || !search) return;
let timer = null;
let composing = false;
function submitFromFilter() {
const page = form.querySelector('input[name="page"]');
if (page) page.value = '1';
form.requestSubmit();
}
search.addEventListener('compositionstart', function () { composing = true; });
search.addEventListener('compositionend', function () {
composing = false;
clearTimeout(timer);
timer = setTimeout(submitFromFilter, 350);
});
search.addEventListener('input', function () {
if (composing) return;
clearTimeout(timer);
timer = setTimeout(submitFromFilter, 350);
});
form.querySelectorAll('select').forEach(function (select) {
select.addEventListener('change', submitFromFilter);
});
})();
</script>
{% endblock %}