Add bulk firm selection to service catalogue

This commit is contained in:
A R R R Associates
2026-08-03 17:23:38 +05:30
parent 6224b721c9
commit 34c258e26e
2 changed files with 226 additions and 1 deletions
@@ -19,6 +19,20 @@
</div>
</div>
{% if request.query_params.get('bulk_action') == 'enable' or request.query_params.get('bulk_action') == 'disable' %}
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-800">
Bulk update completed: <span class="font-semibold">{{ request.query_params.get('bulk_updated', '0') }}</span> changed,
<span class="font-semibold">{{ request.query_params.get('bulk_unchanged', '0') }}</span> already in the requested state,
and <span class="font-semibold">{{ request.query_params.get('bulk_skipped', '0') }}</span> skipped.
</div>
{% elif request.query_params.get('bulk_action') == 'no_selection' %}
<div class="rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">Select at least one service before applying a bulk action.</div>
{% elif request.query_params.get('bulk_action') == 'invalid_branch' %}
<div class="rounded-2xl border border-red-200 bg-red-50 p-4 text-sm text-red-800">The selected default branch is not available for the active firm.</div>
{% elif request.query_params.get('bulk_action') == 'invalid' %}
<div class="rounded-2xl border border-red-200 bg-red-50 p-4 text-sm text-red-800">The requested bulk action is invalid.</div>
{% endif %}
{% if can_manage_firm_services %}
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-800">
Select the services your firm provides. Once selected, you can open <span class="font-semibold">Firm Task Templates</span> and customise tasks for your firm.
@@ -59,10 +73,41 @@
</div>
</form>
{% if can_manage_firm_services %}
<form id="bulk-service-form" method="post" action="/services/catalogue/bulk-selection" class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="return_q" value="{{ q }}">
<input type="hidden" name="return_category_id" value="{{ category_id or '' }}">
<input type="hidden" name="return_recurrence_type" value="{{ recurrence_type }}">
<input type="hidden" name="return_engagement_type" value="{{ engagement_type }}">
<input type="hidden" name="return_page" value="{{ page }}">
<input type="hidden" name="return_per_page" value="{{ per_page }}">
<div class="flex flex-wrap items-center gap-3">
<div class="mr-auto text-sm font-medium text-slate-700"><span id="bulk-selected-count">0</span> services selected</div>
{% if branches %}
<select name="default_branch_id" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
<option value="">Keep existing branch / no default for new selections</option>
{% for branch in branches %}<option value="{{ branch.id }}">{{ branch.name }}</option>{% endfor %}
</select>
{% else %}
<input type="hidden" name="default_branch_id" value="">
{% endif %}
<button type="submit" name="bulk_action" value="enable" class="rounded-xl bg-emerald-600 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-700">Select for Firm</button>
<button type="submit" name="bulk_action" value="disable" class="rounded-xl border border-amber-300 px-4 py-2 text-sm font-semibold text-amber-700 hover:bg-amber-50">Disable for Firm</button>
<button type="button" id="bulk-clear-selection" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Clear Selection</button>
</div>
</form>
{% endif %}
<div class="overflow-hidden rounded-2xl bg-white shadow-soft">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50">
<tr>
{% if can_manage_firm_services %}
<th class="w-12 px-4 py-3 text-left">
<input id="bulk-select-all-visible" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-brand-600" aria-label="Select all services visible on this page">
</th>
{% endif %}
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Code</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Name</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Category</th>
@@ -75,6 +120,11 @@
{% for row in rows %}
{% set selection = firm_selection_by_catalogue.get(row.id) if firm_selection_by_catalogue else None %}
<tr>
{% if can_manage_firm_services %}
<td class="px-4 py-3 align-top">
<input type="checkbox" name="service_ids" value="{{ row.id }}" form="bulk-service-form" class="bulk-service-checkbox h-4 w-4 rounded border-slate-300 text-brand-600" aria-label="Select {{ row.service_name }}">
</td>
{% endif %}
<td class="px-4 py-3 text-sm font-medium text-slate-900">{{ row.service_code }}</td>
<td class="px-4 py-3 text-sm text-slate-700">
<div class="font-medium text-slate-900">{{ row.service_name }}</div>
@@ -118,7 +168,7 @@
</td>
</tr>
{% else %}
<tr><td colspan="6" class="px-4 py-8 text-center text-sm text-slate-500">No catalogue services found.</td></tr>
<tr><td colspan="{{ 7 if can_manage_firm_services else 6 }}" class="px-4 py-8 text-center text-sm text-slate-500">No catalogue services found.</td></tr>
{% endfor %}
</tbody>
</table>
@@ -190,4 +240,41 @@
</div>
</div>
</div>
{% if can_manage_firm_services %}
<script>
(() => {
const selectAll = document.getElementById('bulk-select-all-visible');
const clearButton = document.getElementById('bulk-clear-selection');
const countLabel = document.getElementById('bulk-selected-count');
const checkboxes = Array.from(document.querySelectorAll('.bulk-service-checkbox'));
const refreshSelectionState = () => {
const selectedCount = checkboxes.filter((checkbox) => checkbox.checked).length;
countLabel.textContent = String(selectedCount);
if (selectAll) {
selectAll.checked = checkboxes.length > 0 && selectedCount === checkboxes.length;
selectAll.indeterminate = selectedCount > 0 && selectedCount < checkboxes.length;
}
};
if (selectAll) {
selectAll.addEventListener('change', () => {
checkboxes.forEach((checkbox) => { checkbox.checked = selectAll.checked; });
refreshSelectionState();
});
}
checkboxes.forEach((checkbox) => checkbox.addEventListener('change', refreshSelectionState));
if (clearButton) {
clearButton.addEventListener('click', () => {
checkboxes.forEach((checkbox) => { checkbox.checked = false; });
refreshSelectionState();
});
}
refreshSelectionState();
})();
</script>
{% endif %}
{% endblock %}