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:
parent
a6cf766394
commit
27fe05e3b6
@ -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">
|
||||
|
||||
@ -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 × 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 — 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">← 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 & Create Absences
|
||||
<i class="fas fa-check me-1"></i> Save Absences
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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 & 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/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user