feat(db): fill in the distances nothing ever recorded

The published page derives a distance as it renders, which fixed the empty
column there but not the cause: the field is empty in the database, so it goes
out empty in every ADIF export and leaves the same gap in whoever imports it.

BackfillDistances computes it from the two locators for QSOs that have both.
Only where it is EMPTY: a stored distance came from the log that recorded the
contact, which knew the real positions, and two four-character squares are a
worse answer that must not overwrite a better one. Whole kilometres, for the
same reason the published column is.

In the Database panel rather than beside the county backfill, which lives under
US Counties: this one touches the whole logbook, whatever country the QSOs are in.
This commit is contained in:
2026-08-11 19:49:03 +02:00
parent d3a405f4f6
commit 92a5f30ac0
7 changed files with 109 additions and 5 deletions
+24 -1
View File
@@ -45,7 +45,7 @@ import {
TestLoTWUpload, ListTQSLStationLocations,
DownloadLoTWUsers, GetLoTWUsersStatus,
GetScpStatus, SetScpEnabled, DownloadScp,
DownloadULSCounties, ULSStatus, BackfillUSCounties,
DownloadULSCounties, ULSStatus, BackfillUSCounties, BackfillDistances,
ComputeStationInfo,
GetUIPref, SetUIPref,
GetFlexState, GetFlexBandAntennas, SaveFlexBandAntennas, GetFlexBandPower, SaveFlexBandPower,
@@ -1495,6 +1495,14 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
try { await DownloadULSCounties(); }
catch (e: any) { setUlsBusy(false); setUlsProgress(null); setUlsMsg({ ok: false, text: String(e?.message ?? e) }); }
};
const [distBusy, setDistBusy] = useState(false);
const [distMsg, setDistMsg] = useState<string | null>(null);
const runDistBackfill = async () => {
setDistBusy(true); setDistMsg(null);
try { const r: any = await BackfillDistances(); setDistMsg(t("db.distDone", { f: r?.filled ?? 0, s: r?.scanned ?? 0, n: r?.no_grid ?? 0 })); }
catch (e: any) { setDistMsg(String(e?.message ?? e)); }
finally { setDistBusy(false); }
};
const runBackfill = async () => {
setBackfillBusy(true); setBackfillMsg(null);
try { const r: any = await BackfillUSCounties(); setBackfillMsg(t('uscty.backfillDone', { c: r?.county ?? 0, g: r?.grid ?? 0, s: r?.scanned ?? 0 })); }
@@ -5323,6 +5331,21 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
<>
<SectionHeader title={t('sec.database')} />
{/* Distance backfill. Here rather than beside the county one, which lives
in the US Counties panel: this touches the whole logbook, whatever
country the QSOs are in. */}
<div className="rounded-md border border-border p-3 space-y-2 max-w-2xl mb-5">
<div className="text-xs font-medium">{t('db.distTitle')}</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">{t('db.distIntro')}</p>
<div className="flex items-center gap-3">
<Button size="sm" variant="secondary" onClick={runDistBackfill} disabled={distBusy}>
{distBusy ? <Loader2 className="size-3.5 animate-spin mr-1.5" /> : null}
{t('db.distRun')}
</Button>
{distMsg && <span className="text-xs text-muted-foreground">{distMsg}</span>}
</div>
</div>
{/* Settings / application database (settings + profiles) always shown,
distinct from the QSO logbook so the two are never confused. */}
<div className="space-y-2 max-w-2xl mb-5 border border-border/60 rounded-md p-3">