Add client quick actions and live search
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
<div class="grid gap-5 xl:grid-cols-3">
|
<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 }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||||
<h3 class="font-semibold text-slate-900">Add Business Unit</h3>
|
<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">
|
<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>
|
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm text-white">Add Business Unit</button>
|
||||||
</form>
|
</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 }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||||
<h3 class="font-semibold text-slate-900">Add Client Branch</h3>
|
<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">
|
<select name="business_unit_id" required class="w-full rounded-xl border px-3 py-2 text-sm">
|
||||||
<option value="">Select Business Unit</option>
|
<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>
|
</select>
|
||||||
<input name="branch_code" required placeholder="Branch code" class="w-full rounded-xl border px-3 py-2 text-sm">
|
<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">
|
<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>
|
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm text-white">Add Client Branch</button>
|
||||||
</form>
|
</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 }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||||
<h3 class="font-semibold text-slate-900">Add Registration</h3>
|
<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="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 }}">{{ row.branch_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">
|
<select name="registration_type_id" required class="w-full rounded-xl border px-3 py-2 text-sm">
|
||||||
<option value="">Select Registration Type</option>
|
<option value="">Select Registration Type</option>
|
||||||
{% for row in registration_types %}<option value="{{ row.id }}">{{ row.name }} ({{ row.code }})</option>{% endfor %}
|
{% 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>
|
<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>
|
</section>
|
||||||
</div>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</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="grid gap-3 md:grid-cols-2 xl:grid-cols-6">
|
||||||
<div class="xl:col-span-2">
|
<div class="xl:col-span-2">
|
||||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Search</label>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Client status</label>
|
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Client status</label>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
|
|
||||||
{% for node in hierarchy %}
|
{% for node in hierarchy %}
|
||||||
{% set client = node.client %}
|
{% 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">
|
<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="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">
|
<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><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.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="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()">
|
<div class="flex flex-wrap justify-end gap-1.5" 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>
|
{% if can_edit %}
|
||||||
<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>
|
<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>
|
||||||
{% 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 %}
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</summary>
|
</summary>
|
||||||
@@ -120,7 +125,13 @@
|
|||||||
</div>
|
</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 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>
|
</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>
|
</div>
|
||||||
|
|
||||||
{% for branch_node in business_node.branches %}
|
{% 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>
|
<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 %}
|
{% 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>
|
||||||
<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>
|
</div>
|
||||||
{% if branch_node.registrations %}
|
{% if branch_node.registrations %}
|
||||||
<div class="mt-2 grid gap-2 xl:grid-cols-2">
|
<div class="mt-2 grid gap-2 xl:grid-cols-2">
|
||||||
@@ -213,4 +229,39 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user