feat: backfill name/QTH/grid/address from the last QSO when lookup finds nothing

When a callsign resolves only to cty.dat (not on QRZ/HamQTH, or no lookup service
configured) — or the lookup errors — enrich the entry from the most recent QSO
already in the log with that call. Fills ONLY the fields the provider left empty
and the operator hasn't edited (name, QTH, grid, country, address, state, county,
lat/lon, zones, continent, email, QSL-via), so a real QRZ/HamQTH hit is never
overridden. Uses the worked-before entries (qso_date DESC, [0] = latest) via a
live ref so it works regardless of the lookup/worked-before debounce ordering.
This commit is contained in:
2026-07-25 15:49:23 +02:00
parent 3e7f3832e0
commit 3564eecc36
2 changed files with 42 additions and 0 deletions
+40
View File
@@ -1442,6 +1442,10 @@ export default function App() {
// re-populating a field the operator just cleared.
const lookupGenRef = useRef(0);
const [wb, setWb] = useState<WB | null>(null);
// Live mirror of `wb` so the lookup fallback can read the latest worked-before
// entries synchronously (the two run on separate debounce timers).
const wbRef = useRef<WB | null>(null);
useEffect(() => { wbRef.current = wb; }, [wb]);
const [wbBusy, setWbBusy] = useState(false);
// Per-award columns for the Recent QSOs / Worked-before grids: load the award
@@ -2854,6 +2858,37 @@ export default function App() {
catch { setWb(null); }
finally { setWbBusy(false); }
}
// fillFromLastQso enriches the entry from the LAST QSO we logged with this call
// when the live lookup came up short — the callsign isn't on QRZ/HamQTH, or no
// lookup service is configured (cty.dat then gives country/zones only). It fills
// ONLY the fields the provider left empty and the operator hasn't edited, so a
// real QRZ/HamQTH hit is never overridden. `r` is the provider result (omitted
// on a lookup error, where every provider field counts as empty).
function fillFromLastQso(r?: any) {
const last: any = wbRef.current?.entries?.[0]; // entries are qso_date DESC → most recent
if (!last) return;
const ue = userEditedRef.current;
const empty = (v: any) => (v ?? '') === '';
if (!ue.has('name') && empty(r?.name) && last.name) setName(last.name);
if (!ue.has('qth') && empty(r?.qth) && last.qth) setQth(last.qth);
if (!ue.has('grid') && empty(r?.grid) && !(r?.lat || r?.lon) && last.grid) setGrid(last.grid);
if (!ue.has('country') && empty(r?.country) && last.country) setCountry(last.country);
setDetails((d) => ({
...d,
address: d.address || last.address || '',
state: d.state || last.state || '',
cnty: d.cnty || last.cnty || '',
lat: d.lat ?? (last.lat ?? undefined),
lon: d.lon ?? (last.lon ?? undefined),
dxcc: d.dxcc ?? (last.dxcc || undefined),
cqz: d.cqz ?? (last.cqz || undefined),
ituz: d.ituz ?? (last.ituz || undefined),
cont: d.cont || last.cont || '',
email: d.email || last.email || '',
qsl_via: d.qsl_via || last.qsl_via || '',
}));
if (last.grid || last.lat) setMapZoomSignal((n) => n + 1);
}
async function runLookup(call: string) {
if (call !== lastLookedUpRef.current) resetAutoFill();
const gen = lookupGenRef.current; // invalidated by ESC / resetEntry
@@ -2901,6 +2936,9 @@ export default function App() {
email: d.email || (r.email ?? ''),
qsl_via: d.qsl_via || (r.qsl_via ?? ''),
}));
// Backfill anything the provider didn't supply from the last time we worked
// this call (call not found on QRZ/HamQTH, or lookup off → cty.dat only).
fillFromLastQso(r);
if (r.dxcc && r.dxcc > 0) runWorkedBefore(call, r.dxcc);
// The DX location is now known (grid set above) — force the world map to
// auto-zoom right away, so it doesn't lag behind the resolved QSO.
@@ -2920,6 +2958,8 @@ export default function App() {
if (gen === lookupGenRef.current && call === callsignValRef.current.trim().toUpperCase()) {
setLookupResult(null);
setLookupError(String(e?.message ?? e));
// Lookup failed outright — still borrow from the last logged QSO.
fillFromLastQso();
}
} finally {
// Only clear the spinner if we're still the current lookup — a newer one