feat: per-operator filter in the statistics dashboard

New Operator picker next to the contest picker, populated with the operators
actually present in the log. Selecting one recomputes every statistic for that
operator — QSO/DXCC totals, top countries, continent split, by band/mode, and
the rate/best-60. "— Station owner —" buckets QSOs logged with no OPERATOR set.
Shown only when the log has more than one operator.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-18 17:11:52 +02:00
co-authored by Claude Opus 4.8
parent db2908b3ef
commit 443698b507
6 changed files with 99 additions and 19 deletions
+12 -2
View File
@@ -4481,13 +4481,23 @@ func (a *App) UpdateQSO(q qso.QSO) error {
// bound", so two empty strings mean the whole log. `to` is inclusive: a bare date
// is stretched to 23:59:59 of that day, otherwise picking today as the end would
// silently drop today's QSOs.
func (a *App) GetLogStats(fromISO, toISO, contestID string, year int) (qso.Stats, error) {
func (a *App) GetLogStats(fromISO, toISO, contestID string, year int, operator string) (qso.Stats, error) {
if a.qso == nil {
return qso.Stats{}, fmt.Errorf("db not initialized")
}
from := parseStatsBound(fromISO, false)
to := parseStatsBound(toISO, true)
return a.qso.Stats(a.ctx, from, to, contestID, year)
return a.qso.Stats(a.ctx, from, to, contestID, year, operator)
}
// GetOperators lists the distinct operators present in the log (upper-cased),
// for the Statistics operator picker. "—" stands for QSOs the station owner
// logged himself (empty OPERATOR). Sorted, owner-bucket last.
func (a *App) GetOperators() ([]string, error) {
if a.qso == nil {
return nil, fmt.Errorf("db not initialized")
}
return a.qso.Operators(a.ctx)
}
// GetContestRuns lists the (contest, year) pairs actually present in the log, so