Report PDF: mirror the executive redesign (hero band + 4 chapters)
PDF template updated to match the new HTML structure: cover block with static filter labels, hero KPI band (4 stacked 2x2), Chapter I lifetime (Projects + Teams full-width, Projects now with Start / Working Days / Avg-R-per-Working-Day columns), Chapter II selected period (existing Total Paid Out hero + Loans/Advances pairs + Labour Cost + Payments/Adjustments), Chapter III worker breakdown (heading renamed), Chapter IV team x project pivot (new). THIS YEAR section dropped per design doc section 3 (redundant with All Time + Selected Period). Same _build_report_context helper so HTML and PDF cannot drift in data. All numbers identical. WeasyPrint-friendly: absolute units, single-column body, no Font Awesome. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fe85c9d7fd
commit
a27da90c58
@ -5,17 +5,14 @@
|
||||
<style>
|
||||
/* ==========================================================
|
||||
PAGE SETUP
|
||||
Standard A4 portrait with generous margins. WeasyPrint
|
||||
understands @page rules for headers/footers; we use a
|
||||
simple bottom-margin footer block instead of a running
|
||||
element so the footer can reference `now` from context.
|
||||
========================================================== */
|
||||
@page {
|
||||
size: a4 portrait;
|
||||
margin: 2cm 1.8cm 1.6cm 1.8cm;
|
||||
@frame footer_frame {
|
||||
-pdf-frame-content: footerContent;
|
||||
bottom: 0.6cm;
|
||||
margin-left: 1.8cm;
|
||||
margin-right: 1.8cm;
|
||||
height: 0.8cm;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
@ -30,12 +27,14 @@
|
||||
p { margin: 3pt 0; }
|
||||
|
||||
/* ==========================================================
|
||||
COVER
|
||||
COVER — brand eyebrow + orange title band + filter pills
|
||||
The accent line colour was updated to the brand amber
|
||||
(#e8851a) so the PDF matches the web app's orange accent.
|
||||
========================================================== */
|
||||
.brand-eyebrow {
|
||||
font-size: 7.5pt;
|
||||
font-weight: bold;
|
||||
color: #10b981;
|
||||
color: #e8851a;
|
||||
letter-spacing: 3pt;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
@ -45,8 +44,8 @@
|
||||
margin: 0;
|
||||
}
|
||||
table.cover-band td {
|
||||
border-top: 1pt solid #10b981;
|
||||
border-bottom: 1pt solid #10b981;
|
||||
border-top: 1pt solid #e8851a;
|
||||
border-bottom: 1pt solid #e8851a;
|
||||
padding: 9pt 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@ -64,39 +63,53 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cover-filters {
|
||||
font-size: 10pt;
|
||||
color: #64748b;
|
||||
letter-spacing: 0.3pt;
|
||||
margin: 4pt 0 14pt 0;
|
||||
font-size: 9.5pt;
|
||||
color: #475569;
|
||||
letter-spacing: 0.2pt;
|
||||
margin: 6pt 0 14pt 0;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
SECTION STRUCTURE
|
||||
CHAPTER HEADINGS
|
||||
Matches the HTML's "chapter-num I" style: a small filled
|
||||
square-ish marker with the Roman numeral next to the
|
||||
chapter title. No Font Awesome in PDFs — just text.
|
||||
========================================================== */
|
||||
.section {
|
||||
margin-top: 16pt;
|
||||
}
|
||||
h2.section-title {
|
||||
.chapter-heading {
|
||||
margin: 16pt 0 8pt 0;
|
||||
padding-bottom: 4pt;
|
||||
border-bottom: 0.8pt solid #e8851a;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
.break-before {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-size: 7pt;
|
||||
.chapter-num {
|
||||
display: inline-block;
|
||||
background-color: #e8851a;
|
||||
color: #ffffff;
|
||||
font-size: 9pt;
|
||||
font-weight: bold;
|
||||
color: #10b981;
|
||||
letter-spacing: 2.5pt;
|
||||
margin-bottom: 3pt;
|
||||
padding: 2pt 7pt;
|
||||
margin-right: 8pt;
|
||||
letter-spacing: 0.5pt;
|
||||
}
|
||||
h2.section-title {
|
||||
font-size: 13pt;
|
||||
.chapter-title {
|
||||
font-size: 14pt;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
margin: 0 0 10pt 0;
|
||||
padding-bottom: 4pt;
|
||||
border-bottom: 0.5pt solid #cbd5e1;
|
||||
letter-spacing: 0.2pt;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
SECTION STRUCTURE — smaller sub-headings within a chapter
|
||||
========================================================== */
|
||||
.section { margin-top: 10pt; }
|
||||
h2.section-title {
|
||||
page-break-after: avoid;
|
||||
font-size: 11pt;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
margin: 10pt 0 6pt 0;
|
||||
padding-bottom: 3pt;
|
||||
border-bottom: 0.4pt solid #cbd5e1;
|
||||
}
|
||||
h3.sub-title {
|
||||
font-size: 9pt;
|
||||
@ -105,35 +118,121 @@
|
||||
letter-spacing: 1pt;
|
||||
margin: 8pt 0 3pt 0;
|
||||
}
|
||||
.break-before { page-break-before: always; }
|
||||
.eyebrow {
|
||||
font-size: 7pt;
|
||||
font-weight: bold;
|
||||
color: #e8851a;
|
||||
letter-spacing: 2.5pt;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
HERO CARD — 50% SMALLER
|
||||
Halved the overall visual weight per feedback:
|
||||
• padding dropped from 9pt → 4pt top/bottom
|
||||
• hero-value dropped from 22pt → 14pt
|
||||
• label/caption scaled down in proportion
|
||||
Result: card is roughly half the height it was before.
|
||||
HERO KPI BAND — 4 cards laid out 2x2 so they fit on the
|
||||
portrait page without shrinking the big numbers. Each cell
|
||||
is a mini stat card: small label, bold value, subline.
|
||||
========================================================== */
|
||||
table.kpi-band {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 6pt;
|
||||
margin: 2pt 0 10pt 0;
|
||||
}
|
||||
table.kpi-band td.kpi {
|
||||
width: 50%;
|
||||
background-color: #f8fafc;
|
||||
border-left: 3pt solid #e8851a;
|
||||
padding: 8pt 10pt;
|
||||
vertical-align: top;
|
||||
}
|
||||
/* Colour-vary left border to echo the HTML hero tiles */
|
||||
table.kpi-band td.kpi-danger { border-left-color: #dc2626; }
|
||||
table.kpi-band td.kpi-warning { border-left-color: #d97706; }
|
||||
table.kpi-band td.kpi-info { border-left-color: #0ea5e9; }
|
||||
.kpi-label {
|
||||
font-size: 7pt;
|
||||
font-weight: bold;
|
||||
color: #64748b;
|
||||
letter-spacing: 1.8pt;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.kpi-value {
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
line-height: 1;
|
||||
margin: 4pt 0 2pt 0;
|
||||
}
|
||||
.kpi-subline {
|
||||
font-size: 7.5pt;
|
||||
color: #64748b;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
SMALLER STAT CARD GRID — 6 stats for Selected Period
|
||||
Rendered as a 3x2 grid so the labels + numbers all fit on
|
||||
the portrait page. Same border-colour trick as the hero.
|
||||
========================================================== */
|
||||
table.stat-grid {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 5pt;
|
||||
margin: 2pt 0 8pt 0;
|
||||
}
|
||||
table.stat-grid td.stat {
|
||||
width: 33.33%;
|
||||
background-color: #f8fafc;
|
||||
border-left: 2.5pt solid #e8851a;
|
||||
padding: 6pt 8pt;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.stat-grid td.stat-danger { border-left-color: #dc2626; }
|
||||
table.stat-grid td.stat-warning { border-left-color: #d97706; }
|
||||
table.stat-grid td.stat-success { border-left-color: #059669; }
|
||||
table.stat-grid td.stat-info { border-left-color: #0ea5e9; }
|
||||
.stat-label {
|
||||
font-size: 6.5pt;
|
||||
font-weight: bold;
|
||||
color: #64748b;
|
||||
letter-spacing: 1.2pt;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 11pt;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
line-height: 1;
|
||||
margin: 3pt 0 0 0;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
HERO — legacy single-card block used by the small
|
||||
"Total Paid Out" hero inside Chapter II (kept for the
|
||||
SELECTED PERIOD section's existing design).
|
||||
========================================================== */
|
||||
table.hero {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 4pt 0 14pt 0;
|
||||
margin: 4pt 0 10pt 0;
|
||||
}
|
||||
table.hero td {
|
||||
background-color: #f8fafc;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.hero td.hero-accent {
|
||||
background-color: #10b981;
|
||||
background-color: #e8851a;
|
||||
width: 3pt;
|
||||
padding: 0;
|
||||
}
|
||||
table.hero td.hero-body {
|
||||
padding: 4pt 14pt;
|
||||
}
|
||||
/* Hero spacing is dominated by line-height, not margin.
|
||||
line-height: 1 collapses the phantom "leading" above/below
|
||||
the value glyphs → ~50% tighter gaps around "R 64 939.00". */
|
||||
.hero-label {
|
||||
font-size: 7pt;
|
||||
font-weight: bold;
|
||||
@ -157,10 +256,10 @@
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
LEDGER LINES — with R-symbol aligned in its own column
|
||||
Splitting the value cell into two cells (rsym + rnum) means
|
||||
every "R" in a column appears at the same x-position, while
|
||||
the numbers right-align neatly on their own edge.
|
||||
LEDGER LINES — used in Chapter II for compact key/value
|
||||
tables (Labour Cost, Payments by Date, Adjustments). The
|
||||
split "rsym"/"rnum" trick keeps every R aligned in its
|
||||
own column so the numbers right-align cleanly.
|
||||
========================================================== */
|
||||
table.ledger {
|
||||
width: 100%;
|
||||
@ -191,9 +290,6 @@
|
||||
padding-right: 10pt;
|
||||
width: 55pt;
|
||||
}
|
||||
/* The two cells that together form a money value.
|
||||
rsym: left-aligned "R" anchored at a fixed x-position
|
||||
rnum: right-aligned number, bold black */
|
||||
table.ledger td.rsym {
|
||||
text-align: left;
|
||||
color: #0f172a;
|
||||
@ -213,57 +309,56 @@
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
TWO-COLUMN LAYOUT
|
||||
LIFETIME PROJECTS — wider table with Start / Working
|
||||
Days / Avg per Working Day columns. Not a ledger because
|
||||
we need column headers.
|
||||
========================================================== */
|
||||
table.cols {
|
||||
table.lifetime {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 0;
|
||||
margin-top: 4pt;
|
||||
font-size: 8.5pt;
|
||||
}
|
||||
table.cols td {
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
table.lifetime th {
|
||||
text-align: left;
|
||||
font-size: 7pt;
|
||||
font-weight: bold;
|
||||
color: #64748b;
|
||||
letter-spacing: 0.8pt;
|
||||
padding: 4pt 5pt 5pt 5pt;
|
||||
border-bottom: 1pt solid #0f172a;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
table.cols td.colL { width: 45%; }
|
||||
table.cols td.gap { width: 10%; }
|
||||
table.cols td.colR { width: 45%; }
|
||||
|
||||
/* Extra breathing room between the two rows of the Period
|
||||
Breakdown section (Labour Cost row ⇢ Payments/Adjustments row) */
|
||||
table.cols-spaced {
|
||||
margin-top: 18pt;
|
||||
table.lifetime th.r { text-align: right; }
|
||||
table.lifetime td {
|
||||
padding: 5pt;
|
||||
border-bottom: 0.4pt solid #e2e8f0;
|
||||
color: #334155;
|
||||
vertical-align: middle;
|
||||
}
|
||||
table.lifetime td.name {
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
}
|
||||
table.lifetime td.r {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.lifetime td.total {
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.lifetime td.dim {
|
||||
color: #cbd5e1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
PERIOD DETAIL — 15% smaller text in this section only
|
||||
Scoped via the .period-detail wrapper so other sections keep
|
||||
their normal size.
|
||||
========================================================== */
|
||||
.period-detail h3.sub-title {
|
||||
font-size: 8pt; /* was 9pt */
|
||||
}
|
||||
.period-detail table.ledger td.lbl {
|
||||
font-size: 8pt; /* was 9.5pt */
|
||||
}
|
||||
.period-detail table.ledger td.meta {
|
||||
font-size: 7.5pt; /* was 8.5pt */
|
||||
}
|
||||
.period-detail table.ledger td.rsym,
|
||||
.period-detail table.ledger td.rnum {
|
||||
font-size: 8.5pt; /* was 10pt */
|
||||
}
|
||||
/* Use split padding-top/bottom (NOT the shorthand) so horizontal
|
||||
padding defined on .meta and .rsym is preserved — otherwise the
|
||||
shorthand clobbers it and you get "130 daysR" with no gap. */
|
||||
.period-detail table.ledger td {
|
||||
padding-top: 3pt;
|
||||
padding-bottom: 3pt;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
WORKER BREAKDOWN TABLE
|
||||
Money values inside use a nested mini-table so R and number
|
||||
live in their own columns (same alignment trick as ledger).
|
||||
WORKER BREAKDOWN TABLE — unchanged layout, now used for
|
||||
Chapter III only.
|
||||
========================================================== */
|
||||
table.worker {
|
||||
width: 100%;
|
||||
@ -296,19 +391,88 @@
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* Total Paid column: bolder, darker for emphasis */
|
||||
table.worker td.total {
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* Empty-value variant (em-dash) */
|
||||
table.worker td.dim {
|
||||
color: #cbd5e1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
PIVOT TABLE — Chapter IV Team x Project activity
|
||||
========================================================== */
|
||||
table.pivot {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 4pt;
|
||||
font-size: 8.5pt;
|
||||
}
|
||||
table.pivot th {
|
||||
text-align: left;
|
||||
font-size: 7pt;
|
||||
font-weight: bold;
|
||||
color: #64748b;
|
||||
letter-spacing: 0.8pt;
|
||||
padding: 4pt 5pt 5pt 5pt;
|
||||
border-bottom: 1pt solid #0f172a;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
table.pivot th.r { text-align: right; }
|
||||
table.pivot td {
|
||||
padding: 4pt 5pt;
|
||||
border-bottom: 0.4pt solid #e2e8f0;
|
||||
color: #334155;
|
||||
}
|
||||
table.pivot td.name {
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
}
|
||||
table.pivot td.r {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.pivot td.dim {
|
||||
color: #cbd5e1;
|
||||
text-align: right;
|
||||
}
|
||||
table.pivot td.total {
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.pivot tr.total-row td {
|
||||
border-top: 0.8pt solid #0f172a;
|
||||
border-bottom: none;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
TWO-COLUMN LAYOUT (kept for the Chapter II sub-blocks)
|
||||
========================================================== */
|
||||
table.cols {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 0;
|
||||
}
|
||||
table.cols td {
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
}
|
||||
table.cols td.colL { width: 48%; }
|
||||
table.cols td.gap { width: 4%; }
|
||||
table.cols td.colR { width: 48%; }
|
||||
table.cols-spaced {
|
||||
margin-top: 12pt;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
MISC
|
||||
========================================================== */
|
||||
@ -317,13 +481,14 @@
|
||||
font-size: 9pt;
|
||||
padding: 5pt 0;
|
||||
}
|
||||
#footerContent {
|
||||
.footer {
|
||||
margin-top: 20pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt solid #e2e8f0;
|
||||
font-size: 7pt;
|
||||
color: #94a3b8;
|
||||
text-align: center;
|
||||
letter-spacing: 0.5pt;
|
||||
border-top: 0.3pt solid #e2e8f0;
|
||||
padding-top: 4pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@ -331,6 +496,11 @@
|
||||
|
||||
<!-- ==============================================================
|
||||
COVER
|
||||
Brand eyebrow + title band + static filter-pill line. The filter
|
||||
line uses plain text (no x-buttons) because this is a static PDF.
|
||||
project_name and team_name already arrive comma-joined from the
|
||||
report-context helper — "All Projects" / "All Teams" when unset,
|
||||
or "Wilkot Boerdery, Solar Farm Alpha" style for multi-select.
|
||||
============================================================== -->
|
||||
<div class="brand-eyebrow">FOXFITT CONSTRUCTION</div>
|
||||
<table class="cover-band">
|
||||
@ -339,168 +509,141 @@
|
||||
<td class="cover-date">{{ start_date|date:"d F Y" }} – {{ end_date|date:"d F Y" }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="cover-filters">{{ project_name }} • {{ team_name }}</div>
|
||||
|
||||
<!-- ==============================================================
|
||||
ALL TIME
|
||||
(All-time and year-to-date sections now appear FIRST, before the
|
||||
Selected Period block — the big-picture lifetime view sets context
|
||||
before we zoom in to the selected date range.)
|
||||
============================================================== -->
|
||||
|
||||
<div class="section">
|
||||
<div class="eyebrow">LIFETIME PERFORMANCE</div>
|
||||
<h2 class="section-title">All Time — Labour Cost</h2>
|
||||
|
||||
<table class="cols">
|
||||
<tr>
|
||||
<td class="colL">
|
||||
<h3 class="sub-title">BY PROJECT</h3>
|
||||
{% if alltime_projects %}
|
||||
<table class="ledger">
|
||||
{% for item in alltime_projects %}
|
||||
<tr>
|
||||
<td class="rank">{{ forloop.counter }}</td>
|
||||
<td class="lbl">{{ item.project }}</td>
|
||||
<td class="rsym">R</td>
|
||||
<td class="rnum">{{ item.total|money }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}<p class="empty">No project data yet.</p>{% endif %}
|
||||
</td>
|
||||
<td class="gap"></td>
|
||||
<td class="colR">
|
||||
<h3 class="sub-title">BY TEAM</h3>
|
||||
{% if alltime_teams %}
|
||||
<table class="ledger">
|
||||
{% for item in alltime_teams %}
|
||||
<tr>
|
||||
<td class="rank">{{ forloop.counter }}</td>
|
||||
<td class="lbl">{{ item.team }}</td>
|
||||
<td class="rsym">R</td>
|
||||
<td class="rnum">{{ item.total|money }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}<p class="empty">No team data yet.</p>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="cover-filters">
|
||||
{{ start_date|date:"d M Y" }} – {{ end_date|date:"d M Y" }}
|
||||
· {{ project_name }}
|
||||
· {{ team_name }}
|
||||
</div>
|
||||
|
||||
<!-- ==============================================================
|
||||
THIS YEAR
|
||||
HERO KPI BAND — 4 cards in a 2x2 grid
|
||||
Shows the big-picture numbers up front:
|
||||
1. Paid This Period (total_paid_out for the selected range)
|
||||
2. Outstanding Now (what's currently unpaid across FoxFitt)
|
||||
3. FoxFitt Avg / Day (lifetime company average)
|
||||
4. FoxFitt Avg / Month (lifetime × ~30.44)
|
||||
============================================================== -->
|
||||
<div class="section">
|
||||
<div class="eyebrow">YEAR-TO-DATE</div>
|
||||
<h2 class="section-title">{{ current_year }} — Labour Cost</h2>
|
||||
|
||||
<table class="cols">
|
||||
<table class="kpi-band">
|
||||
<tr>
|
||||
<td class="colL">
|
||||
<h3 class="sub-title">BY PROJECT</h3>
|
||||
{% if year_projects %}
|
||||
<table class="ledger">
|
||||
{% for item in year_projects %}
|
||||
<tr>
|
||||
<td class="rank">{{ forloop.counter }}</td>
|
||||
<td class="lbl">{{ item.project }}</td>
|
||||
<td class="rsym">R</td>
|
||||
<td class="rnum">{{ item.total|money }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}<p class="empty">No project data for {{ current_year }}.</p>{% endif %}
|
||||
</td>
|
||||
<td class="gap"></td>
|
||||
<td class="colR">
|
||||
<h3 class="sub-title">BY TEAM</h3>
|
||||
{% if year_teams %}
|
||||
<table class="ledger">
|
||||
{% for item in year_teams %}
|
||||
<tr>
|
||||
<td class="rank">{{ forloop.counter }}</td>
|
||||
<td class="lbl">{{ item.team }}</td>
|
||||
<td class="rsym">R</td>
|
||||
<td class="rnum">{{ item.total|money }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}<p class="empty">No team data for {{ current_year }}.</p>{% endif %}
|
||||
</td>
|
||||
<td class="kpi kpi-danger">
|
||||
<div class="kpi-label">Paid This Period</div>
|
||||
<div class="kpi-value">R {{ total_paid_out|money }}</div>
|
||||
<div class="kpi-subline">{{ start_date|date:"d M Y" }} – {{ end_date|date:"d M Y" }}</div>
|
||||
</td>
|
||||
<td class="kpi kpi-warning">
|
||||
<div class="kpi-label">Outstanding Now</div>
|
||||
<div class="kpi-value">R {{ current_outstanding.total|money }}</div>
|
||||
<div class="kpi-subline">as of {{ current_as_of|date:"d M Y H:i" }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<tr>
|
||||
<td class="kpi kpi-info">
|
||||
<div class="kpi-label">FoxFitt Avg / Day</div>
|
||||
<div class="kpi-value">R {{ company_avg_daily|money }}</div>
|
||||
<div class="kpi-subline">lifetime avg · {{ company_working_days }} working days</div>
|
||||
</td>
|
||||
<td class="kpi kpi-info">
|
||||
<div class="kpi-label">FoxFitt Avg / Month</div>
|
||||
<div class="kpi-value">R {{ company_avg_monthly|money }}</div>
|
||||
<div class="kpi-subline">lifetime avg · ~30.44 days/month</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- ==============================================================
|
||||
SELECTED PERIOD (new page, compact text via .period-detail)
|
||||
All summary figures for the chosen date range live here:
|
||||
- Hero: Total Paid Out (headline KPI)
|
||||
- Loans pair (issued + outstanding)
|
||||
- Advances pair (issued + outstanding)
|
||||
- Labour Cost by project / team
|
||||
- Payments by date / Adjustments
|
||||
- Worker breakdown (next section, flows naturally)
|
||||
The .period-detail wrapper shrinks ledger text 15%; the hero
|
||||
card uses its own classes so its headline stays prominent.
|
||||
CHAPTER I — Lifetime Context
|
||||
All-time totals by project (with start date / working days /
|
||||
avg per working day) and by team (simpler: name + total).
|
||||
============================================================== -->
|
||||
<div class="section break-before period-detail">
|
||||
<div class="eyebrow">SELECTED PERIOD</div>
|
||||
<h2 class="section-title">Period Breakdown</h2>
|
||||
<div class="chapter-heading">
|
||||
<span class="chapter-num">I</span><span class="chapter-title">Lifetime Context</span>
|
||||
</div>
|
||||
|
||||
<!-- Hero: the headline KPI for this period -->
|
||||
<table class="hero">
|
||||
<h2 class="section-title">All Time — Projects</h2>
|
||||
{% if alltime_projects %}
|
||||
<table class="lifetime">
|
||||
<tr>
|
||||
<th>Project</th>
|
||||
<th>Start</th>
|
||||
<th class="r">Working Days</th>
|
||||
<th class="r">Total Cost</th>
|
||||
<th class="r">Avg R / Working Day</th>
|
||||
</tr>
|
||||
{% for item in alltime_projects %}
|
||||
<tr>
|
||||
<td class="name">{{ item.project }}</td>
|
||||
<td>{% if item.start_date %}{{ item.start_date|date:"d M Y" }}{% else %}<span style="color:#cbd5e1;">—</span>{% endif %}</td>
|
||||
<td class="r">{% if item.working_days %}{{ item.working_days }}{% else %}<span style="color:#cbd5e1;">—</span>{% endif %}</td>
|
||||
<td class="total">R {{ item.total|money }}</td>
|
||||
<td class="r">{% if item.working_days %}R {{ item.avg_per_working_day|money }}{% else %}<span style="color:#cbd5e1;">—</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}<p class="empty">No lifetime project data.</p>{% endif %}
|
||||
|
||||
<h2 class="section-title">All Time — Teams</h2>
|
||||
{% if alltime_teams %}
|
||||
<table class="lifetime">
|
||||
<tr>
|
||||
<th>Team</th>
|
||||
<th class="r">Total Cost</th>
|
||||
</tr>
|
||||
{% for item in alltime_teams %}
|
||||
<tr>
|
||||
<td class="name">{{ item.team }}</td>
|
||||
<td class="total">R {{ item.total|money }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}<p class="empty">No lifetime team data.</p>{% endif %}
|
||||
|
||||
<!-- ==============================================================
|
||||
CHAPTER II — Selected Period
|
||||
The detailed breakdown for the chosen date range. Starts on a
|
||||
fresh page so lifetime context (Chapter I) doesn't crowd it.
|
||||
Structure:
|
||||
- 6 stat cards (Paid · Worker-Days · Loans×2 · Advances×2)
|
||||
- Labour Cost by Project / by Team (side-by-side)
|
||||
- Payments by Date / Adjustments (side-by-side)
|
||||
============================================================== -->
|
||||
<div class="break-before">
|
||||
<div class="chapter-heading">
|
||||
<span class="chapter-num">II</span><span class="chapter-title">Selected Period: {{ start_date|date:"d M Y" }} – {{ end_date|date:"d M Y" }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 6 stat cards in a 3x2 grid -->
|
||||
<table class="stat-grid">
|
||||
<tr>
|
||||
<td class="hero-accent"></td>
|
||||
<td class="hero-body">
|
||||
<div class="hero-label">TOTAL PAID OUT</div>
|
||||
<div class="hero-value">R {{ total_paid_out|money }}</div>
|
||||
<div class="hero-caption">across {{ total_worker_days }} worker-days in this period</div>
|
||||
<td class="stat stat-danger">
|
||||
<div class="stat-label">Total Paid Out</div>
|
||||
<div class="stat-value">R {{ total_paid_out|money }}</div>
|
||||
</td>
|
||||
<td class="stat stat-info">
|
||||
<div class="stat-label">Worker-Days</div>
|
||||
<div class="stat-value">{{ total_worker_days }}</div>
|
||||
</td>
|
||||
<td class="stat stat-success">
|
||||
<div class="stat-label">Loans Issued</div>
|
||||
<div class="stat-value">R {{ loans_issued|money }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="stat stat-warning">
|
||||
<div class="stat-label">Loans Outstanding</div>
|
||||
<div class="stat-value">R {{ loans_outstanding|money }}</div>
|
||||
</td>
|
||||
<td class="stat stat-success">
|
||||
<div class="stat-label">Advances Issued</div>
|
||||
<div class="stat-value">R {{ advances_issued|money }}</div>
|
||||
</td>
|
||||
<td class="stat stat-warning">
|
||||
<div class="stat-label">Advances Outstanding</div>
|
||||
<div class="stat-value">R {{ advances_outstanding|money }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Loans pair (left) + Advances pair (right) -->
|
||||
<!-- Each column shows issued first, then outstanding — grouped
|
||||
by instrument type for easier scanning. -->
|
||||
<table class="cols">
|
||||
<tr>
|
||||
<td class="colL">
|
||||
<h3 class="sub-title">LOANS</h3>
|
||||
<table class="ledger">
|
||||
<tr>
|
||||
<td class="lbl">Loans issued</td>
|
||||
<td class="rsym">R</td>
|
||||
<td class="rnum">{{ loans_issued|money }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl">Loans outstanding</td>
|
||||
<td class="rsym">R</td>
|
||||
<td class="rnum">{{ loans_outstanding|money }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="gap"></td>
|
||||
<td class="colR">
|
||||
<h3 class="sub-title">ADVANCES</h3>
|
||||
<table class="ledger">
|
||||
<tr>
|
||||
<td class="lbl">Advances issued</td>
|
||||
<td class="rsym">R</td>
|
||||
<td class="rnum">{{ advances_issued|money }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl">Advances outstanding</td>
|
||||
<td class="rsym">R</td>
|
||||
<td class="rnum">{{ advances_outstanding|money }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- First row: Labour Cost by project / team -->
|
||||
<!-- Labour Cost by Project / by Team (side-by-side) -->
|
||||
<table class="cols">
|
||||
<tr>
|
||||
<td class="colL">
|
||||
@ -537,7 +680,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Second row: extra top margin creates clear visual gap -->
|
||||
<!-- Payments by Date / Adjustments (side-by-side) -->
|
||||
<table class="cols cols-spaced">
|
||||
<tr>
|
||||
<td class="colL">
|
||||
@ -574,13 +717,14 @@
|
||||
</div>
|
||||
|
||||
<!-- ==============================================================
|
||||
WORKER BREAKDOWN
|
||||
Uses nested mini-tables inside each money cell so the R and the
|
||||
number line up column-wise across every row.
|
||||
CHAPTER III — Worker Breakdown
|
||||
Per-worker detail: days worked, total paid, plus one column
|
||||
per active adjustment type. Same table as before.
|
||||
============================================================== -->
|
||||
<div class="section">
|
||||
<div class="eyebrow">PER-WORKER DETAIL</div>
|
||||
<h2 class="section-title">Worker Breakdown</h2>
|
||||
<div class="chapter-heading">
|
||||
<span class="chapter-num">III</span><span class="chapter-title">Worker Breakdown</span>
|
||||
</div>
|
||||
|
||||
{% if worker_breakdown %}
|
||||
<table class="worker">
|
||||
@ -612,10 +756,59 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- ==============================================================
|
||||
CHAPTER IV — Team × Project Activity
|
||||
Pivot table: rows = teams, columns = projects, cells = distinct
|
||||
work days. The `dictlookup` filter (from core/templatetags/
|
||||
format_tags.py) lets the template fetch cells by project id
|
||||
from the `row.cells_by_project_id` dict.
|
||||
============================================================== -->
|
||||
<div class="section">
|
||||
<div class="chapter-heading">
|
||||
<span class="chapter-num">IV</span><span class="chapter-title">Team × Project Activity</span>
|
||||
</div>
|
||||
|
||||
{% if team_project_activity.rows %}
|
||||
<table class="pivot">
|
||||
<tr>
|
||||
<th>Team</th>
|
||||
{% for col in team_project_activity.columns %}
|
||||
<th class="r">{{ col.name }}</th>
|
||||
{% endfor %}
|
||||
<th class="r">Total</th>
|
||||
</tr>
|
||||
{% for row in team_project_activity.rows %}
|
||||
<tr>
|
||||
<td class="name">{{ row.team_name }}</td>
|
||||
{% for col in team_project_activity.columns %}
|
||||
{% with days=row.cells_by_project_id|dictlookup:col.id %}
|
||||
{% if days %}
|
||||
<td class="r">{{ days }}</td>
|
||||
{% else %}
|
||||
<td class="dim">—</td>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
<td class="total">{{ row.row_total }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="total-row">
|
||||
<td>Total</td>
|
||||
{% for col in team_project_activity.columns %}
|
||||
<td class="r">{{ team_project_activity.col_totals|dictlookup:col.id }}</td>
|
||||
{% endfor %}
|
||||
<td class="r">{{ team_project_activity.grand_total }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="empty">No team × project activity in this period.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- ==============================================================
|
||||
FOOTER
|
||||
============================================================== -->
|
||||
<div id="footerContent">
|
||||
<div class="footer">
|
||||
GENERATED {{ now|date:"d M Y H:i" }} • FOXFITT CONSTRUCTION • CONFIDENTIAL
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user