This commit is contained in:
2026-06-06 14:16:30 +02:00
parent f91f9ff3b8
commit 17f7a00bd7
19 changed files with 1278 additions and 91 deletions
+31 -19
View File
@@ -11,7 +11,11 @@ type Meta = { code: string; count: number; can_update: boolean };
// Fields auto-derived from structured QSO data — their awards (DXCC/WAZ/WAS/…)
// are computed, never manually picked, so they don't belong in this picker.
const COMPUTED_FIELDS = new Set(['dxcc', 'cqz', 'ituz', 'prefix', 'callsign', 'state', 'cont', 'country', 'grid']);
// Fields purely derived from the callsign / cty.dat — their awards are computed,
// never picked. NB: 'state' and 'cnty' are NOT here — they're operator-settable
// QSO fields driving predefined-list awards (WAS/RAC/WAJA/JCC), so they ARE
// pickable (a lookup rarely fills the JA prefecture or VE province).
const COMPUTED_FIELDS = new Set(['dxcc', 'cqz', 'ituz', 'prefix', 'callsign', 'cont', 'country', 'grid']);
// If DXCC-filtered auto-results exceed this, require the user to type instead.
const AUTO_SHOW_MAX = 100;
@@ -72,15 +76,26 @@ export function AwardRefSelector({ dxcc, value, onChange }: Props) {
if (!awards.find((a) => a.code === awardCode)) setAwardCode(awards[0].code);
}, [awards, awardCode]);
// Auto-load DXCC-filtered refs on award/dxcc change with empty query.
// Fetches AUTO_SHOW_MAX+1 so we can distinguish "all results shown" from "too many".
// Is the selected award a giant dynamic list (POTA/SOTA/IOTA)? Those carry a
// per-reference DXCC so we filter by entity; predefined lists (WAS/RAC/WAJA)
// are small and their refs may lack a per-ref DXCC, so we load them whole.
const isDynamic = useMemo(
() => !!defs.find((d) => d.code === awardCode)?.dynamic,
[defs, awardCode],
);
// For dynamic lists, restrict to the contacted entity; otherwise load all.
const refDxcc = isDynamic ? (dxcc ?? 0) : 0;
// Auto-load refs on award/dxcc change with empty query. Fetches AUTO_SHOW_MAX+1
// so we can distinguish "all results shown" from "too many to list".
useEffect(() => {
setAutoResults([]);
if (!dxcc) return;
SearchAwardReferences(awardCode, '', dxcc, AUTO_SHOW_MAX + 1)
// Dynamic lists need an entity to scope to; predefined lists load regardless.
if (isDynamic && !dxcc) return;
SearchAwardReferences(awardCode, '', refDxcc, AUTO_SHOW_MAX + 1)
.then((r) => setAutoResults((r ?? []) as any))
.catch(() => {});
}, [awardCode, dxcc]);
}, [awardCode, dxcc, isDynamic, refDxcc]);
// Typed search (2+ chars).
useEffect(() => {
@@ -88,13 +103,13 @@ export function AwardRefSelector({ dxcc, value, onChange }: Props) {
const t = window.setTimeout(async () => {
setBusy(true);
try {
const r = await SearchAwardReferences(awardCode, q, dxcc ?? 0, 50);
const r = await SearchAwardReferences(awardCode, q, refDxcc, 50);
setSearchResults((r ?? []) as any);
} catch { setSearchResults([]); }
finally { setBusy(false); }
}, 200);
return () => window.clearTimeout(t);
}, [awardCode, q, dxcc]);
}, [awardCode, q, refDxcc]);
const tooManyAuto = autoResults.length > AUTO_SHOW_MAX;
// When q is empty/short: show auto-results (if ≤ AUTO_SHOW_MAX); when typing: show search results.
@@ -212,22 +227,19 @@ export function AwardRefSelector({ dxcc, value, onChange }: Props) {
<Loader2 className="size-3 animate-spin" />Searching
</div>
)}
{/* No callsign yet */}
{!busy && !dxcc && q.length < 2 && (
<div className="px-2 py-1.5 text-[11px] text-muted-foreground leading-snug">
Enter a callsign, or type to search.
</div>
)}
{/* DXCC known but too many auto-results → require typed search */}
{!busy && !!dxcc && q.length < 2 && tooManyAuto && (
{/* Too many auto-results → require typed search */}
{!busy && q.length < 2 && tooManyAuto && (
<div className="px-2 py-1.5 text-[11px] text-muted-foreground leading-snug">
Type 2+ chars to search
</div>
)}
{/* DXCC known, auto-results loaded, none found */}
{!busy && !!dxcc && q.length < 2 && !tooManyAuto && autoResults.length === 0 && (
{/* Empty short-query state: prompt for a callsign (dynamic lists) or
note the list is empty (predefined awards with no references). */}
{!busy && q.length < 2 && !tooManyAuto && autoResults.length === 0 && (
<div className="px-2 py-1.5 text-[11px] text-muted-foreground leading-snug">
No references for this entity.
{isDynamic && !dxcc
? 'Enter a callsign, or type to search.'
: 'No references for this entity.'}
</div>
)}
{/* Typed search, no results */}