Upgrade client dashboard to v2 grouped ajax portal

This commit is contained in:
A R R R Associates
2026-07-06 11:20:37 +05:30
parent 770ba9af07
commit dcb352d6c3
6 changed files with 68 additions and 23 deletions
@@ -6,13 +6,14 @@
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between"> <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div> <div>
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-brand-100">Client Portal</p> <p class="text-xs font-semibold uppercase tracking-[0.22em] text-brand-100">Client Portal</p>
<h2 class="mt-2 text-2xl font-semibold">My Compliance & Firm Communication</h2> <h2 class="mt-2 text-2xl font-semibold">My Compliance Workspace</h2>
<p class="mt-2 max-w-3xl text-sm text-brand-100">Track pending actions, service status, documents, messages, bills and firm updates from one place.</p> <p class="mt-2 max-w-3xl text-sm text-brand-100">View pending actions, service progress, documents, invoices, payments and firm messages from one clean dashboard.</p>
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{% if client_row %} {% if client_row %}
<a href="/client/profile" class="rounded-xl bg-white px-4 py-2 text-sm font-semibold text-brand-700 hover:bg-brand-50">Update My Profile</a> <a href="/client/documents" class="rounded-xl bg-white px-4 py-2 text-sm font-semibold text-brand-700 hover:bg-brand-50">Upload Documents</a>
<a href="/client/documents" class="rounded-xl border border-white/30 px-4 py-2 text-sm font-semibold text-white hover:bg-white/10">Upload Documents</a> <a href="/client/messages" class="rounded-xl border border-white/30 px-4 py-2 text-sm font-semibold text-white hover:bg-white/10">Messages</a>
<a href="/client/profile" class="rounded-xl border border-white/30 px-4 py-2 text-sm font-semibold text-white hover:bg-white/10">Profile</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -25,11 +26,9 @@
{% else %} {% else %}
{% set client_tabs = [ {% set client_tabs = [
('overview','Overview'), ('overview','Overview'),
('pending','Pending From Me'), ('action-centre','Action Centre'),
('services','Services'), ('services-documents','Services & Documents'),
('documents','Documents'), ('billing-messages','Billing & Messages'),
('billing','Invoices & Payments'),
('messages','Messages'),
('reports','Reports') ('reports','Reports')
] %} ] %}
@@ -76,6 +75,7 @@
} }
} }
window.loadClientDashboardTab = loadTab;
buttons.forEach((btn) => btn.addEventListener('click', () => loadTab(btn.dataset.tab || 'overview'))); buttons.forEach((btn) => btn.addEventListener('click', () => loadTab(btn.dataset.tab || 'overview')));
})(); })();
</script> </script>
@@ -0,0 +1,12 @@
<div class="space-y-6">
<div class="grid gap-4 md:grid-cols-3">
<button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('pending')" class="af-metric-card border-amber-200 bg-amber-50 text-left hover:border-amber-300"><div class="text-xs font-semibold uppercase text-amber-700">Pending From Me</div><div class="mt-2 text-3xl font-semibold text-amber-700">{{ pending_from_client or 0 }}</div><div class="mt-1 text-xs text-amber-700">Documents / replies required</div></button>
<button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('messages')" class="af-metric-card text-left hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Firm Messages</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ client_visible_comments|length if client_visible_comments else 0 }}</div><div class="mt-1 text-xs text-slate-500">Recent messages</div></button>
<a href="/client/documents" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Documents</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ recent_documents|length if recent_documents else 0 }}</div><div class="mt-1 text-xs text-slate-500">Recent uploads / shared files</div></a>
</div>
<div class="grid gap-6 xl:grid-cols-2">
<div>{% include "modules/clients/templates/clients/portal_partials/pending.html" %}</div>
<div>{% include "modules/clients/templates/clients/portal_partials/messages.html" %}</div>
</div>
</div>
@@ -0,0 +1,11 @@
<div class="space-y-6">
<section class="grid gap-4 md:grid-cols-3">
<button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('billing')" class="af-metric-card border-amber-200 bg-amber-50 text-left hover:border-amber-300"><div class="text-xs font-semibold uppercase text-amber-700">Outstanding</div><div class="mt-2 text-2xl font-semibold text-amber-700">₹ {{ '%.2f'|format(billing_outstanding_amount or 0) }}</div><div class="mt-1 text-xs text-amber-700">{{ billing_open_count or 0 }} open bill(s)</div></button>
<a href="/client/billing" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Total Bills</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ billing_total_count or 0 }}</div><div class="mt-1 text-xs text-slate-500">Invoices & receipts</div></a>
<button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('messages')" class="af-metric-card text-left hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Messages</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ client_visible_comments|length if client_visible_comments else 0 }}</div><div class="mt-1 text-xs text-slate-500">Firm communication</div></button>
</section>
<div class="grid gap-6 xl:grid-cols-2">
<div>{% include "modules/clients/templates/clients/portal_partials/billing.html" %}</div>
<div>{% include "modules/clients/templates/clients/portal_partials/messages.html" %}</div>
</div>
</div>
@@ -1,16 +1,19 @@
<div class="space-y-6"> <div class="space-y-6">
<section class="grid gap-4 md:grid-cols-2 xl:grid-cols-5"> <section class="grid gap-4 md:grid-cols-2 xl:grid-cols-5">
<a href="/client/compliance" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Active Services</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ total_engagements or 0 }}</div><div class="mt-1 text-xs text-slate-500">Services / filings</div></a> <a href="/client/compliance" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Active Services</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ total_engagements or 0 }}</div><div class="mt-1 text-xs text-slate-500">Services handled by firm</div></a>
<button type="button" data-tab="pending" class="client-dashboard-tab af-metric-card border-amber-200 bg-amber-50 text-left hover:border-amber-300"><div class="text-xs font-semibold uppercase text-amber-700">Pending From Me</div><div class="mt-2 text-3xl font-semibold text-amber-700">{{ pending_from_client or 0 }}</div><div class="mt-1 text-xs text-amber-700">Action required</div></button> <button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('action-centre')" class="af-metric-card border-amber-200 bg-amber-50 text-left hover:border-amber-300"><div class="text-xs font-semibold uppercase text-amber-700">Pending From Me</div><div class="mt-2 text-3xl font-semibold text-amber-700">{{ pending_from_client or 0 }}</div><div class="mt-1 text-xs text-amber-700">Action required</div></button>
<a href="/client/compliance" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">With Firm</div><div class="mt-2 text-3xl font-semibold text-brand-700">{{ with_firm or 0 }}</div><div class="mt-1 text-xs text-slate-500">Being handled</div></a> <a href="/client/compliance" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">With Firm</div><div class="mt-2 text-3xl font-semibold text-brand-700">{{ with_firm or 0 }}</div><div class="mt-1 text-xs text-slate-500">Being processed</div></a>
<a href="/client/documents" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Recent Documents</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ recent_documents|length if recent_documents else 0 }}</div><div class="mt-1 text-xs text-slate-500">Latest documents</div></a> <button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('services-documents')" class="af-metric-card text-left hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Recent Documents</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ recent_documents|length if recent_documents else 0 }}</div><div class="mt-1 text-xs text-slate-500">Latest files</div></button>
<a href="/client/billing" class="af-metric-card border-amber-200 bg-amber-50 hover:border-amber-300"><div class="text-xs font-semibold uppercase text-amber-700">Outstanding Bills</div><div class="mt-2 text-2xl font-semibold text-amber-700">₹ {{ '%.2f'|format(billing_outstanding_amount or 0) }}</div><div class="mt-1 text-xs text-amber-700">{{ billing_open_count or 0 }} open bill(s)</div></a> <button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('billing-messages')" class="af-metric-card border-amber-200 bg-amber-50 text-left hover:border-amber-300"><div class="text-xs font-semibold uppercase text-amber-700">Outstanding</div><div class="mt-2 text-2xl font-semibold text-amber-700">₹ {{ '%.2f'|format(billing_outstanding_amount or 0) }}</div><div class="mt-1 text-xs text-amber-700">{{ billing_open_count or 0 }} open bill(s)</div></button>
</section> </section>
<section class="grid gap-6 xl:grid-cols-[minmax(0,1fr)_380px]"> <section class="grid gap-6 xl:grid-cols-[minmax(0,1fr)_380px]">
<div class="space-y-6"> <div class="space-y-6">
<div class="af-card"> <div class="af-card">
<div class="flex items-center justify-between gap-3"><div><h3 class="text-lg font-semibold text-slate-900">Needs Your Attention</h3><p class="mt-1 text-sm text-slate-500">Open items where the firm may need documents, replies or confirmation from you.</p></div><a href="/client/compliance" class="af-btn af-btn-primary">View Services</a></div> <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div><h3 class="text-lg font-semibold text-slate-900">Needs Your Attention</h3><p class="mt-1 text-sm text-slate-500">Documents, replies or confirmations currently expected from you.</p></div>
<button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('pending')" class="af-btn af-btn-primary">View Pending Items</button>
</div>
<div class="mt-5 space-y-3"> <div class="mt-5 space-y-3">
{% set shown = namespace(count=0) %} {% set shown = namespace(count=0) %}
{% for task in task_rows or [] %} {% for task in task_rows or [] %}
@@ -25,15 +28,18 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if shown.count == 0 %} {% if shown.count == 0 %}
<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No urgent action is pending from you.</div> <div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No immediate action is pending from you.</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="af-card"> <div class="af-card">
<div class="flex items-center justify-between gap-3"><div><h3 class="text-lg font-semibold text-slate-900">Upcoming Compliance</h3><p class="mt-1 text-sm text-slate-500">Nearest active service due dates.</p></div><a href="/client/compliance" class="text-sm font-semibold text-brand-700 hover:underline">Open Compliance</a></div> <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div class="mt-4 space-y-3"> <div><h3 class="text-lg font-semibold text-slate-900">Upcoming Services & Due Dates</h3><p class="mt-1 text-sm text-slate-500">Important service deadlines visible to your client login.</p></div>
{% for row in due_soon_engagements[:6] %} <a href="/client/compliance" class="af-btn af-btn-secondary">Full Compliance Page</a>
</div>
<div class="mt-5 space-y-3">
{% for row in engagements[:6] if engagements is defined %}
<a href="/client/engagements/{{ row.id }}" class="block rounded-2xl border border-slate-200 p-4 hover:bg-slate-50"> <a href="/client/engagements/{{ row.id }}" class="block rounded-2xl border border-slate-200 p-4 hover:bg-slate-50">
<div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between"> <div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div><div class="font-semibold text-slate-900">{{ row.catalogue.service_name if row.catalogue else 'Service' }}</div><div class="text-xs text-slate-500">FY {{ row.financial_year }}{% if row.assessment_year %} • AY {{ row.assessment_year }}{% endif %}</div></div> <div><div class="font-semibold text-slate-900">{{ row.catalogue.service_name if row.catalogue else 'Service' }}</div><div class="text-xs text-slate-500">FY {{ row.financial_year }}{% if row.assessment_year %} • AY {{ row.assessment_year }}{% endif %}</div></div>
@@ -41,7 +47,7 @@
</div> </div>
</a> </a>
{% else %} {% else %}
<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No upcoming compliance due dates found.</div> <div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No upcoming service due dates found.</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@@ -62,7 +68,7 @@
<div class="af-card"> <div class="af-card">
<h3 class="font-semibold text-slate-900">Quick Actions</h3> <h3 class="font-semibold text-slate-900">Quick Actions</h3>
<div class="mt-4 grid gap-2 text-sm"> <div class="mt-4 grid gap-2 text-sm">
<a href="/client/compliance" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">View My Services</a> <button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('action-centre')" class="rounded-xl border border-slate-200 px-4 py-3 text-left font-semibold text-slate-700 hover:bg-slate-50">Pending From Me</button>
<a href="/client/documents" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">Upload / View Documents</a> <a href="/client/documents" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">Upload / View Documents</a>
<a href="/client/messages" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">Messages from Firm</a> <a href="/client/messages" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">Messages from Firm</a>
<a href="/client/billing" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">View Bills & Receipts</a> <a href="/client/billing" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">View Bills & Receipts</a>
@@ -0,0 +1,14 @@
<div class="space-y-6">
<section class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div><h3 class="text-lg font-semibold text-slate-900">Services & Documents</h3><p class="mt-1 text-sm text-slate-500">Track services handled by the firm and documents visible to you.</p></div>
<div class="flex flex-wrap gap-2">
<button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('services')" class="af-btn af-btn-secondary">Services</button>
<button type="button" onclick="window.loadClientDashboardTab && window.loadClientDashboardTab('documents')" class="af-btn af-btn-secondary">Documents</button>
<a href="/client/documents" class="af-btn af-btn-primary">Upload Documents</a>
</div>
</div>
</section>
{% include "modules/clients/templates/clients/portal_partials/services.html" %}
{% include "modules/clients/templates/clients/portal_partials/documents.html" %}
</div>
+4 -2
View File
@@ -916,15 +916,17 @@ def _active_financial_year(request: Request) -> str | None:
CLIENT_PORTAL_TABS = { CLIENT_PORTAL_TABS = {
"overview": "modules/clients/templates/clients/portal_partials/overview.html", "overview": "modules/clients/templates/clients/portal_partials/overview.html",
"action-centre": "modules/clients/templates/clients/portal_partials/action_centre.html",
"services-documents": "modules/clients/templates/clients/portal_partials/services_documents.html",
"billing-messages": "modules/clients/templates/clients/portal_partials/billing_messages.html",
"reports": "modules/clients/templates/clients/portal_partials/reports.html",
"pending": "modules/clients/templates/clients/portal_partials/pending.html", "pending": "modules/clients/templates/clients/portal_partials/pending.html",
"services": "modules/clients/templates/clients/portal_partials/services.html", "services": "modules/clients/templates/clients/portal_partials/services.html",
"documents": "modules/clients/templates/clients/portal_partials/documents.html", "documents": "modules/clients/templates/clients/portal_partials/documents.html",
"billing": "modules/clients/templates/clients/portal_partials/billing.html", "billing": "modules/clients/templates/clients/portal_partials/billing.html",
"messages": "modules/clients/templates/clients/portal_partials/messages.html", "messages": "modules/clients/templates/clients/portal_partials/messages.html",
"reports": "modules/clients/templates/clients/portal_partials/reports.html",
} }
def _client_dashboard_payload(db, client_row, financial_year: str | None = None) -> dict: def _client_dashboard_payload(db, client_row, financial_year: str | None = None) -> dict:
summary = build_client_portal_summary(db, client_row, financial_year=financial_year) summary = build_client_portal_summary(db, client_row, financial_year=financial_year)
billing_summary = build_client_billing_summary(db, client_row, financial_year=financial_year) billing_summary = build_client_billing_summary(db, client_row, financial_year=financial_year)