All checks were successful
Deploy to K8s / deploy (push) Successful in 12s
Instead of hiding older months entirely, show all months in the from/to selectors but default the from-select to the last MONTHS_TO_SHOW months on page load. The "All" button resets to full history as before. Python: passes months_to_show to render_template, IIFE sets fromSelect.value. Go: adds MonthsToShow to response structs, data-months-to-show attr in templates, filters.js reads it and defaults fromSelect after hideFutureMonths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
208 lines
7.8 KiB
Cheetah
208 lines
7.8 KiB
Cheetah
{{define "title"}}Adults{{end}}
|
|
{{define "content"}}
|
|
<h1>Adults Dashboard</h1>
|
|
|
|
{{if .Error}}
|
|
<div class="description">Error loading data: {{.Error}}</div>
|
|
{{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}}" data-page="adults" data-bank-account="{{.Data.BankAccount}}" data-months-to-show="{{.Data.MonthsToShow}}">
|
|
<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-select" type="button" style="cursor: pointer;">Apply</button>
|
|
<button id="clearFilter" class="filter-select" type="button" style="cursor: pointer;">All</button>
|
|
</div>
|
|
</div>
|
|
|
|
{{if .Data.Results}}
|
|
<div class="table-container">
|
|
<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>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range $row := .Data.Results}}
|
|
<tr class="member-row" data-name="{{$row.Name}}">
|
|
<td class="member-name">{{$row.Name}}<span class="info-icon" data-name="{{$row.Name}}" title="Show details">[i]</span></td>
|
|
{{range $i, $cell := $row.Months}}
|
|
<td data-month-idx="{{$i}}" title="{{$cell.Tooltip}}"
|
|
class="{{if eq $cell.Status "empty"}}cell-empty{{else if and (or (eq $cell.Status "unpaid") (eq $cell.Status "partial")) (ge $cell.RawMonth $.Data.CurrentMonth)}}cell-unpaid-current{{else if or (eq $cell.Status "unpaid") (eq $cell.Status "partial")}}cell-unpaid{{else if eq $cell.Status "ok"}}cell-ok{{end}}{{if $cell.Overridden}} cell-overridden{{end}}">
|
|
{{$cell.Text}}
|
|
{{if and (or (eq $cell.Status "unpaid") (eq $cell.Status "partial")) (lt $cell.RawMonth $.Data.CurrentMonth)}}
|
|
<button type="button" class="pay-btn" data-name="{{$row.Name}}" data-amount="{{$cell.Amount}}" data-month="{{$cell.Month}}" data-raw-month="{{$cell.RawMonth}}">Pay</button>
|
|
{{end}}
|
|
</td>
|
|
{{end}}
|
|
<td class="{{if lt $row.Balance 0}}balance-neg{{else if gt $row.Balance 0}}balance-pos{{end}}" style="position: relative;">
|
|
{{$row.Balance}}
|
|
{{if gt $row.PayableAmount 0}}
|
|
<button type="button" class="pay-btn" data-name="{{$row.Name}}" data-amount="{{$row.PayableAmount}}" data-month="{{$row.UnpaidPeriods}}" data-raw-month="{{$row.RawUnpaidPeriods}}" data-pay-all="1">Pay All</button>
|
|
{{end}}
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
<tr class="totals-row" style="font-weight: bold; background-color: #111; border-top: 2px solid #333;">
|
|
<td style="text-align: left; padding: 6px 8px;">TOTAL</td>
|
|
{{range $i, $t := .Data.Totals}}
|
|
<td data-month-idx="{{$i}}" data-raw-month="{{index $.Data.RawMonths $i}}" class="{{if eq $t.Status "ok"}}cell-ok{{else if eq $t.Status "unpaid"}}cell-unpaid{{else if eq $t.Status "surplus"}}cell-overridden{{end}}" 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}}
|
|
<td></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{else}}
|
|
<div class="description">No members found.</div>
|
|
{{end}}
|
|
|
|
{{if .Data.Credits}}
|
|
<h2>Credits (Advance Payments / Surplus)</h2>
|
|
<div class="list-container">
|
|
{{range .Data.Credits}}
|
|
<div class="list-item">
|
|
<span class="list-item-name">{{.Name}}</span>
|
|
<span class="list-item-val">{{.Amount}} CZK</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .Data.Debts}}
|
|
<h2>Debts (Missing Payments)</h2>
|
|
<div class="list-container">
|
|
{{range .Data.Debts}}
|
|
<div class="list-item">
|
|
<span class="list-item-name">{{.Name}}</span>
|
|
<span class="list-item-val" style="color: #ff3333;">{{.Amount}} CZK</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .Data.Unmatched}}
|
|
<h2>Unmatched Transactions</h2>
|
|
<div class="list-container">
|
|
<div class="unmatched-row unmatched-header">
|
|
<span>Date</span>
|
|
<span>Amount</span>
|
|
<span>Sender</span>
|
|
<span>Message</span>
|
|
</div>
|
|
{{range .Data.Unmatched}}
|
|
<div class="unmatched-row">
|
|
<span>{{.Date}}</span>
|
|
<span>{{printf "%.0f" .Amount}}</span>
|
|
<span>{{.Sender}}</span>
|
|
<span>{{.Message}}</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{end}}
|
|
|
|
<div id="memberModal" onclick="if(event.target===this)this.classList.remove('active')">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<div class="modal-title" id="modalMemberName">Member Name</div>
|
|
<div class="close-btn" onclick="document.getElementById('memberModal').classList.remove('active')">[close]</div>
|
|
</div>
|
|
|
|
<div class="modal-section">
|
|
<div class="modal-section-title">Status Summary</div>
|
|
<div id="modalTier" style="margin-bottom: 10px; color: #888;">Tier: -</div>
|
|
<table class="modal-table">
|
|
<thead><tr>
|
|
<th>Month</th>
|
|
<th style="text-align: center;">Att.</th>
|
|
<th style="text-align: center;">Expected</th>
|
|
<th style="text-align: center;">Paid</th>
|
|
<th style="text-align: right;">Status</th>
|
|
</tr></thead>
|
|
<tbody id="modalStatusBody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="modal-section" id="modalExceptionSection" style="display: none;">
|
|
<div class="modal-section-title">Fee Exceptions</div>
|
|
<div id="modalExceptionList" class="tx-list"></div>
|
|
</div>
|
|
|
|
<div class="modal-section" id="modalOtherSection" style="display: none;">
|
|
<div class="modal-section-title">Other Transactions</div>
|
|
<div id="modalOtherList" class="tx-list"></div>
|
|
</div>
|
|
|
|
<div class="modal-section">
|
|
<div class="modal-section-title">Payment History</div>
|
|
<div id="modalTxList" class="tx-list"></div>
|
|
</div>
|
|
|
|
<div class="modal-section">
|
|
<div class="modal-section-title">
|
|
Raw Payments
|
|
<a href="#" id="rawPaymentsToggle" class="raw-toggle">[show]</a>
|
|
</div>
|
|
<div id="modalRawList" class="tx-list" style="display: none;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="qrModal" onclick="if(event.target===this)this.classList.remove('active')">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<div class="modal-title" id="qrTitle">Payment for —</div>
|
|
<div class="close-btn" onclick="document.getElementById('qrModal').classList.remove('active')">[close]</div>
|
|
</div>
|
|
<div class="qr-image">
|
|
<img id="qrImg" src="" alt="Payment QR Code">
|
|
</div>
|
|
<div class="qr-details">
|
|
<div>Account: <span id="qrAccount"></span></div>
|
|
<div>Amount: <span id="qrAmount"></span> CZK</div>
|
|
<div>Message: <span id="qrMessage"></span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/static/js/filters.js" defer></script>
|
|
<script src="/static/js/member-detail.js" defer></script>
|
|
<script src="/static/js/payment-qr.js" defer></script>
|
|
{{end}}
|