fix(go): adults template — emit markup that the lifted CSS expects
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s

The lifted-from-Python app.css already styles .list-container/.list-item,
.unmatched-row, .balance-pos/.balance-neg, and cell-{status}; the M6.2
template invented .credits-list / .unmatched-table / .balance-cell that
had no rules, so those sections rendered unstyled.

- Credits / Debts: <ul><li> → <div class="list-container"><div class="list-item">
  <span class="list-item-name"> + <span class="list-item-val"> (debts red inline).
- Unmatched: <table> → <div class="list-container"> + <div class="unmatched-row">.
- Balance cell: balance-pos / balance-neg with style="position: relative;";
  Pay-All button now lives inside it (no separate trailing column).
- Total row: cell-{status} + caption span "received / expected" + bold/dark inline styles.
- Drop redundant .cell wrapper class; balance value drops trailing "CZK".
- Section headings: "Credits (Advance Payments / Surplus)" + "Debts (Missing Payments)".
- Source links: <div class="description"> block under h1 (was at page bottom).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-08 10:17:03 +02:00
parent c85748b3aa
commit daac5d7392
2 changed files with 63 additions and 52 deletions

View File

@@ -1,5 +1,14 @@
# Changelog # Changelog
## 2026-05-08 10:15 CEST — fix(go): adults template — use lifted CSS classes for visual parity
- Use existing `.balance-pos` / `.balance-neg` (drop invented `balance-cell` / `balance-negative`); Pay-All button now lives inside the balance cell with `position: relative` (matches Python; no separate trailing column).
- Credits / Debts / Unmatched sections rewritten from `<ul>` / `<table>` to `<div class="list-container"><div class="list-item">…` / `<div class="unmatched-row">…` so the lifted CSS actually applies.
- Section headings get the descriptive Python text: "Credits (Advance Payments / Surplus)", "Debts (Missing Payments)".
- Source links moved from page bottom to a `<div class="description">` block under the h1, matching Python's "Source: Attendance Sheet | Payments Ledger".
- Total row uses `cell-{{Status}}` plus the small "received / expected" caption span and Python's inline-styled bold/dark background.
- Drop the redundant `class="cell"` wrapper; debts amount turns red via inline style; balance value drops the trailing "CZK".
## 2026-05-08 01:09 CEST — feat(go): M6.2 — adults page (table, filters, Pay buttons) ## 2026-05-08 01:09 CEST — feat(go): M6.2 — adults page (table, filters, Pay buttons)
- `go/internal/web/api/handler.go`: extracted `ServeAdults` body into `AssembleAdults(ctx)` — shared by the JSON API route and the new HTML handler. - `go/internal/web/api/handler.go`: extracted `ServeAdults` body into `AssembleAdults(ctx)` — shared by the JSON API route and the new HTML handler.

View File

@@ -3,9 +3,15 @@
<h1>Adults Dashboard</h1> <h1>Adults Dashboard</h1>
{{if .Error}} {{if .Error}}
<p class="description error-banner">Error loading data: {{.Error}}</p> <div class="description">Error loading data: {{.Error}}</div>
{{else}} {{else}}
<div class="description">
Balances calculated by matching Google Sheet payments against attendance fees.<br>
Source: <a href="{{.Data.AttendanceURL}}" target="_blank" rel="noopener">Attendance Sheet</a> |
<a href="{{.Data.PaymentsURL}}" target="_blank" rel="noopener">Payments Ledger</a>
</div>
<div class="filter-container" id="filterContainer" data-current-month="{{.Data.CurrentMonth}}"> <div class="filter-container" id="filterContainer" data-current-month="{{.Data.CurrentMonth}}">
<div class="filter-item"> <div class="filter-item">
<label class="filter-label" for="nameFilter">Member</label> <label class="filter-label" for="nameFilter">Member</label>
@@ -30,14 +36,13 @@
</select> </select>
</div> </div>
<div class="filter-item"> <div class="filter-item">
<button id="applyFilter" class="filter-btn">Apply</button> <button id="applyFilter" class="filter-select" type="button" style="cursor: pointer;">Apply</button>
<button id="clearFilter" class="filter-btn">All</button> <button id="clearFilter" class="filter-select" type="button" style="cursor: pointer;">All</button>
</div> </div>
</div> </div>
{{if .Data.Results}} {{if .Data.Results}}
<div class="table-wrapper"> <table>
<table class="reconcile-table">
<thead> <thead>
<tr> <tr>
<th>Member</th> <th>Member</th>
@@ -45,7 +50,6 @@
<th data-month-idx="{{$i}}" data-raw-month="{{index $.Data.RawMonths $i}}">{{$m}}</th> <th data-month-idx="{{$i}}" data-raw-month="{{index $.Data.RawMonths $i}}">{{$m}}</th>
{{end}} {{end}}
<th>Balance</th> <th>Balance</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -53,84 +57,82 @@
<tr class="member-row" data-name="{{$row.Name}}"> <tr class="member-row" data-name="{{$row.Name}}">
<td class="member-name">{{$row.Name}}</td> <td class="member-name">{{$row.Name}}</td>
{{range $i, $cell := $row.Months}} {{range $i, $cell := $row.Months}}
<td class="cell cell-{{$cell.Status}}{{if and (or (eq $cell.Status "unpaid") (eq $cell.Status "partial")) (ge $cell.RawMonth $.Data.CurrentMonth)}} cell-unpaid-current{{end}}{{if $cell.Overridden}} cell-overridden{{end}}" <td data-month-idx="{{$i}}" title="{{$cell.Tooltip}}"
data-month-idx="{{$i}}" title="{{$cell.Tooltip}}"> class="cell-{{$cell.Status}}{{if and (or (eq $cell.Status "unpaid") (eq $cell.Status "partial")) (ge $cell.RawMonth $.Data.CurrentMonth)}} cell-unpaid-current{{end}}{{if $cell.Overridden}} cell-overridden{{end}}">
{{$cell.Text}} {{$cell.Text}}
{{if and (or (eq $cell.Status "unpaid") (eq $cell.Status "partial")) (lt $cell.RawMonth $.Data.CurrentMonth)}} {{if and (or (eq $cell.Status "unpaid") (eq $cell.Status "partial")) (lt $cell.RawMonth $.Data.CurrentMonth)}}
<a class="pay-btn" href="{{qrHref $.Data.BankAccount $cell.Amount $row.Name $cell.RawMonth}}">Pay</a> <a class="pay-btn" href="{{qrHref $.Data.BankAccount $cell.Amount $row.Name $cell.RawMonth}}">Pay</a>
{{end}} {{end}}
</td> </td>
{{end}} {{end}}
<td class="balance-cell{{if lt $row.Balance 0}} balance-negative{{end}}">{{$row.Balance}} CZK</td> <td class="{{if lt $row.Balance 0}}balance-neg{{else if gt $row.Balance 0}}balance-pos{{end}}" style="position: relative;">
<td class="payall-cell"> {{$row.Balance}}
{{if gt $row.PayableAmount 0}} {{if gt $row.PayableAmount 0}}
<a class="pay-btn pay-all-btn" href="{{qrHrefAll $.Data.BankAccount $row.PayableAmount $row.Name $row.RawUnpaidPeriods}}">Pay All</a> <a class="pay-btn" href="{{qrHrefAll $.Data.BankAccount $row.PayableAmount $row.Name $row.RawUnpaidPeriods}}">Pay All</a>
{{end}} {{end}}
</td> </td>
</tr> </tr>
{{end}} {{end}}
<tr class="totals-row"> <tr class="totals-row" style="font-weight: bold; background-color: #111; border-top: 2px solid #333;">
<td><strong>TOTAL</strong></td> <td style="text-align: left; padding: 6px 8px;">TOTAL</td>
{{range $i, $t := .Data.Totals}} {{range $i, $t := .Data.Totals}}
<td class="total-cell total-cell-{{$t.Status}}" data-month-idx="{{$i}}" data-raw-month="{{index $.Data.RawMonths $i}}">{{$t.Text}}</td> <td data-month-idx="{{$i}}" data-raw-month="{{index $.Data.RawMonths $i}}" class="cell-{{$t.Status}}" style="padding-top: 4px; padding-bottom: 4px;">
<span style="font-size: 0.6em; font-weight: normal; color: #666; text-transform: lowercase; display: block; margin-bottom: 2px;">received / expected</span>
{{$t.Text}}
</td>
{{end}} {{end}}
<td colspan="2"></td> <td></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
{{else}} {{else}}
<p class="description">No members found.</p> <div class="description">No members found.</div>
{{end}} {{end}}
{{if .Data.Credits}} {{if .Data.Credits}}
<h2 class="section-header">Credits</h2> <h2>Credits (Advance Payments / Surplus)</h2>
<ul class="credits-list"> <div class="list-container">
{{range .Data.Credits}} {{range .Data.Credits}}
<li>{{.Name}}: <strong>+{{.Amount}} CZK</strong></li> <div class="list-item">
<span class="list-item-name">{{.Name}}</span>
<span class="list-item-val">{{.Amount}} CZK</span>
</div>
{{end}} {{end}}
</ul> </div>
{{end}} {{end}}
{{if .Data.Debts}} {{if .Data.Debts}}
<h2 class="section-header">Debts</h2> <h2>Debts (Missing Payments)</h2>
<ul class="debts-list"> <div class="list-container">
{{range .Data.Debts}} {{range .Data.Debts}}
<li>{{.Name}}: <strong>{{.Amount}} CZK</strong></li> <div class="list-item">
<span class="list-item-name">{{.Name}}</span>
<span class="list-item-val" style="color: #ff3333;">{{.Amount}} CZK</span>
</div>
{{end}} {{end}}
</ul> </div>
{{end}} {{end}}
{{if .Data.Unmatched}} {{if .Data.Unmatched}}
<h2 class="unmatched-header section-header">Unmatched Transactions</h2> <h2>Unmatched Transactions</h2>
<table class="unmatched-table"> <div class="list-container">
<thead> <div class="unmatched-row unmatched-header">
<tr> <span>Date</span>
<th>Date</th> <span>Amount</span>
<th>Amount</th> <span>Sender</span>
<th>Sender</th> <span>Message</span>
<th>Message</th> </div>
</tr> {{range .Data.Unmatched}}
</thead> <div class="unmatched-row">
<tbody> <span>{{.Date}}</span>
{{range .Data.Unmatched}} <span>{{printf "%.0f" .Amount}}</span>
<tr class="unmatched-row"> <span>{{.Sender}}</span>
<td>{{.Date}}</td> <span>{{.Message}}</span>
<td>{{printf "%.0f" .Amount}} CZK</td> </div>
<td>{{.Sender}}</td> {{end}}
<td>{{.Message}}</td> </div>
</tr>
{{end}}
</tbody>
</table>
{{end}} {{end}}
<p class="sheet-links">
<a href="{{.Data.AttendanceURL}}" target="_blank" rel="noopener">[Attendance sheet]</a>
&middot;
<a href="{{.Data.PaymentsURL}}" target="_blank" rel="noopener">[Payments sheet]</a>
</p>
{{end}} {{end}}
<script src="/static/js/filters.js" defer></script> <script src="/static/js/filters.js" defer></script>