fix(templates): convert 8 broken multi-line {# #} comments + clarify cryptic sublines
CLAUDE.md gotcha #1 strikes again — the dashboard audit pass added 7 multi-line {# ... #} comment blocks across index.html, report.html, and pdf/report_pdf.html. All rendered as literal text on the live pages (Konrad screenshotted them). Also caught an old one in admin/base_site.html that was technically broken syntax but non-rendering (outside any block). All 8 converted to {% comment %}{% endcomment %}. CLAUDE.md updated: - Bumped the bit-us count (4 → confirmed 4 + 5 + 7 across three features). Added a grep-one-liner sanity check that finds broken multi-line {# blocks across all templates so future passes can spot-check before committing. Cryptic hero-card sublines on /report/ clarified (Konrad asked what they mean): - "as of 08:13" → "Live total at 08:13 today · for <scope>" with hover tooltip explaining the snapshot semantics. - "Company Avg / Working Day" / "/ Month" labels renamed to "Avg Labour Cost / Working Day" / "/ Month". Sublines simplified to "Lifetime average across all crews" / "Daily figure × 30.44 days". Both gain hover tooltips that explain the math and the "current pay rates" basis. Pure template + docs change. 173/173 tests still passing (no test changes — these are cosmetic fixes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
652168fe88
commit
1d224bc01b
@ -28,7 +28,7 @@ answers — see `docs/plans/parked-work.md`.
|
||||
- Add plain English comments explaining what complex logic does
|
||||
- The project owner is not a programmer — comments should be understandable by a non-technical person
|
||||
- When creating or editing code, maintain the existing comment structure
|
||||
- **Django template comments `{# ... #}` are SINGLE-LINE only.** Multi-line blocks need `{% comment %}...{% endcomment %}`. A `{#` on line N with no closing `#}` on the same line renders the whole block as literal text onto the page (and silently — no error). This bit us 4× during the Adjustments feature. Also: the literal tokens `{#` and `#}` cannot appear inside a `{% comment %}` block — they'll be parsed as a nested comment marker. Rephrase meta-notes about comment syntax OUTSIDE the block.
|
||||
- **Django template comments `{# ... #}` are SINGLE-LINE only.** Multi-line blocks need `{% comment %}...{% endcomment %}`. A `{#` on line N with no closing `#}` on the same line renders the whole block as literal text onto the page (and silently — no error). This bit us 4× during the Adjustments feature, 5× during the Absences feature, and 7× during the 15 May dashboard-audit pass. **Sanity check after any template edit:** `grep -rn "^\s*{#" core/templates/ | awk -F: '$0 !~ /#}/ {print}'` — every match is a multi-line broken comment. Also: the literal tokens `{#` and `#}` cannot appear inside a `{% comment %}` block — they'll be parsed as a nested comment marker. Rephrase meta-notes about comment syntax OUTSIDE the block.
|
||||
- **Duplicate `id=""` attributes cause silent bugs.** `document.getElementById()` returns only the FIRST match in DOM order, so adding a second element with an existing id silently steals the handler from the original. Grep the template before assigning any new id (caught `adjSelectAll` collision in Task 6 — header checkbox stole the Add-Adjustment modal's Select-All handler).
|
||||
- **Bootstrap dropdowns inside `.card` elements get clipped by sibling cards.** A `.dropdown-menu` with `z-index: 1050` rendered inside a filter `.card` will STILL appear behind a sibling table `.card` that follows in document order. Bootstrap's `transform: translate(...)` Popper positioning creates a new stacking context — the z-index is measured INSIDE the parent card, not globally. The fix: lift the wrapping element (e.g. the filter `<form class="card">`) with `style="position: relative; z-index: 10;"` so the entire card sits above its siblings. The dropdown's local z-index then resolves correctly. Bit us on the Absences filter dropdown (May 2026).
|
||||
- **JS reading from `data-worker-id` was unreliable; read from `<input name="workers">[value]` directly.** Round A's first absence-form team filter rendered `data-worker-id="{{ worker.choice_value }}"` on the row `<div>` and read it via `row.dataset.workerId`. On production this hid ALL workers when a team was selected — likely a stale-template / template-render mismatch. The proven pattern (used by `attendance_log.html` for years) is to read `row.querySelector('input[name="workers"]').value`. The form widget's `<input value="<pk>">` is the source of truth; data attributes are an unnecessary indirection.
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
{% extends "admin/base.html" %}
|
||||
{# ===========================================================
|
||||
Minimal override of the default admin/base_site.html.
|
||||
The sole purpose right now is to inject a small <style> block
|
||||
into every Django admin page. Add more admin CSS tweaks here
|
||||
as needed — keeps them in one place and isolated from the
|
||||
main app's custom.css.
|
||||
=========================================================== #}
|
||||
{% comment %}
|
||||
===========================================================
|
||||
Minimal override of the default admin/base_site.html.
|
||||
The sole purpose right now is to inject a small <style> block
|
||||
into every Django admin page. Add more admin CSS tweaks here
|
||||
as needed — keeps them in one place and isolated from the
|
||||
main app's custom.css.
|
||||
===========================================================
|
||||
{% endcomment %}
|
||||
|
||||
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
||||
|
||||
|
||||
@ -148,8 +148,10 @@
|
||||
<div style="flex: 1;">
|
||||
<div class="stat-label">Outstanding by Project</div>
|
||||
{% if outstanding_by_project_list %}
|
||||
{# Sorted by amount desc. Keyed by project_id so two projects
|
||||
sharing a name don't get merged. #}
|
||||
{% comment %}
|
||||
Sorted by amount desc. Keyed by project_id so two projects
|
||||
sharing a name don't get merged.
|
||||
{% endcomment %}
|
||||
<div style="font-size: 0.85rem; margin-top: 0.35rem;">
|
||||
{% for proj in outstanding_by_project_list %}
|
||||
<div class="d-flex justify-content-between" style="color: var(--text-primary);">
|
||||
|
||||
@ -744,8 +744,10 @@
|
||||
<td class="name">{{ w.name }}</td>
|
||||
<td class="r">{{ w.days }}</td>
|
||||
<td class="total">R {{ w.total_paid|money }}</td>
|
||||
{# adj_values now contains {'amount', 'is_deductive'} per column.
|
||||
Render "-R …" for deductive types. #}
|
||||
{% comment %}
|
||||
adj_values now contains an amount + is_deductive flag per column.
|
||||
Render with a leading minus for deductive types.
|
||||
{% endcomment %}
|
||||
{% for val in w.adj_values %}
|
||||
{% if val.amount %}
|
||||
<td class="r">{% if val.is_deductive %}−R {% else %}R {% endif %}{{ val.amount|money_abs }}</td>
|
||||
|
||||
@ -199,9 +199,11 @@
|
||||
</div>
|
||||
|
||||
{# === HERO KPI BAND === #}
|
||||
{# Sub-labels intentionally call out current-pay-rate basis (Finding 2)
|
||||
and the active filter scope (Finding 5) so the KPIs aren't read as
|
||||
apples-to-apples when filters are on. #}
|
||||
{% comment %}
|
||||
Sub-labels intentionally call out current-pay-rate basis (Finding 2)
|
||||
and the active filter scope (Finding 5) so the KPIs aren't read as
|
||||
apples-to-apples when filters are on.
|
||||
{% endcomment %}
|
||||
<div class="row g-3 mb-4 hero-kpi-row">
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="stat-card stat-card--danger stat-card--hero h-100">
|
||||
@ -216,26 +218,36 @@
|
||||
<div class="stat-card stat-card--warning stat-card--hero h-100">
|
||||
<div class="stat-label">Outstanding Now</div>
|
||||
<div class="stat-value">R {{ current_outstanding.total|money }}</div>
|
||||
<div class="stat-subline">
|
||||
as of {{ current_as_of|date:"H:i" }}
|
||||
<div class="stat-subline"
|
||||
data-bs-toggle="tooltip"
|
||||
title="Live total: unpaid wages plus any pending payroll adjustments. Refresh the page to see the latest.">
|
||||
Live total at {{ current_as_of|date:"H:i" }} today
|
||||
{% if project_name != 'All Projects' or team_name != 'All Teams' %}
|
||||
· scoped to {{ project_name }}{% if team_name != 'All Teams' %} / {{ team_name }}{% endif %}
|
||||
· for {{ project_name }}{% if team_name != 'All Teams' %} / {{ team_name }}{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="stat-card stat-card--info stat-card--hero h-100">
|
||||
<div class="stat-label">Company Avg / Working Day</div>
|
||||
<div class="stat-label">Avg Labour Cost / Working Day</div>
|
||||
<div class="stat-value">R {{ company_avg_daily|money }}</div>
|
||||
<div class="stat-subline">lifetime · all crews · at current pay rates</div>
|
||||
<div class="stat-subline"
|
||||
data-bs-toggle="tooltip"
|
||||
title="Sum of daily rates across all crews on every day FoxFitt has logged work, divided by the count of those days. Uses each worker's CURRENT salary — a recent raise will retroactively change this figure.">
|
||||
Lifetime average across all crews
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="stat-card stat-card--info stat-card--hero h-100">
|
||||
<div class="stat-label">Company Avg / Month</div>
|
||||
<div class="stat-label">Avg Labour Cost / Month</div>
|
||||
<div class="stat-value">R {{ company_avg_monthly|money }}</div>
|
||||
<div class="stat-subline">lifetime · ~30.44 days/month · at current pay rates</div>
|
||||
<div class="stat-subline"
|
||||
data-bs-toggle="tooltip"
|
||||
title="The daily figure to the left multiplied by 30.44 (the average number of days in a month, i.e. 365.25 ÷ 12). Same lifetime + current-pay-rates basis.">
|
||||
Daily figure × 30.44 days
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -390,8 +402,10 @@
|
||||
<table class="table table-sm mb-0">
|
||||
<thead><tr><th>Category</th><th class="text-end">Total</th></tr></thead>
|
||||
<tbody>
|
||||
{# Sign-aware rendering: deductive types show as red "-R …";
|
||||
additive types stay in default colour with no sign. Finding 4. #}
|
||||
{% comment %}
|
||||
Sign-aware rendering: deductive types show as red "-R ...";
|
||||
additive types stay in default colour with no sign. Finding 4.
|
||||
{% endcomment %}
|
||||
{% for item in adjustment_totals %}
|
||||
<tr>
|
||||
<td>{{ item.label }}</td>
|
||||
@ -487,8 +501,10 @@
|
||||
<th>Worker</th>
|
||||
<th class="text-end">Days</th>
|
||||
<th class="text-end">Total Paid</th>
|
||||
{# Sign-aware headers: deductive types render in muted red
|
||||
so the negative sign on rows below is unmistakable. Finding 4. #}
|
||||
{% comment %}
|
||||
Sign-aware headers: deductive types render in muted red
|
||||
so the negative sign on rows below is unmistakable. Finding 4.
|
||||
{% endcomment %}
|
||||
{% for h in active_adj_headers %}
|
||||
<th class="text-end d-none d-md-table-cell {% if h.is_deductive %}text-danger{% endif %}" style="font-size: 0.75rem;">{{ h.label }}</th>
|
||||
{% endfor %}
|
||||
@ -500,8 +516,10 @@
|
||||
<td class="fw-medium">{{ w.name }}</td>
|
||||
<td class="text-end">{{ w.days }}</td>
|
||||
<td class="text-end fw-semibold">R {{ w.total_paid|money }}</td>
|
||||
{# Each val is now {'amount': Decimal, 'is_deductive': bool}
|
||||
— render "-R 500.00" in red for deductive types. #}
|
||||
{% comment %}
|
||||
Each val is now an "amount" Decimal plus an "is_deductive" bool
|
||||
— render the amount in red with a leading minus sign for deductive types.
|
||||
{% endcomment %}
|
||||
{% for val in w.adj_values %}
|
||||
<td class="text-end d-none d-md-table-cell {% if val.is_deductive and val.amount %}text-danger{% endif %}" style="font-size: 0.8rem;">
|
||||
{% if val.amount %}
|
||||
@ -514,8 +532,10 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{# Footnote — explains why Days and Total Paid can read as
|
||||
"not matching" within a single period (different timing). #}
|
||||
{% comment %}
|
||||
Footnote — explains why Days and Total Paid can read as
|
||||
"not matching" within a single period (different timing).
|
||||
{% endcomment %}
|
||||
<p class="text-muted px-3 py-2 mb-0" style="font-size: 0.75rem; border-top: 1px solid var(--border-subtle);">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
<strong>Days</strong> reflect work logged in this period.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user