fix(awards): stack the reference validity dates, and prove the award fallback
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.
This commit is contained in:
@@ -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 }) {
|
function Field2({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-[120px_1fr] items-center gap-2">
|
<div className="grid grid-cols-[120px_1fr] items-center gap-2">
|
||||||
@@ -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
|
Left empty the award's own dates govern, which is why they show
|
||||||
as the placeholder: the operator can see what "empty" inherits
|
as the placeholder: the operator can see what "empty" inherits
|
||||||
instead of having to remember. */}
|
instead of having to remember. */}
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<Field2 label={t('awed.refValidFrom')}>
|
||||||
<Field2 label={t('awed.refValidFrom')}>
|
<Input type="date" className="h-8 w-44" value={sel.valid_from ?? ''}
|
||||||
<Input type="date" className="h-8" value={sel.valid_from ?? ''}
|
onChange={(e) => patchSel({ valid_from: e.target.value })} />
|
||||||
onChange={(e) => patchSel({ valid_from: e.target.value })} />
|
</Field2>
|
||||||
</Field2>
|
<Field2 label={t('awed.refValidTo')}>
|
||||||
<Field2 label={t('awed.refValidTo')}>
|
<Input type="date" className="h-8 w-44" value={sel.valid_to ?? ''}
|
||||||
<Input type="date" className="h-8" value={sel.valid_to ?? ''}
|
onChange={(e) => patchSel({ valid_to: e.target.value })} />
|
||||||
onChange={(e) => patchSel({ valid_to: e.target.value })} />
|
</Field2>
|
||||||
</Field2>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{(awardValidFrom || awardValidTo)
|
{(awardValidFrom || awardValidTo)
|
||||||
? t('awed.refValidHintAward', { from: awardValidFrom || '—', to: awardValidTo || '—' })
|
? t('awed.refValidHintAward', { from: openEnded(awardValidFrom), to: openEnded(awardValidTo) })
|
||||||
: t('awed.refValidHint')}
|
: t('awed.refValidHint')}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-end pt-1"><Button size="sm" className="h-7" onClick={() => sel && saveRef(sel)}><Save className="size-3.5 mr-1" /> {t('awed.saveReference')}</Button></div>
|
<div className="flex justify-end pt-1"><Button size="sm" className="h-7" onClick={() => sel && saveRef(sel)}><Save className="size-3.5 mr-1" /> {t('awed.saveReference')}</Button></div>
|
||||||
|
|||||||
@@ -76,3 +76,34 @@ func TestExpiredRefStopsCountingForLaterQSOs(t *testing.T) {
|
|||||||
t.Errorf("a reference with no window is unaffected: got %v", got)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user