Add partner operations dashboard v1
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<section class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-brand-600">Branch Operations Control Centre</p>
|
||||
<h1 class="mt-2 text-2xl font-bold text-slate-900">Partner Operations Dashboard</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
Monitor branch clients, due work, overdue tasks, review queue, team workload, billing and operational reports.
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-slate-500">
|
||||
Scope: {{ overview.tenant.name if overview.tenant else 'Tenant' }}{% if overview.branch %} · {{ overview.branch.name }}{% else %} · All permitted branches{% endif %}{% if overview.financial_year %} · FY {{ overview.financial_year }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/partner/reviews" class="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft hover:bg-brand-700">Review Board</a>
|
||||
<a href="/clients" class="rounded-2xl border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Clients</a>
|
||||
<a href="/billing" class="rounded-2xl border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Billing</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% set tabs = [
|
||||
('overview','Overview'),
|
||||
('branch-work','Branch Work'),
|
||||
('clients','Clients'),
|
||||
('staff','Staff'),
|
||||
('review','Review'),
|
||||
('billing','Billing'),
|
||||
('reports','Reports'),
|
||||
('wizards','Wizards')
|
||||
] %}
|
||||
<section class="rounded-3xl border border-slate-200 bg-white p-2 shadow-soft">
|
||||
<div class="flex gap-2 overflow-x-auto" id="partner-dashboard-tabs">
|
||||
{% for code, label in tabs %}
|
||||
<button type="button" data-tab="{{ code }}" class="partner-dashboard-tab whitespace-nowrap rounded-2xl px-4 py-2 text-sm font-semibold transition {% if active_tab == code %}bg-brand-600 text-white{% else %}text-slate-600 hover:bg-slate-100{% endif %}">{{ label }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="partner-dashboard-panel">
|
||||
{% include 'modules/partner_dashboard/templates/partner_dashboard/partials/' ~ (active_tab|replace('-', '_')) ~ '.html' ignore missing %}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const panel = document.getElementById('partner-dashboard-panel');
|
||||
const buttons = Array.from(document.querySelectorAll('.partner-dashboard-tab'));
|
||||
async function loadTab(tab) {
|
||||
buttons.forEach(btn => {
|
||||
const active = btn.dataset.tab === tab;
|
||||
btn.classList.toggle('bg-brand-600', active);
|
||||
btn.classList.toggle('text-white', active);
|
||||
btn.classList.toggle('text-slate-600', !active);
|
||||
btn.classList.toggle('hover:bg-slate-100', !active);
|
||||
});
|
||||
panel.innerHTML = '<div class="rounded-3xl border border-slate-200 bg-white p-8 text-center text-sm text-slate-500 shadow-soft">Loading...</div>';
|
||||
try {
|
||||
const res = await fetch('/partner/dashboard/tab/' + encodeURIComponent(tab), {headers: {'X-Requested-With':'fetch'}});
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
panel.innerHTML = await res.text();
|
||||
history.replaceState(null, '', '/partner/dashboard?tab=' + encodeURIComponent(tab));
|
||||
} catch (err) {
|
||||
panel.innerHTML = '<div class="rounded-3xl border border-red-200 bg-red-50 p-6 text-sm font-semibold text-red-700">Unable to load tab. Please refresh the page.</div>';
|
||||
}
|
||||
}
|
||||
buttons.forEach(btn => btn.addEventListener('click', () => loadTab(btn.dataset.tab)));
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="space-y-6">
|
||||
<div class="grid gap-4 md:grid-cols-3"><div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft"><div class="text-xs uppercase tracking-wide text-slate-400">Invoices Listed</div><div class="mt-2 text-3xl font-bold">{{ billing.invoice_count }}</div></div><div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft"><div class="text-xs uppercase tracking-wide text-slate-400">Draft Invoices</div><div class="mt-2 text-3xl font-bold">{{ billing.draft_count }}</div></div><div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft"><div class="text-xs uppercase tracking-wide text-slate-400">Outstanding</div><div class="mt-2 text-3xl font-bold">₹ {{ billing.outstanding }}</div></div></div>
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft"><div class="flex items-center justify-between"><div><h2 class="text-lg font-semibold text-slate-900">Billing Control</h2><p class="text-sm text-slate-500">Recent invoices in the active branch/FY scope.</p></div><a href="/billing" class="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Open Billing</a></div>
|
||||
<div class="mt-5 overflow-x-auto"><table class="min-w-full text-left text-sm"><thead class="text-xs uppercase tracking-wide text-slate-400"><tr><th class="px-3 py-2">Invoice</th><th class="px-3 py-2">Client</th><th class="px-3 py-2">Date</th><th class="px-3 py-2">Total</th><th class="px-3 py-2">Balance</th><th class="px-3 py-2">Status</th></tr></thead><tbody class="divide-y divide-slate-100">{% for inv in billing.invoices %}<tr><td class="px-3 py-3 font-semibold">{{ inv.invoice_no }}</td><td class="px-3 py-3">{{ inv.client_legal_name or inv.client_trade_name or inv.client_id }}</td><td class="px-3 py-3">{{ inv.invoice_date }}</td><td class="px-3 py-3">₹ {{ inv.total_amount }}</td><td class="px-3 py-3">₹ {{ inv.balance_amount }}</td><td class="px-3 py-3"><span class="rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold">{{ inv.status }}</span></td></tr>{% else %}<tr><td colspan="6" class="px-3 py-8 text-center text-slate-500">No billing invoices found in current scope.</td></tr>{% endfor %}</tbody></table></div></div>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div><h2 class="text-lg font-semibold text-slate-900">Branch Work Status</h2><p class="text-sm text-slate-500">Due dates, assignment and status for the current partner scope.</p></div>
|
||||
<a href="/work" class="rounded-2xl border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Open Work Tracker</a>
|
||||
</div>
|
||||
<div class="mt-5 overflow-x-auto">
|
||||
<table class="min-w-full text-left text-sm">
|
||||
<thead class="text-xs uppercase tracking-wide text-slate-400"><tr><th class="px-3 py-2">Client</th><th class="px-3 py-2">Service / Task</th><th class="px-3 py-2">Due</th><th class="px-3 py-2">Assigned</th><th class="px-3 py-2">Status</th><th class="px-3 py-2">Action</th></tr></thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for item in branch_work %}
|
||||
<tr class="hover:bg-slate-50"><td class="px-3 py-3"><div class="font-semibold text-slate-900">{{ item.client_name }}</div><div class="text-xs text-slate-500">{{ item.client_code }}</div></td><td class="px-3 py-3"><div>{{ item.service_name }}</div><div class="text-xs text-slate-500">{{ item.task_name }}</div></td><td class="px-3 py-3">{{ item.due_date or '-' }}{% if item.is_overdue %}<span class="ml-2 rounded-full bg-red-100 px-2 py-1 text-xs font-semibold text-red-700">Overdue</span>{% endif %}</td><td class="px-3 py-3">{{ item.assigned_to }}</td><td class="px-3 py-3"><span class="rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-700">{{ item.status_label }}</span></td><td class="px-3 py-3"><a href="{{ item.href }}" class="font-semibold text-brand-700">Open</a></td></tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="px-3 py-8 text-center text-slate-500">No branch tasks found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<div class="flex items-center justify-between"><div><h2 class="text-lg font-semibold text-slate-900">Branch Clients</h2><p class="text-sm text-slate-500">Client health by service count, open tasks and overdue items.</p></div><a href="/clients" class="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Manage Clients</a></div>
|
||||
<div class="mt-5 grid gap-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{% for c in clients %}
|
||||
<a href="{{ c.href }}" class="rounded-3xl border border-slate-200 p-5 hover:bg-slate-50">
|
||||
<div class="flex items-start justify-between gap-3"><div><h3 class="font-semibold text-slate-900">{{ c.client_name }}</h3><p class="mt-1 text-xs text-slate-500">{{ c.client_code }}{% if c.gstin %} · {{ c.gstin }}{% elif c.pan %} · {{ c.pan }}{% endif %}</p></div>{% if c.overdue_count > 0 %}<span class="rounded-full bg-red-100 px-2 py-1 text-xs font-semibold text-red-700">{{ c.overdue_count }} overdue</span>{% endif %}</div>
|
||||
<div class="mt-4 grid grid-cols-3 gap-2 text-center text-xs"><div class="rounded-2xl bg-slate-50 p-3"><div class="text-lg font-semibold">{{ c.service_count }}</div><div class="text-slate-500">Services</div></div><div class="rounded-2xl bg-slate-50 p-3"><div class="text-lg font-semibold">{{ c.task_count }}</div><div class="text-slate-500">Tasks</div></div><div class="rounded-2xl bg-slate-50 p-3"><div class="text-lg font-semibold">{{ c.overdue_count }}</div><div class="text-slate-500">Overdue</div></div></div>
|
||||
</a>
|
||||
{% else %}<div class="rounded-3xl border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500 xl:col-span-3">No branch clients found.</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,49 @@
|
||||
<div class="space-y-6">
|
||||
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{% for card in [
|
||||
('Branch Clients', overview.client_count),
|
||||
('Open Tasks', overview.open_task_count),
|
||||
('Overdue', overview.overdue_count),
|
||||
('Review Pending', overview.review_pending_count),
|
||||
('Due Today', overview.due_today_count),
|
||||
('Due This Week', overview.due_week_count),
|
||||
('Client Pending', overview.client_pending_count),
|
||||
('Outstanding', '₹ ' ~ overview.outstanding)
|
||||
] %}
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-400">{{ card[0] }}</div>
|
||||
<div class="mt-2 text-3xl font-bold text-slate-900">{{ card[1] }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-2">
|
||||
<section class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<div class="flex items-center justify-between"><h2 class="font-semibold text-slate-900">Needs Attention</h2><a href="/partner/dashboard?tab=branch-work" class="text-xs font-semibold text-brand-700">View work</a></div>
|
||||
<div class="mt-4 space-y-3">
|
||||
{% for item in overdue[:6] + client_pending[:4] %}
|
||||
<a href="{{ item.href }}" class="block rounded-2xl border border-slate-200 p-4 hover:bg-slate-50">
|
||||
<div class="flex items-start justify-between gap-3"><div><div class="font-semibold text-slate-900">{{ item.task_name }}</div><div class="mt-1 text-xs text-slate-500">{{ item.client_name }} · {{ item.service_name }}</div></div><span class="rounded-full bg-red-100 px-2 py-1 text-xs font-semibold text-red-700">{{ item.status_label }}</span></div>
|
||||
<div class="mt-2 text-xs text-slate-500">Due: {{ item.due_date or '-' }} · Assigned: {{ item.assigned_to }}</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-center text-sm text-slate-500">No overdue or blocked branch work in current scope.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<div class="flex items-center justify-between"><h2 class="font-semibold text-slate-900">Review Queue</h2><a href="/partner/reviews" class="text-xs font-semibold text-brand-700">Open review board</a></div>
|
||||
<div class="mt-4 space-y-3">
|
||||
{% for item in review_queue[:8] %}
|
||||
<a href="{{ item.href }}" class="block rounded-2xl border border-slate-200 p-4 hover:bg-slate-50">
|
||||
<div class="font-semibold text-slate-900">{{ item.task_name }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">{{ item.client_name }} · {{ item.service_name }} · {{ item.assigned_to }}</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-center text-sm text-slate-500">No completed tasks waiting for partner review.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{% for report in reports %}
|
||||
<a href="{{ report.href }}" class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft hover:border-brand-300 hover:bg-brand-50/30"><div class="text-xs font-semibold uppercase tracking-wide text-brand-600">{{ report.group }}</div><h3 class="mt-2 font-semibold text-slate-900">{{ report.title }}</h3><p class="mt-2 text-sm text-slate-500">{{ report.desc }}</p><div class="mt-4 text-sm font-semibold text-brand-700">View report →</div></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<div class="flex items-center justify-between"><div><h2 class="text-lg font-semibold text-slate-900">Partner Review Queue</h2><p class="text-sm text-slate-500">Completed work awaiting partner approval, rework decision or final sign-off.</p></div><a href="/partner/reviews" class="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Open Review Board</a></div>
|
||||
<div class="mt-5 grid gap-4 lg:grid-cols-2">
|
||||
{% for item in review_queue %}<a href="{{ item.href }}" class="rounded-3xl border border-slate-200 p-5 hover:bg-slate-50"><div class="flex items-start justify-between gap-3"><div><h3 class="font-semibold text-slate-900">{{ item.task_name }}</h3><p class="mt-1 text-xs text-slate-500">{{ item.client_name }} · {{ item.service_name }}</p></div><span class="rounded-full bg-brand-100 px-2.5 py-1 text-xs font-semibold text-brand-700">Review</span></div><div class="mt-3 text-xs text-slate-500">Prepared by: {{ item.assigned_to }} · Due: {{ item.due_date or '-' }}</div></a>{% else %}<div class="rounded-3xl border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500 lg:col-span-2">No completed tasks waiting for partner review.</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<h2 class="text-lg font-semibold text-slate-900">Branch Staff Workload</h2><p class="mt-1 text-sm text-slate-500">Workload is calculated from current branch task assignments.</p>
|
||||
<div class="mt-5 overflow-x-auto"><table class="min-w-full text-left text-sm"><thead class="text-xs uppercase tracking-wide text-slate-400"><tr><th class="px-3 py-2">Staff</th><th class="px-3 py-2">Active</th><th class="px-3 py-2">Overdue</th><th class="px-3 py-2">Client Pending</th><th class="px-3 py-2">Review</th><th class="px-3 py-2">Load</th></tr></thead><tbody class="divide-y divide-slate-100">
|
||||
{% for s in staff_rows %}<tr class="hover:bg-slate-50"><td class="px-3 py-3"><div class="font-semibold text-slate-900">{{ s.name }}</div><div class="text-xs text-slate-500">{{ s.designation or s.email }}</div></td><td class="px-3 py-3">{{ s.active }}</td><td class="px-3 py-3">{{ s.overdue }}</td><td class="px-3 py-3">{{ s.client_pending }}</td><td class="px-3 py-3">{{ s.review }}</td><td class="px-3 py-3"><span class="rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-700">{{ s.load_status }}</span></td></tr>{% else %}<tr><td colspan="6" class="px-3 py-8 text-center text-slate-500">No branch users found.</td></tr>{% endfor %}
|
||||
</tbody></table></div>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{% for wizard in wizards %}
|
||||
<a href="{{ wizard.href }}" class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft hover:border-brand-300 hover:bg-brand-50/30"><h3 class="font-semibold text-slate-900">{{ wizard.title }}</h3><p class="mt-2 text-sm text-slate-500">{{ wizard.desc }}</p><div class="mt-4 text-sm font-semibold text-brand-700">Open →</div></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
Reference in New Issue
Block a user