diff --git a/changelog.json b/changelog.json index fe81b12..7aad3bc 100644 --- a/changelog.json +++ b/changelog.json @@ -3,10 +3,12 @@ "version": "0.25.7", "date": "", "en": [ - "Opening the Awards panel no longer pulls the whole logbook several times at once — a large log briefly took gigabytes of memory." + "Opening the Awards panel no longer pulls the whole logbook several times at once — a large log briefly took gigabytes of memory.", + "Awards → Missing refs no longer freezes on an award with a huge reference list, POTA above all: the list is searched, not scrolled." ], "fr": [ - "Ouvrir le panneau Awards ne tire plus plusieurs fois le journal entier en même temps — un gros log occupait brièvement des gigaoctets de mémoire." + "Ouvrir le panneau Awards ne tire plus plusieurs fois le journal entier en même temps — un gros log occupait brièvement des gigaoctets de mémoire.", + "Awards → Réf. manquantes ne fige plus sur un diplôme à très longue liste de références, POTA en tête : la liste se cherche, elle ne se déroule plus." ] }, { diff --git a/frontend/src/components/AwardsPanel.tsx b/frontend/src/components/AwardsPanel.tsx index 74b3721..d051761 100644 --- a/frontend/src/components/AwardsPanel.tsx +++ b/frontend/src/components/AwardsPanel.tsx @@ -608,6 +608,10 @@ export function AwardsPanel({ onEditQSO, onAwardsChanged }: { onEditQSO?: (id: n // the missing reference (e.g. a department for DDFM). type MissingSortKey = 'qso_date' | 'callsign' | 'band' | 'mode' | 'country' | 'qth'; +// How many references the assign dropdown will put in the DOM at once. Above +// this a search box appears beside it and the rest are held back, counted. +const REF_MENU_MAX = 300; + function MissingQSOModal({ code, name, onClose, onEditQSO }: { code: string; name: string; onClose: () => void; onEditQSO?: (id: number) => void }) { const { t } = useI18n(); const [qsos, setQsos] = useState([]); @@ -616,6 +620,7 @@ function MissingQSOModal({ code, name, onClose, onEditQSO }: { code: string; nam const [sortKey, setSortKey] = useState('callsign'); const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc'); const [refs, setRefs] = useState>([]); + const [refSearch, setRefSearch] = useState(''); const [assignRef, setAssignRef] = useState(''); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState(''); @@ -629,12 +634,38 @@ function MissingQSOModal({ code, name, onClose, onEditQSO }: { code: string; nam .finally(() => setLoading(false)); }; useEffect(() => { load(); }, [code]); - // The award's reference list drives the "assign" dropdown (e.g. China provinces). + // The award's reference list drives the "assign" dropdown (e.g. China + // provinces) — fetched ONLY once there is something to assign it to. + // + // Missing-reference detection needs a DXCC scope, so a worldwide award like + // POTA always has zero rows here and says so. It was still loading every + // reference behind that message: on a log with the full POTA park list + // imported, tens of thousands of them, feeding a dropdown that could not be + // used for anything. That is the window freezing on a screen with nothing to + // offer. useEffect(() => { + if (loading || qsos.length === 0) { setRefs([]); return; } ListAwardReferences(code) .then((r) => setRefs(((r ?? []) as any[]).map((x) => ({ code: String(x.code).toUpperCase(), name: String(x.name ?? '') })))) .catch(() => setRefs([])); - }, [code]); + }, [code, loading, qsos.length]); + + // What the assign dropdown actually renders. Bounded, and it says how many it + // is holding back rather than silently showing the first few hundred. + const shownRefs = useMemo(() => { + const q = refSearch.trim().toUpperCase(); + const rows = q + ? refs.filter((r) => r.code.includes(q) || r.name.toUpperCase().includes(q)) + : refs; + return rows.slice(0, REF_MENU_MAX); + }, [refs, refSearch]); + const hiddenRefs = useMemo(() => { + const q = refSearch.trim().toUpperCase(); + const total = q + ? refs.filter((r) => r.code.includes(q) || r.name.toUpperCase().includes(q)).length + : refs.length; + return Math.max(0, total - shownRefs.length); + }, [refs, refSearch, shownRefs.length]); const qthOf = (q: any) => String(q.qth || q.notes || ''); const sorted = useMemo(() => { @@ -711,14 +742,29 @@ function MissingQSOModal({ code, name, onClose, onEditQSO }: { code: string; nam {/* Bulk-assign toolbar */}
{t('awp.selectedArrow', { n: sel.size })} + {/* A search box beside the dropdown, and a bounded list inside it. + A reference list can hold tens of thousands of entries (POTA + parks, Russian districts); every one of them as a menu item is + hundreds of thousands of DOM nodes and a window that stops + answering. Nobody scrolls to K-4521 anyway — they type it. */} + {refs.length > REF_MENU_MAX && ( + setRefSearch(e.target.value)} /> + )}