Show partner review board by engagement
This commit is contained in:
@@ -4,28 +4,178 @@
|
|||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex flex-col gap-3 rounded-3xl border border-slate-200 bg-white p-5 shadow-soft lg:flex-row lg:items-center lg:justify-between">
|
<div class="flex flex-col gap-3 rounded-3xl border border-slate-200 bg-white p-5 shadow-soft lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div><h2 class="text-xl font-semibold">Partner Review Board</h2><p class="text-sm text-slate-500">Review completed work, blocked tasks, rework and in-progress engagement tasks.</p></div>
|
<div>
|
||||||
<form method="get" class="flex gap-2"><input name="q" value="{{ q or '' }}" placeholder="Search client, task or service" class="w-72 rounded-xl border border-slate-300 px-3 py-2 text-sm"><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Search</button></form>
|
<h2 class="text-xl font-semibold">Partner Review Board</h2>
|
||||||
|
<p class="text-sm text-slate-500">Engagement-level review queue. Task-level review, rework and history remain available inside each engagement.</p>
|
||||||
|
</div>
|
||||||
|
<form method="get" class="flex gap-2">
|
||||||
|
<input name="q" value="{{ q or '' }}" placeholder="Search client, task or service" class="w-72 rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||||
|
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Search</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-4 xl:grid-cols-5">
|
{# Build engagement-level counts without changing the existing backend payload. #}
|
||||||
|
{% set metrics = namespace(pending_review=[], clarification_required=[], rework_sent=[], approved=[], completed=[]) %}
|
||||||
{% for column in payload.columns %}
|
{% for column in payload.columns %}
|
||||||
<section class="min-h-[420px] rounded-3xl border border-slate-200 bg-white p-4 shadow-soft">
|
|
||||||
<div class="mb-4 flex items-start justify-between gap-3"><div><h3 class="font-semibold">{{ column.label }}</h3><p class="text-xs text-slate-500">{{ column.hint }}</p></div><span class="rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold">{{ column.count }}</span></div>
|
|
||||||
<div class="space-y-3">
|
|
||||||
{% for task in column.tasks %}
|
{% for task in column.tasks %}
|
||||||
<div class="rounded-2xl border border-slate-200 p-4">
|
{% if task.subscription_id %}
|
||||||
<div class="flex items-start justify-between gap-3"><div><a href="/work/engagements/{{ task.subscription_id }}" class="font-semibold text-slate-900 hover:text-brand-700">{{ task.task_name }}</a><div class="mt-1 text-xs text-slate-500">{{ task.client_display }}</div></div>{% if task.is_overdue %}<span class="rounded-full bg-red-100 px-2 py-1 text-[11px] font-semibold text-red-700">Overdue</span>{% endif %}</div>
|
{% if column.code == 'pending_review' and task.subscription_id not in metrics.pending_review %}
|
||||||
<div class="mt-2 text-xs text-slate-500">{{ task.engagement_label }}</div>
|
{% set _ = metrics.pending_review.append(task.subscription_id) %}
|
||||||
<div class="mt-3 flex flex-wrap gap-2 text-[11px]"><span class="rounded-full bg-slate-100 px-2 py-1">{{ task.status_label }}</span><span class="rounded-full bg-slate-100 px-2 py-1">{{ task.priority_label }}</span><span class="rounded-full bg-slate-100 px-2 py-1">{{ task.assignee_display }}</span></div>
|
{% elif column.code == 'clarification_required' and task.subscription_id not in metrics.clarification_required %}
|
||||||
<a href="/work/engagements/{{ task.subscription_id }}" class="mt-3 inline-flex rounded-xl border border-slate-300 px-3 py-1.5 text-xs font-semibold text-slate-700 hover:bg-slate-50">Open Review Details</a>
|
{% set _ = metrics.clarification_required.append(task.subscription_id) %}
|
||||||
</div>
|
{% elif column.code == 'rework_sent' and task.subscription_id not in metrics.rework_sent %}
|
||||||
{% else %}
|
{% set _ = metrics.rework_sent.append(task.subscription_id) %}
|
||||||
<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-center text-xs text-slate-500">No items.</div>
|
{% elif column.code == 'approved' and task.subscription_id not in metrics.approved %}
|
||||||
|
{% set _ = metrics.approved.append(task.subscription_id) %}
|
||||||
|
{% elif column.code == 'completed' and task.subscription_id not in metrics.completed %}
|
||||||
|
{% set _ = metrics.completed.append(task.subscription_id) %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
|
||||||
|
<div class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||||
|
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Pending Review</div>
|
||||||
|
<div class="mt-1 text-2xl font-semibold text-slate-900">{{ metrics.pending_review|length }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">Engagements with completed work awaiting review</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||||
|
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Clarification Required</div>
|
||||||
|
<div class="mt-1 text-2xl font-semibold text-slate-900">{{ metrics.clarification_required|length }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">Engagements with blocked tasks</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||||
|
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Rework Sent</div>
|
||||||
|
<div class="mt-1 text-2xl font-semibold text-slate-900">{{ metrics.rework_sent|length }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">Engagements containing rework items</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||||
|
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">In Progress</div>
|
||||||
|
<div class="mt-1 text-2xl font-semibold text-slate-900">{{ metrics.approved|length }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">Engagements currently moving with the team</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||||
|
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Completed</div>
|
||||||
|
<div class="mt-1 text-2xl font-semibold text-slate-900">{{ metrics.completed|length }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">Engagements containing closed tasks</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-soft">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-slate-200">
|
||||||
|
<thead class="bg-slate-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Client / Engagement</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Status</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Task Progress</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Review Items</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Assigned Team</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Priority / Target</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wide text-slate-500">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
{% set board = namespace(seen=[]) %}
|
||||||
|
{% for column in payload.columns %}
|
||||||
|
{% for task in column.tasks %}
|
||||||
|
{% if task.subscription_id and task.subscription_id not in board.seen %}
|
||||||
|
{% set _ = board.seen.append(task.subscription_id) %}
|
||||||
|
{% set stats = namespace(total=0, completed=0, blocked=0, review=0, rework=0, overdue=false, assignees=[]) %}
|
||||||
|
{% for stat_column in payload.columns %}
|
||||||
|
{% for item in stat_column.tasks %}
|
||||||
|
{% if item.subscription_id == task.subscription_id %}
|
||||||
|
{% set stats.total = stats.total + 1 %}
|
||||||
|
{% if (item.status or '')|lower in ['completed', 'not_applicable', 'cancelled'] %}
|
||||||
|
{% set stats.completed = stats.completed + 1 %}
|
||||||
|
{% endif %}
|
||||||
|
{% if (item.status or '')|lower == 'blocked' %}
|
||||||
|
{% set stats.blocked = stats.blocked + 1 %}
|
||||||
|
{% endif %}
|
||||||
|
{% if stat_column.code == 'pending_review' %}
|
||||||
|
{% set stats.review = stats.review + 1 %}
|
||||||
|
{% endif %}
|
||||||
|
{% if stat_column.code == 'rework_sent' %}
|
||||||
|
{% set stats.rework = stats.rework + 1 %}
|
||||||
|
{% endif %}
|
||||||
|
{% if item.is_overdue %}
|
||||||
|
{% set stats.overdue = true %}
|
||||||
|
{% endif %}
|
||||||
|
{% if item.assignee_display and item.assignee_display not in stats.assignees %}
|
||||||
|
{% set _ = stats.assignees.append(item.assignee_display) %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<tr class="align-top hover:bg-slate-50/70">
|
||||||
|
<td class="px-4 py-4 text-sm">
|
||||||
|
<a href="/work/engagements/{{ task.subscription_id }}" class="font-semibold text-slate-900 hover:text-brand-700">{{ task.client_display }}</a>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">{{ task.engagement_label }}</div>
|
||||||
|
<div class="mt-1 text-[11px] text-slate-400">Engagement #{{ task.subscription_id }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 text-sm">
|
||||||
|
{% if column.code == 'pending_review' %}
|
||||||
|
<span class="rounded-full bg-amber-100 px-2.5 py-1 text-xs font-semibold text-amber-700">Pending Review</span>
|
||||||
|
{% elif column.code == 'clarification_required' %}
|
||||||
|
<span class="rounded-full bg-red-100 px-2.5 py-1 text-xs font-semibold text-red-700">Clarification Required</span>
|
||||||
|
{% elif column.code == 'rework_sent' %}
|
||||||
|
<span class="rounded-full bg-orange-100 px-2.5 py-1 text-xs font-semibold text-orange-700">Rework Sent</span>
|
||||||
|
{% elif column.code == 'approved' %}
|
||||||
|
<span class="rounded-full bg-blue-100 px-2.5 py-1 text-xs font-semibold text-blue-700">In Progress</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-semibold text-emerald-700">Completed</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if stats.overdue %}
|
||||||
|
<div class="mt-2"><span class="rounded-full bg-red-50 px-2 py-1 text-[11px] font-semibold text-red-700">Overdue item</span></div>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 text-sm">
|
||||||
|
<div class="font-semibold text-slate-900">{{ stats.completed }} / {{ stats.total }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">closed task{{ '' if stats.completed == 1 else 's' }} / visible task{{ '' if stats.total == 1 else 's' }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 text-sm">
|
||||||
|
<div class="flex flex-wrap gap-1.5">
|
||||||
|
{% if stats.review %}<span class="rounded-full bg-amber-50 px-2 py-1 text-[11px] font-semibold text-amber-700">{{ stats.review }} review</span>{% endif %}
|
||||||
|
{% if stats.blocked %}<span class="rounded-full bg-red-50 px-2 py-1 text-[11px] font-semibold text-red-700">{{ stats.blocked }} blocked</span>{% endif %}
|
||||||
|
{% if stats.rework %}<span class="rounded-full bg-orange-50 px-2 py-1 text-[11px] font-semibold text-orange-700">{{ stats.rework }} rework</span>{% endif %}
|
||||||
|
{% if not stats.review and not stats.blocked and not stats.rework %}<span class="text-xs text-slate-500">No review exception</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 text-sm text-slate-700">
|
||||||
|
{% for assignee in stats.assignees[:3] %}
|
||||||
|
<div>{{ assignee }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if stats.assignees|length > 3 %}
|
||||||
|
<div class="mt-1 text-xs text-slate-500">+{{ stats.assignees|length - 3 }} more</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if not stats.assignees %}<span class="text-slate-500">Unassigned</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 text-sm">
|
||||||
|
<div class="text-xs font-semibold text-slate-700">{{ task.priority_label }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">{% if task.internal_target_date %}Target {{ task.internal_target_date }}{% else %}No task target{% endif %}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 text-right">
|
||||||
|
<a href="/work/engagements/{{ task.subscription_id }}" class="inline-flex rounded-xl border border-slate-300 px-3 py-2 text-xs font-semibold text-slate-700 hover:bg-slate-50">Open Review</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if board.seen|length == 0 %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="px-6 py-12 text-center text-sm text-slate-500">No engagement review items found.</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endfor %}
|
|
||||||
|
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-500">
|
||||||
|
Review remains task-level inside the engagement. This board only groups the existing task records into one engagement row for easier partner navigation.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user