fix(absences): dropdown z-index + clearer Confirm Absences copy

- /absences/ Reasons multi-checkbox dropdown: z-index 1050 so it
  renders above the table rows (was hiding the bottom 4 options).
- /absences/log/confirm/: action-oriented copy, pre-checked
  'remove from work log' (the common case), explicit Cancel button.
  Was confusing: 'Also remove from WorkLog' didn't read as the
  natural fix for the conflict. New language explains both branches
  in plain English. +1 regression test for the new copy.
This commit is contained in:
Konrad du Plessis 2026-05-14 23:32:45 +02:00
parent a6cf766394
commit 27fe05e3b6
3 changed files with 45 additions and 25 deletions

View File

@ -78,7 +78,7 @@ has an inline delete form. CSV export button only shows for admin.
<button class="btn btn-outline-secondary btn-sm dropdown-toggle form-select-sm" type="button" data-bs-toggle="dropdown" style="min-width: 140px; text-align: left;">
{% if filter_reasons %}{{ filter_reasons|length }} selected{% else %}All{% endif %}
</button>
<ul class="dropdown-menu p-2" style="min-width: 220px;" onclick="event.stopPropagation();">
<ul class="dropdown-menu p-2" style="min-width: 220px; z-index: 1050;" onclick="event.stopPropagation();">
{% for key, label in reason_choices %}
<li>
<label class="form-check d-block mb-0">

View File

@ -7,51 +7,58 @@
{% comment %}
Conflict-confirmation page. Shown when at least one (worker, date)
pair on the absence-log form already has a WorkLog. The admin can
tick "Also remove from WorkLog" per conflict before committing —
useful when correcting an earlier mistake (worker was clocked in
but in fact was absent that day). Untouched rows keep their
WorkLog intact, so partial-day cases work too.
keep or untick the pre-checked "Remove from work log" box per
conflict — the common case (clocked-in but actually absent) is
fixed by removing them from the work log AND creating the absence,
so we pre-tick the box. Unticking keeps the WorkLog and SKIPS the
absence creation contradiction by still creating it (untouched
rows keep their WorkLog intact, so partial-day cases work too).
{% endcomment %}
<div class="container py-4">
<h1 class="page-title mb-3">
<i class="fas fa-exclamation-triangle me-2" style="color: var(--accent);"></i>
<i class="fas fa-triangle-exclamation me-2" style="color: var(--accent);"></i>
Confirm Absences
</h1>
<div class="alert alert-warning">
<strong>{{ conflicts|length }} worker(s) already have work logs on these dates.</strong><br>
Tick the boxes below to also remove them from those work logs (recommended if you're correcting a mistake).
<strong>{{ conflicts|length }} worker(s) were already logged as <em>working</em> on these dates.</strong>
<p class="mb-0 mt-2">
You can't be both <em>working</em> and <em>absent</em> on the same day — pick which is correct below.
The usual fix is to remove them from the work log (their attendance was recorded by mistake), so we've
pre-selected that for you. Untick the box if you want to keep the work log entry instead (their absence
won't be recorded in that case — you'll have to delete the absence manually after this saves).
</p>
</div>
<form method="post" class="card">
{% csrf_token %}
<div class="card-body">
<h6 class="text-uppercase mb-3" style="font-size: 0.75rem; color: var(--text-secondary);">
Conflicts
Worker &times; Date
</h6>
<table class="table table-sm">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>Worker</th>
<th>Date</th>
<th>WorkLog</th>
<th>Action</th>
<th>Existing Work Log</th>
<th style="min-width: 240px;">What should happen?</th>
</tr>
</thead>
<tbody>
{% for c in conflicts %}
<tr>
<td>{{ c.worker_name }}</td>
<td><strong>{{ c.worker_name }}</strong></td>
<td>{{ c.date }}</td>
<td>{{ c.project_name }} (WorkLog #{{ c.work_log_id }})</td>
<td><span class="text-muted">{{ c.project_name }}</span> (Log #{{ c.work_log_id }})</td>
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox"
name="remove_from_worklog_{{ c.work_log_id }}_{{ c.worker_id }}"
id="remove_{{ forloop.counter }}">
id="remove_{{ forloop.counter }}" checked>
<label class="form-check-label" for="remove_{{ forloop.counter }}">
Also remove from WorkLog
Remove from work log <span class="text-muted">(recommended &mdash; they were absent, not working)</span>
</label>
</div>
</td>
@ -61,18 +68,18 @@ WorkLog intact, so partial-day cases work too.
</table>
<hr>
<p class="mb-2"><strong>{{ absence_count }} absence(s) will be created:</strong></p>
<p>
<div class="alert alert-secondary mb-0">
<strong>{{ absence_count }} absence record(s) will be created.</strong>
Reason: <strong>{{ reason }}</strong>.
Paid: <strong>{% if is_paid %}Yes{% else %}No{% endif %}</strong>.
{# Project line is conditional — older flows / non-project absences leave it blank. #}
{% if project_name %}Project: <strong>{{ project_name }}</strong>.{% endif %}
</p>
Paid: <strong>{% if is_paid %}Yes (Bonus payroll adjustment){% else %}No{% endif %}</strong>.
</div>
<div class="d-flex justify-content-end gap-2 mt-3">
<a href="{% url 'absence_log' %}" class="btn btn-outline-secondary">&larr; Back to form</a>
<div class="d-flex justify-content-between gap-2 mt-3">
<a href="{% url 'absence_log' %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i> Cancel
</a>
<button type="submit" class="btn btn-accent">
<i class="fas fa-check me-1"></i> Confirm &amp; Create Absences
<i class="fas fa-check me-1"></i> Save Absences
</button>
</div>
</div>

View File

@ -2241,6 +2241,19 @@ class AbsenceConfirmViewTests(TestCase):
self.assertEqual(Absence.objects.count(), 1) # Still creates absence
self.assertEqual(resp.status_code, 302) # No 500
def test_confirm_page_renders_new_copy(self):
"""Round D-followup — confirm page uses action-oriented copy + Cancel button."""
resp = self.client.get('/absences/log/confirm/')
self.assertEqual(resp.status_code, 200)
# New banner copy
self.assertContains(resp, "were already logged")
# Cancel button (was 'Back to form')
self.assertContains(resp, "Cancel")
# Save button (was 'Confirm &amp; Create Absences')
self.assertContains(resp, "Save Absences")
# Recommended hint on the checkbox
self.assertContains(resp, "recommended")
# === ABSENCE LIST / EDIT / DELETE / EXPORT VIEW TESTS ============================
# Covers Task 5 of the Worker Absences feature: browsing absences via /absences/