From dd7b63c0599e418d6bb9e95ae794a5cdafdce1b8 Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Wed, 12 Aug 2026 10:51:12 +0200 Subject: [PATCH] fix(awards): stack the reference validity dates, and prove the award fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two dates side by side put each one in a half-width Field2, which spends 120px on its label column — the second label overlapped the first input and the row ran off the panel. The comment ten lines above says exactly this about the group/subgroup fields; one per row, as everything else in this editor. Also shows a far-future end date as open-ended. RDA carries 9999-12-31 as "no end" and printing it back reads like a real deadline. The claim that an empty per-reference window inherits the award's is now a test rather than a comment: a QSO before the award's own ValidFrom does not count for a reference that has no window of its own, and a narrower reference window still applies on top. --- frontend/src/components/AwardEditor.tsx | 27 ++++++++++++--------- internal/award/refvalidity_test.go | 31 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/AwardEditor.tsx b/frontend/src/components/AwardEditor.tsx index 4cdad24..a7a44ef 100644 --- a/frontend/src/components/AwardEditor.tsx +++ b/frontend/src/components/AwardEditor.tsx @@ -97,6 +97,13 @@ function Chips({ all, value, onToggle }: { all: string[]; value: string[]; onTog ); } +// Awards use a far-future date as "no end" (RDA carries 9999-12-31). Printing it +// back at the operator reads like a real deadline, so show it as open-ended. +function openEnded(d?: string): string { + if (!d) return '—'; + return /^9\d{3}-/.test(d) ? '—' : d; +} + function Field2({ label, children }: { label: string; children: React.ReactNode }) { return (
@@ -1064,19 +1071,17 @@ function ReferencesPanel({ code, presets, meta, awardValidFrom, awardValidTo, on Left empty the award's own dates govern, which is why they show as the placeholder: the operator can see what "empty" inherits instead of having to remember. */} -
- - patchSel({ valid_from: e.target.value })} /> - - - patchSel({ valid_to: e.target.value })} /> - -
+ + patchSel({ valid_from: e.target.value })} /> + + + patchSel({ valid_to: e.target.value })} /> +

{(awardValidFrom || awardValidTo) - ? t('awed.refValidHintAward', { from: awardValidFrom || '—', to: awardValidTo || '—' }) + ? t('awed.refValidHintAward', { from: openEnded(awardValidFrom), to: openEnded(awardValidTo) }) : t('awed.refValidHint')}

diff --git a/internal/award/refvalidity_test.go b/internal/award/refvalidity_test.go index ae6dcdc..7178cfe 100644 --- a/internal/award/refvalidity_test.go +++ b/internal/award/refvalidity_test.go @@ -76,3 +76,34 @@ func TestExpiredRefStopsCountingForLaterQSOs(t *testing.T) { t.Errorf("a reference with no window is unaffected: got %v", got) } } + +// The claim that an empty per-reference window "inherits the award's" has to be +// a fact about the code, not a comment. Compute and MatchQSO both gate on +// inScope, which enforces the award's own dates — so a reference with no window +// of its own is already bounded by them, and duplicating the check per reference +// would only create a second place for the same dates to disagree. +func TestEmptyRefWindowInheritsTheAward(t *testing.T) { + d := Def{ + Code: "RDA", Name: "Russian District Award", Valid: true, + Type: TypeQSOFields, Field: "note", MatchBy: "code", + Confirm: []string{"lotw"}, + ValidFrom: "1991-06-12", // the award itself starts here + } + metas := []RefMeta{{Code: "KL-04", Name: "Kostomuksha", Valid: true}} // no window of its own + + q := func(on string) *qso.QSO { + return &qso.QSO{Callsign: "RA1ABC", Band: "20m", Notes: "KL-04", QSODate: day(on)} + } + if got := MatchQSO(d, metas, q("1991-06-11")); len(got) != 0 { + t.Errorf("a QSO before the AWARD's start counted for a reference with no window of its own: %v", got) + } + if got := MatchQSO(d, metas, q("1991-06-12")); len(got) != 1 { + t.Errorf("a QSO on the award's first day must count: %v", got) + } + + // And a reference window NARROWER than the award's still applies on top. + metas[0].ValidTo = "2025-08-31" + if got := MatchQSO(d, metas, q("2025-09-01")); len(got) != 0 { + t.Errorf("the reference's own end date was ignored: %v", got) + } +}