All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
- Extract AssembleAdults(ctx) from ServeAdults so HTML and JSON API share one reconcile path. - HTMLHandler gains *api.Handler; ServeAdults loads real data and renders adults.tmpl. - AdultsPageData view model + qrHref/qrHrefAll funcMap (URL-encode /qr params, YYYY-MM→MM/YYYY). - adults.tmpl: full reconcile table, per-cell status classes + cell-unpaid-current, Pay button hrefs, totals row, credits/debts/unmatched sections, filter controls, sheet links. - static/js/filters.js: NFD-normalize name filter + month-range column hiding; future months hidden by default. - TestAdultsPage asserts member name and cell text against fixture data. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
138 lines
4.1 KiB
Cheetah
138 lines
4.1 KiB
Cheetah
{{define "title"}}Adults{{end}}
|
||
{{define "content"}}
|
||
<h1>Adults Dashboard</h1>
|
||
|
||
{{if .Error}}
|
||
<p class="description error-banner">Error loading data: {{.Error}}</p>
|
||
{{else}}
|
||
|
||
<div class="filter-container" id="filterContainer" data-current-month="{{.Data.CurrentMonth}}">
|
||
<div class="filter-item">
|
||
<label class="filter-label" for="nameFilter">Member</label>
|
||
<input id="nameFilter" class="filter-input" type="text" placeholder="Filter by name…">
|
||
</div>
|
||
<div class="filter-item">
|
||
<label class="filter-label" for="fromMonth">From</label>
|
||
<select id="fromMonth" class="filter-select">
|
||
<option value="">All</option>
|
||
{{range $i, $m := .Data.Months}}
|
||
<option value="{{$i}}">{{$m}}</option>
|
||
{{end}}
|
||
</select>
|
||
</div>
|
||
<div class="filter-item">
|
||
<label class="filter-label" for="toMonth">To</label>
|
||
<select id="toMonth" class="filter-select">
|
||
<option value="">All</option>
|
||
{{range $i, $m := .Data.Months}}
|
||
<option value="{{$i}}">{{$m}}</option>
|
||
{{end}}
|
||
</select>
|
||
</div>
|
||
<div class="filter-item">
|
||
<button id="applyFilter" class="filter-btn">Apply</button>
|
||
<button id="clearFilter" class="filter-btn">All</button>
|
||
</div>
|
||
</div>
|
||
|
||
{{if .Data.Results}}
|
||
<div class="table-wrapper">
|
||
<table class="reconcile-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Member</th>
|
||
{{range $i, $m := .Data.Months}}
|
||
<th data-month-idx="{{$i}}" data-raw-month="{{index $.Data.RawMonths $i}}">{{$m}}</th>
|
||
{{end}}
|
||
<th>Balance</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range $row := .Data.Results}}
|
||
<tr class="member-row" data-name="{{$row.Name}}">
|
||
<td class="member-name">{{$row.Name}}</td>
|
||
{{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}}"
|
||
data-month-idx="{{$i}}" title="{{$cell.Tooltip}}">
|
||
{{$cell.Text}}
|
||
{{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>
|
||
{{end}}
|
||
</td>
|
||
{{end}}
|
||
<td class="balance-cell{{if lt $row.Balance 0}} balance-negative{{end}}">{{$row.Balance}} CZK</td>
|
||
<td class="payall-cell">
|
||
{{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>
|
||
{{end}}
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
<tr class="totals-row">
|
||
<td><strong>TOTAL</strong></td>
|
||
{{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>
|
||
{{end}}
|
||
<td colspan="2"></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}
|
||
<p class="description">No members found.</p>
|
||
{{end}}
|
||
|
||
{{if .Data.Credits}}
|
||
<h2 class="section-header">Credits</h2>
|
||
<ul class="credits-list">
|
||
{{range .Data.Credits}}
|
||
<li>{{.Name}}: <strong>+{{.Amount}} CZK</strong></li>
|
||
{{end}}
|
||
</ul>
|
||
{{end}}
|
||
|
||
{{if .Data.Debts}}
|
||
<h2 class="section-header">Debts</h2>
|
||
<ul class="debts-list">
|
||
{{range .Data.Debts}}
|
||
<li>{{.Name}}: <strong>−{{.Amount}} CZK</strong></li>
|
||
{{end}}
|
||
</ul>
|
||
{{end}}
|
||
|
||
{{if .Data.Unmatched}}
|
||
<h2 class="unmatched-header section-header">Unmatched Transactions</h2>
|
||
<table class="unmatched-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Date</th>
|
||
<th>Amount</th>
|
||
<th>Sender</th>
|
||
<th>Message</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range .Data.Unmatched}}
|
||
<tr class="unmatched-row">
|
||
<td>{{.Date}}</td>
|
||
<td>{{printf "%.0f" .Amount}} CZK</td>
|
||
<td>{{.Sender}}</td>
|
||
<td>{{.Message}}</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
{{end}}
|
||
|
||
<p class="sheet-links">
|
||
<a href="{{.Data.AttendanceURL}}" target="_blank" rel="noopener">[Attendance sheet]</a>
|
||
·
|
||
<a href="{{.Data.PaymentsURL}}" target="_blank" rel="noopener">[Payments sheet]</a>
|
||
</p>
|
||
|
||
{{end}}
|
||
|
||
<script src="/static/js/filters.js" defer></script>
|
||
{{end}}
|