Files
arrr-erp/app/modules/clients/templates/clients/management.html
T
2026-08-26 21:53:16 +05:30

268 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-5">
<div class="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-brand-600">Clients · Unified Management</div>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Client Management</h1>
<p class="mt-1 text-sm text-slate-500">Compact Client → Business → Branch → Registration management using the existing ERP masters and permissions.</p>
</div>
<div class="flex flex-wrap gap-2">
{% if can_create %}<a href="/clients/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">+ Add Client</a>{% endif %}
<a href="/clients" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Existing Client List</a>
</div>
</div>
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-6">
{% for label, value in [
('Clients', counters.clients), ('Businesses', counters.businesses), ('Branches', counters.branches),
('Registrations', counters.registrations), ('GSTINs', counters.gstin), ('TANs', counters.tan)
] %}
<div class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
<div class="text-xs font-semibold uppercase text-slate-500">{{ label }}</div>
<div class="mt-1 text-2xl font-semibold text-slate-900">{{ value }}</div>
</div>
{% endfor %}
</div>
<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 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>
<select name="status" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
<option value="">All</option>
{% for value in ['active','inactive','archived'] %}<option value="{{ value }}" {% if status|lower == value %}selected{% endif %}>{{ value|title }}</option>{% endfor %}
</select>
</div>
<div>
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Client type</label>
<select name="client_type" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
<option value="">All</option>
{% for value in client_types %}<option value="{{ value }}" {% if client_type == value %}selected{% endif %}>{{ value }}</option>{% endfor %}
</select>
</div>
<div>
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Registration</label>
<select name="registration_type" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
<option value="">All</option>
{% for rt in registration_types %}<option value="{{ rt.code }}" {% if registration_type|lower == rt.code|lower %}selected{% endif %}>{{ rt.code }} · {{ rt.name }}</option>{% endfor %}
</select>
</div>
<div>
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Reg. status</label>
<select name="registration_status" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
<option value="">All</option>
{% for value in registration_statuses %}<option value="{{ value }}" {% if registration_status == value %}selected{% endif %}>{{ value|title }}</option>{% endfor %}
</select>
</div>
</div>
<div class="mt-3 flex flex-wrap items-center justify-between gap-3">
<div class="text-xs text-slate-500">Search covers client name/code, business/trade name, PAN, GSTIN, TAN, CIN/LLPIN, branch, mobile and email.</div>
<div class="flex gap-2">
<a href="/clients/management" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700">Clear</a>
<button class="rounded-xl bg-slate-900 px-5 py-2 text-sm font-semibold text-white">Apply</button>
</div>
</div>
</form>
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-soft">
<div class="flex items-center justify-between border-b border-slate-200 px-4 py-3">
<div>
<div class="font-semibold text-slate-900">{{ total }} client{{ '' if total == 1 else 's' }}</div>
<div class="text-xs text-slate-500">Expand only the client you need. Existing detail/edit/business-structure pages remain available.</div>
</div>
<div class="text-xs font-medium text-slate-500">Page {{ page }} of {{ pages }}</div>
</div>
{% for node in hierarchy %}
{% set client = node.client %}
<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">
<span class="mt-0.5 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-blue-50 text-blue-700 transition group-open:rotate-90">›</span>
<div class="min-w-0">
<div class="truncate font-semibold text-blue-700">{{ client.client_name }}</div>
<div class="mt-0.5 flex flex-wrap gap-x-2 text-xs text-slate-500">
<span>{{ client.client_code }}</span>
{% if client.trade_name and client.trade_name != client.client_name %}<span>· {{ client.trade_name }}</span>{% endif %}
{% if client.pan %}<span>· PAN {{ client.pan }}</span>{% endif %}
</div>
</div>
</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.branch_count }}</span><span class="ml-1 text-xs text-slate-500">branch</span></div>
<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>
<div class="border-t border-slate-100 bg-slate-50/70 px-4 py-4">
{% if node.businesses %}
<div class="space-y-3">
{% for business_node in node.businesses %}
{% set business = business_node.row %}
<div class="overflow-hidden rounded-xl border border-slate-200 bg-white">
<div class="flex flex-col gap-2 border-b border-slate-100 px-4 py-3 md:flex-row md:items-center md:justify-between">
<div>
<div class="flex flex-wrap items-center gap-2">
<span class="text-sm font-semibold text-slate-900">{{ business.business_name }}</span>
{% if business.is_primary %}<span class="rounded-full bg-emerald-50 px-2 py-0.5 text-[11px] font-semibold text-emerald-700">Primary Business</span>{% endif %}
{% if not business.is_active %}<span class="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-semibold text-slate-500">Inactive</span>{% 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 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 %}
{% set branch = branch_node.row %}
<div class="border-b border-slate-100 px-5 py-3 last:border-b-0">
<div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div>
<span class="text-sm font-medium text-slate-800">↳ {{ branch.branch_name }}</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 %}
</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">
{% for reg, rt in branch_node.registrations %}
<div class="flex items-center justify-between rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<span class="rounded-md bg-white px-2 py-0.5 text-[11px] font-bold text-slate-700 ring-1 ring-slate-200">{{ rt.code }}</span>
<span class="truncate text-sm font-medium text-slate-800">{{ reg.registration_number }}</span>
<span class="rounded-full px-2 py-0.5 text-[11px] font-semibold {% if reg.status|lower in ['active','valid','registered'] %}bg-emerald-50 text-emerald-700{% else %}bg-slate-100 text-slate-600{% endif %}">{{ reg.status|title }}</span>
</div>
{% if reg.trade_name or reg.legal_name %}<div class="mt-0.5 truncate text-xs text-slate-500">{{ reg.trade_name or reg.legal_name }}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
{% if business_node.registrations %}
<div class="px-5 py-3">
<div class="mb-2 text-xs font-semibold uppercase text-slate-500">Business-level registrations</div>
<div class="grid gap-2 xl:grid-cols-2">
{% for reg, rt in business_node.registrations %}
<div class="rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm">
<span class="mr-2 rounded-md bg-white px-2 py-0.5 text-[11px] font-bold ring-1 ring-slate-200">{{ rt.code }}</span>
<span class="font-medium">{{ reg.registration_number }}</span>
<span class="ml-2 text-xs text-slate-500">{{ reg.trade_name or reg.legal_name or '' }}</span>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<div class="rounded-xl border border-dashed border-slate-300 bg-white px-4 py-5 text-center text-sm text-slate-500">
No Business/Business Unit has been created for this client.
{% if can_edit %}<a href="/clients/{{ client.id }}/business-structure" class="ml-1 font-semibold text-brand-700">Create business structure</a>{% endif %}
</div>
{% endif %}
{% if node.unassigned_registrations %}
<div class="mt-3 rounded-xl border border-amber-200 bg-amber-50/60 p-4">
<div class="text-xs font-semibold uppercase text-amber-800">Client-level / unassigned registrations</div>
<div class="mt-2 grid gap-2 xl:grid-cols-2">
{% for reg, rt in node.unassigned_registrations %}
<div class="rounded-lg border border-amber-200 bg-white px-3 py-2 text-sm">
<span class="mr-2 rounded-md bg-amber-50 px-2 py-0.5 text-[11px] font-bold text-amber-800">{{ rt.code }}</span>
<span class="font-medium">{{ reg.registration_number }}</span>
<span class="ml-2 text-xs text-slate-500">{{ reg.trade_name or reg.legal_name or '' }}</span>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</details>
{% else %}
<div class="px-6 py-16 text-center">
<div class="text-base font-semibold text-slate-700">No clients match the selected filters.</div>
<div class="mt-1 text-sm text-slate-500">Clear one or more filters and try again.</div>
</div>
{% endfor %}
</div>
{% if pages > 1 %}
<div class="flex flex-col gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
<div class="text-sm text-slate-500">Showing {{ ((page - 1) * per_page) + 1 if total else 0 }}–{{ [page * per_page, total]|min }} of {{ total }}</div>
<div class="flex flex-wrap items-center gap-2">
{% set qs = 'q=' ~ (q|urlencode) ~ '&status=' ~ (status|urlencode) ~ '&client_type=' ~ (client_type|urlencode) ~ '&registration_type=' ~ (registration_type|urlencode) ~ '&registration_status=' ~ (registration_status|urlencode) ~ '&per_page=' ~ per_page %}
{% if page > 1 %}<a href="/clients/management?{{ qs }}&page={{ page - 1 }}" class="rounded-lg border border-slate-300 px-3 py-1.5 text-sm">Previous</a>{% endif %}
<span class="px-2 text-sm font-medium text-slate-700">{{ page }} / {{ pages }}</span>
{% if page < pages %}<a href="/clients/management?{{ qs }}&page={{ page + 1 }}" class="rounded-lg border border-slate-300 px-3 py-1.5 text-sm">Next</a>{% endif %}
</div>
</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 %}