ux(labels): extend display labels to AJAX-sourced modal renders

Closes the Task 3 design-goal gap: two user-facing modals (work-log
payroll preview in base.html, split-payslip preview in
payroll_dashboard.html) render adjustment types via JS reading AJAX
JSON. After Task 3's TYPE_CHOICES rename they were still showing
the old long labels because the backend endpoints
(work_log_payroll_ajax, preview_payslip) only emitted adj.type (DB
value), not the display label.

Added a 'type_label' field to the JSON payloads alongside the
existing 'type' field. JS at both render sites now reads
`adj.type_label || adj.type` — with the fallback so any stale
client-side JSON degrades gracefully to the DB value rather than
rendering blank.

Path A still holds: adj.type in JSON stays the DB value for any
identifier purposes; the new type_label is additive.

Tests: 69/69.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-04-24 09:58:05 +02:00
parent c1d9014fe1
commit 1cf13048c2
3 changed files with 15 additions and 2 deletions

View File

@ -549,7 +549,10 @@ document.addEventListener('DOMContentLoaded', function() {
var adjBody = document.createElement('tbody');
data.adjustments.forEach(function(adj) {
var tr = document.createElement('tr');
tr.appendChild(el('td', null, adj.type));
// Prefer the short display label from the server; fall back to the
// raw DB value if a stale JSON response doesn't include type_label
// (graceful degradation — never render a blank cell).
tr.appendChild(el('td', null, adj.type_label || adj.type));
var wTd = document.createElement('td');
wTd.appendChild(link('/workers/' + adj.worker_id + '/', adj.worker_name));
tr.appendChild(wTd);

View File

@ -2395,7 +2395,10 @@ document.addEventListener('DOMContentLoaded', function() {
labelWrap.className = 'd-flex justify-content-between flex-grow-1';
var label = document.createElement('span');
label.textContent = adj.type + (adj.project ? ' (' + adj.project + ')' : '');
// Prefer the short display label ('Loan' / 'Advance' / 'Advance Repaid')
// from the server; fall back to the raw DB value if a stale JSON
// response doesn't include type_label (graceful degradation).
label.textContent = (adj.type_label || adj.type) + (adj.project ? ' (' + adj.project + ')' : '');
if (adj.description) {
var descSmall = document.createElement('small');
descSmall.className = 'text-muted ms-1';

View File

@ -1020,8 +1020,11 @@ def work_log_payroll_ajax(request, log_id):
} for row in ctx['worker_rows']]
# Adjustments linked directly to this work_log (Overtime, etc.).
# We emit BOTH 'type' (raw DB value — stable identifier for any JS logic)
# AND 'type_label' (short display label from get_type_display()) — visible UI.
adjustments = [{
'type': adj.type,
'type_label': adj.get_type_display(),
'amount': float(adj.amount),
'worker_id': adj.worker.id,
'worker_name': adj.worker.name,
@ -4236,7 +4239,11 @@ def preview_payslip(request, worker_id):
adj_total += float(adj.amount) if adj.type in ADDITIVE_TYPES else -float(adj.amount)
adjustments_list.append({
'id': adj.id,
# 'type' keeps the raw DB value so any JS that uses it as an
# identifier keeps working; 'type_label' is the short display
# label ('Loan' / 'Advance' / 'Advance Repaid' etc.) for visible UI.
'type': adj.type,
'type_label': adj.get_type_display(),
'amount': float(adj.amount),
'sign': sign,
'description': adj.description,