feat(counties): right-click to re-derive a US county from the ULS database

The automatic pass fills a blank county only, and deliberately: a value the
operator or QRZ supplied usually beats one derived from a ZIP code. That leaves
no way at all to correct a county already stored — and the Connecticut planning
regions are exactly that case. Every CT contact logged before that correction
holds a county the state abolished in 2022, and filling blanks never reaches
one of them.

So the new entry OVERWRITES, because it is asked for by hand on a chosen set of
rows precisely because the stored value is believed wrong. Only US entities are
touched, only callsigns the database holds, and only when the answer actually
differs — so re-running it on a mixed selection is safe and the count reported
is the number of counties that really changed. Award refs are re-materialised:
the county IS the reference for CQ USA-CA and the state for WAS.

The entry appears only once the database has been downloaded, and appears
without a restart when one finishes — an action that can only ever answer "no
database" is not worth a line in a menu this long.
This commit is contained in:
2026-08-16 10:45:21 +02:00
parent 5770c40d89
commit 3ed2d957e5
10 changed files with 123 additions and 15 deletions
+60
View File
@@ -10549,6 +10549,66 @@ func (a *App) applyULSCounty(q *qso.QSO) {
} }
} }
// UpdateQSOsCountyFromULS re-derives the county of the selected US contacts from
// the offline ULS database, and OVERWRITES what is there.
//
// That is the difference from applyULSCounty beside it, and it is deliberate.
// The automatic pass fills blanks only, because a value the operator or QRZ
// supplied is usually better than one derived from a ZIP code. This one is asked
// for by hand, on a chosen set of rows, precisely BECAUSE the stored county is
// believed wrong — the Connecticut planning regions are the case that prompted
// it: every CT contact logged before that correction holds a county the state
// abolished in 2022, and no amount of filling blanks reaches them.
//
// Only US entities are touched, only callsigns the database knows, and only when
// the answer actually differs — so re-running it on a mixed selection is safe
// and the count reported is the number of counties that really changed.
func (a *App) UpdateQSOsCountyFromULS(ids []int64) (int, error) {
if a.qso == nil {
return 0, fmt.Errorf("db not initialized")
}
if a.uls == nil || a.uls.Count() == 0 {
return 0, fmt.Errorf("the US county database has not been downloaded yet — Settings Awards US counties")
}
changed := 0
for _, id := range ids {
q, err := a.qso.GetByID(a.ctx, id)
if err != nil || q.DXCC == nil {
continue
}
switch *q.DXCC {
case 291, 110, 6: // United States, Hawaii, Alaska
default:
continue
}
loc, ok := a.uls.Resolve(q.Callsign)
if !ok {
continue
}
cnty := loc.CNTY()
if cnty == "" {
continue
}
if q.County == cnty && (loc.State == "" || q.State == loc.State) {
continue
}
q.County = cnty
if loc.State != "" {
q.State = loc.State
}
if a.qso.Update(a.ctx, q) == nil {
changed++
}
}
if changed > 0 {
// The county IS the reference for CQ USA-CA and the state for WAS, so the
// stored award refs are stale the moment it changes.
a.invalidateAwardStats()
a.materializeAwardRefsForIDs(ids)
}
return changed, nil
}
// ULSStatusResult reports whether the county database is loaded, and how fresh. // ULSStatusResult reports whether the county database is loaded, and how fresh.
type ULSStatusResult struct { type ULSStatusResult struct {
Count int `json:"count"` Count int `json:"count"`
+6 -2
View File
@@ -2,8 +2,12 @@
{ {
"version": "0.25.6", "version": "0.25.6",
"date": "", "date": "",
"en": [], "en": [
"fr": [] "Right-click: update the US county of the selected contacts from the ULS database, replacing a county since renamed or abolished."
],
"fr": [
"Clic droit : mettre à jour le comté US des contacts sélectionnés depuis la base ULS, pour remplacer un comté renommé ou supprimé."
]
}, },
{ {
"version": "0.25.5", "version": "0.25.5",
+27 -7
View File
@@ -11,7 +11,7 @@ import {
SaveCabrilloFile, ExportCabrillo, ExportCabrilloFiltered, ExportCabrilloSelected, SaveCabrilloFile, ExportCabrillo, ExportCabrilloFiltered, ExportCabrilloSelected,
ContestDupe, ContestDupe,
GetQSO, UpdateQSO, DeleteQSO, DeleteQSOs, DeleteAllQSO, GetQSO, UpdateQSO, DeleteQSO, DeleteQSOs, DeleteAllQSO,
UpdateQSOsFromCty, UpdateQSOsFromQRZ, UpdateQSOsFromClublog, UploadQSOsManual, SendQSORecordingEmail, UpdateQSOsFromCty, UpdateQSOsFromQRZ, UpdateQSOsFromClublog, UpdateQSOsCountyFromULS, ULSStatus, UploadQSOsManual, SendQSORecordingEmail,
LookupCallsign, GetStationSettings, GetListsSettings, LookupCallsign, GetStationSettings, GetListsSettings,
GetStartupStatus, CheckForUpdate, DownloadAndApplyUpdate, GetLiveStations, GetWhatsNew, GetChangelog, GetStartupStatus, CheckForUpdate, DownloadAndApplyUpdate, GetLiveStations, GetWhatsNew, GetChangelog,
SMTPConfigured, SendLogToDeveloper, SMTPConfigured, SendLogToDeveloper,
@@ -3583,6 +3583,26 @@ export default function App() {
catch (e: any) { setError(String(e?.message ?? e)); } catch (e: any) { setError(String(e?.message ?? e)); }
finally { setBulkProgress(null); } finally { setBulkProgress(null); }
} }
// Whether the offline US county database holds anything. It gates the
// right-click entry below: an action that can only answer "no database" is
// not worth a line in a menu this long. Re-read when a download finishes, so
// the entry appears without a restart.
const [ulsReady, setUlsReady] = useState(false);
useEffect(() => {
const read = () => ULSStatus().then((s: any) => setUlsReady((s?.count ?? 0) > 0)).catch(() => {});
read();
const off = EventsOn('uls:done', () => { read(); });
return () => { off(); };
}, []);
// Re-derive the county of the selected US contacts from the offline ULS
// database, OVERWRITING what is stored. Offered only once that database has
// been downloaded — see ulsReady above.
async function bulkUpdateCountyFromULS(ids: number[]) {
if (ids.length === 0) return;
try { await afterBulkUpdate(await UpdateQSOsCountyFromULS(ids as any), t('qctx.fromUlsLabel')); }
catch (e: any) { setError(String(e?.message ?? e)); }
}
async function bulkUpdateFromClublog(ids: number[]) { async function bulkUpdateFromClublog(ids: number[]) {
if (ids.length === 0) return; if (ids.length === 0) return;
try { await afterBulkUpdate(await UpdateQSOsFromClublog(ids as any), 'from ClubLog'); } try { await afterBulkUpdate(await UpdateQSOsFromClublog(ids as any), 'from ClubLog'); }
@@ -5252,7 +5272,7 @@ export default function App() {
return ( return (
<div className="h-full w-full min-h-0 flex flex-col bg-card border border-border rounded-lg overflow-hidden"> <div className="h-full w-full min-h-0 flex flex-col bg-card border border-border rounded-lg overflow-hidden">
<WorkedBeforeGrid key={`wbg-${activeProfileId ?? 'x'}`} wb={wbWithAwards as any} myGrid={station.my_grid} awardCols={awardCols} busy={wbBusy} currentCall={callsign} onRowDoubleClicked={(q) => openEdit(q.id as number)} <WorkedBeforeGrid key={`wbg-${activeProfileId ?? 'x'}`} wb={wbWithAwards as any} myGrid={station.my_grid} awardCols={awardCols} busy={wbBusy} currentCall={callsign} onRowDoubleClicked={(q) => openEdit(q.id as number)}
onUpdateFromCty={bulkUpdateFromCty} onUpdateFromQRZ={bulkUpdateFromQRZ} onUpdateFromClublog={bulkUpdateFromClublog} onUpdateFromCty={bulkUpdateFromCty} onUpdateFromQRZ={bulkUpdateFromQRZ} onUpdateFromClublog={bulkUpdateFromClublog} onUpdateCountyFromULS={ulsReady ? bulkUpdateCountyFromULS : undefined}
onSendTo={bulkSendTo} onSendRecording={bulkSendRecording} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)} onSendTo={bulkSendTo} onSendRecording={bulkSendRecording} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)}
onBulkEdit={openBulkEdit} onExportSelected={exportSelectedADIF} onExportSelectedFields={exportSelectedFields} onBulkEdit={openBulkEdit} onExportSelected={exportSelectedADIF} onExportSelectedFields={exportSelectedFields}
onExportCabrilloSelected={exportSelectedCabrillo} onDelete={(ids) => setDeletingIds(ids)} /> onExportCabrilloSelected={exportSelectedCabrillo} onDelete={(ids) => setDeletingIds(ids)} />
@@ -5282,7 +5302,7 @@ export default function App() {
<div className="h-full w-full min-h-0 flex flex-col rounded-lg overflow-hidden border border-border"> <div className="h-full w-full min-h-0 flex flex-col rounded-lg overflow-hidden border border-border">
<NetControlPanel onLogged={refresh} countries={countries} bands={bands} modes={modes} <NetControlPanel onLogged={refresh} countries={countries} bands={bands} modes={modes}
qsoMenuHandlers={{ qsoMenuHandlers={{
onUpdateFromCty: bulkUpdateFromCty, onUpdateFromQRZ: bulkUpdateFromQRZ, onUpdateFromClublog: bulkUpdateFromClublog, onUpdateFromCty: bulkUpdateFromCty, onUpdateFromQRZ: bulkUpdateFromQRZ, onUpdateFromClublog: bulkUpdateFromClublog, onUpdateCountyFromULS: ulsReady ? bulkUpdateCountyFromULS : undefined,
onSendTo: bulkSendTo, onSendRecording: bulkSendRecording, onSendEQSL: (ids: number[]) => setEqslQsoId(ids[0] ?? null), onSendTo: bulkSendTo, onSendRecording: bulkSendRecording, onSendEQSL: (ids: number[]) => setEqslQsoId(ids[0] ?? null),
onDelete: (ids: number[]) => setDeletingIds(ids), onDelete: (ids: number[]) => setDeletingIds(ids),
}} /> }} />
@@ -5306,7 +5326,7 @@ export default function App() {
onRowDoubleClicked={(q) => openEdit(q.id as number)} onRowDoubleClicked={(q) => openEdit(q.id as number)}
onUpdateFromCty={bulkUpdateFromCty} onUpdateFromCty={bulkUpdateFromCty}
onUpdateFromQRZ={bulkUpdateFromQRZ} onUpdateFromQRZ={bulkUpdateFromQRZ}
onUpdateFromClublog={bulkUpdateFromClublog} onUpdateFromClublog={bulkUpdateFromClublog} onUpdateCountyFromULS={ulsReady ? bulkUpdateCountyFromULS : undefined}
onSendTo={bulkSendTo} onSendTo={bulkSendTo}
onSendRecording={bulkSendRecording} onSendRecording={bulkSendRecording}
onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)}
@@ -6622,7 +6642,7 @@ export default function App() {
onRowDoubleClicked={(q) => openEdit(q.id as number)} onRowDoubleClicked={(q) => openEdit(q.id as number)}
onUpdateFromCty={bulkUpdateFromCty} onUpdateFromCty={bulkUpdateFromCty}
onUpdateFromQRZ={bulkUpdateFromQRZ} onUpdateFromQRZ={bulkUpdateFromQRZ}
onUpdateFromClublog={bulkUpdateFromClublog} onUpdateFromClublog={bulkUpdateFromClublog} onUpdateCountyFromULS={ulsReady ? bulkUpdateCountyFromULS : undefined}
onSendTo={bulkSendTo} onSendTo={bulkSendTo}
onSendRecording={bulkSendRecording} onSendRecording={bulkSendRecording}
onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)}
@@ -6902,7 +6922,7 @@ export default function App() {
<TabsContent value="worked" className="mt-0 flex flex-col min-h-0 flex-1"> <TabsContent value="worked" className="mt-0 flex flex-col min-h-0 flex-1">
<WorkedBeforeGrid key={`wbg-${activeProfileId ?? 'x'}`} wb={wbWithAwards as any} myGrid={station.my_grid} awardCols={awardCols} busy={wbBusy} currentCall={callsign} onRowDoubleClicked={(q) => openEdit(q.id as number)} <WorkedBeforeGrid key={`wbg-${activeProfileId ?? 'x'}`} wb={wbWithAwards as any} myGrid={station.my_grid} awardCols={awardCols} busy={wbBusy} currentCall={callsign} onRowDoubleClicked={(q) => openEdit(q.id as number)}
onUpdateFromCty={bulkUpdateFromCty} onUpdateFromQRZ={bulkUpdateFromQRZ} onUpdateFromClublog={bulkUpdateFromClublog} onSendTo={bulkSendTo} onSendRecording={bulkSendRecording} onUpdateFromCty={bulkUpdateFromCty} onUpdateFromQRZ={bulkUpdateFromQRZ} onUpdateFromClublog={bulkUpdateFromClublog} onUpdateCountyFromULS={ulsReady ? bulkUpdateCountyFromULS : undefined} onSendTo={bulkSendTo} onSendRecording={bulkSendRecording}
onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)}
onBulkEdit={openBulkEdit} onExportSelected={exportSelectedADIF} onExportSelectedFields={exportSelectedFields} onBulkEdit={openBulkEdit} onExportSelected={exportSelectedADIF} onExportSelectedFields={exportSelectedFields}
onExportCabrilloSelected={exportSelectedCabrillo} onDelete={(ids) => setDeletingIds(ids)} /> onExportCabrilloSelected={exportSelectedCabrillo} onDelete={(ids) => setDeletingIds(ids)} />
@@ -6999,7 +7019,7 @@ export default function App() {
<TabsContent value="net" className="mt-0 flex flex-col min-h-0 flex-1"> <TabsContent value="net" className="mt-0 flex flex-col min-h-0 flex-1">
<NetControlPanel onLogged={refresh} countries={countries} bands={bands} modes={modes} <NetControlPanel onLogged={refresh} countries={countries} bands={bands} modes={modes}
qsoMenuHandlers={{ qsoMenuHandlers={{
onUpdateFromCty: bulkUpdateFromCty, onUpdateFromQRZ: bulkUpdateFromQRZ, onUpdateFromClublog: bulkUpdateFromClublog, onUpdateFromCty: bulkUpdateFromCty, onUpdateFromQRZ: bulkUpdateFromQRZ, onUpdateFromClublog: bulkUpdateFromClublog, onUpdateCountyFromULS: ulsReady ? bulkUpdateCountyFromULS : undefined,
onSendTo: bulkSendTo, onSendRecording: bulkSendRecording, onSendEQSL: (ids: number[]) => setEqslQsoId(ids[0] ?? null), onSendTo: bulkSendTo, onSendRecording: bulkSendRecording, onSendEQSL: (ids: number[]) => setEqslQsoId(ids[0] ?? null),
onDelete: (ids: number[]) => setDeletingIds(ids), onDelete: (ids: number[]) => setDeletingIds(ids),
}} /> }} />
@@ -49,6 +49,7 @@ type QSOMenuHandlers = {
onUpdateFromCty?: (ids: number[]) => void; onUpdateFromCty?: (ids: number[]) => void;
onUpdateFromQRZ?: (ids: number[]) => void; onUpdateFromQRZ?: (ids: number[]) => void;
onUpdateFromClublog?: (ids: number[]) => void; onUpdateFromClublog?: (ids: number[]) => void;
onUpdateCountyFromULS?: (ids: number[]) => void;
onSendTo?: (service: string, ids: number[]) => void; onSendTo?: (service: string, ids: number[]) => void;
onSendRecording?: (ids: number[]) => void; onSendRecording?: (ids: number[]) => void;
onSendEQSL?: (ids: number[]) => void; onSendEQSL?: (ids: number[]) => void;
+15 -2
View File
@@ -1,5 +1,5 @@
import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { Globe2, RefreshCw, Upload, BadgeCheck, Mail, FileDown, PencilLine, Trash2 } from 'lucide-react'; import { Globe2, RefreshCw, Upload, BadgeCheck, Mail, FileDown, PencilLine, Trash2, MapPin } from 'lucide-react';
import { useI18n } from '@/lib/i18n'; import { useI18n } from '@/lib/i18n';
export type QSOMenuState = { x: number; y: number; ids: number[] } | null; export type QSOMenuState = { x: number; y: number; ids: number[] } | null;
@@ -10,6 +10,9 @@ type Props = {
onUpdateFromCty: (ids: number[]) => void; onUpdateFromCty: (ids: number[]) => void;
onUpdateFromQRZ: (ids: number[]) => void; onUpdateFromQRZ: (ids: number[]) => void;
onUpdateFromClublog?: (ids: number[]) => void; onUpdateFromClublog?: (ids: number[]) => void;
// Only passed once the offline US county database has been downloaded —
// an entry that can only ever answer "no database" is not worth a line here.
onUpdateCountyFromULS?: (ids: number[]) => void;
onSendTo?: (service: string, ids: number[]) => void; onSendTo?: (service: string, ids: number[]) => void;
onSendRecording?: (ids: number[]) => void; onSendRecording?: (ids: number[]) => void;
onSendEQSL?: (ids: number[]) => void; onSendEQSL?: (ids: number[]) => void;
@@ -36,7 +39,7 @@ const UPLOAD_TARGETS: { service: string; name: string }[] = [
// or picks a command. (We deliberately do NOT close on scroll/resize: the QSO // or picks a command. (We deliberately do NOT close on scroll/resize: the QSO
// list auto-refreshes and AG Grid fires internal scroll events on refresh, // list auto-refreshes and AG Grid fires internal scroll events on refresh,
// which used to dismiss the menu the instant it appeared.) // which used to dismiss the menu the instant it appeared.)
export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete }: Props) { export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onUpdateCountyFromULS, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete }: Props) {
const { t } = useI18n(); const { t } = useI18n();
const boxRef = useRef<HTMLDivElement>(null); const boxRef = useRef<HTMLDivElement>(null);
// Starts at the cursor; the layout effect corrects it once the real size is // Starts at the cursor; the layout effect corrects it once the real size is
@@ -118,6 +121,16 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
<span>{t('qctx.updateClublog')}</span> <span>{t('qctx.updateClublog')}</span>
</button> </button>
)} )}
{onUpdateCountyFromULS && (
<button
className="flex w-full items-center gap-2 px-3 py-1.5 text-left hover:bg-accent/50"
onClick={() => { onUpdateCountyFromULS(menu.ids); onClose(); }}
title={t('qctx.updateUlsCountyTitle')}
>
<MapPin className="size-4 text-info" />
<span>{t('qctx.updateUlsCounty')}</span>
</button>
)}
{(onSendRecording || onSendEQSL) && ( {(onSendRecording || onSendEQSL) && (
<> <>
+3 -1
View File
@@ -64,6 +64,7 @@ type Props = {
onUpdateFromCty?: (ids: number[]) => void; onUpdateFromCty?: (ids: number[]) => void;
onUpdateFromQRZ?: (ids: number[]) => void; onUpdateFromQRZ?: (ids: number[]) => void;
onUpdateFromClublog?: (ids: number[]) => void; onUpdateFromClublog?: (ids: number[]) => void;
onUpdateCountyFromULS?: (ids: number[]) => void;
onSendTo?: (service: string, ids: number[]) => void; onSendTo?: (service: string, ids: number[]) => void;
onSendRecording?: (ids: number[]) => void; onSendRecording?: (ids: number[]) => void;
onSendEQSL?: (ids: number[]) => void; onSendEQSL?: (ids: number[]) => void;
@@ -308,7 +309,7 @@ const sanitizeAwardCols = (st: any[] | null | undefined): any[] =>
return rest; return rest;
}); });
export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal, rowDragCall, passOrder, onGridApi, storageKey, onRowDoubleClicked, onRowClicked, onRowSelected, onRowSelectedQso, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete, onFilteredCountChange, awardCols, rowColors }: Props) { export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal, rowDragCall, passOrder, onGridApi, storageKey, onRowDoubleClicked, onRowClicked, onRowSelected, onRowSelectedQso, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onUpdateCountyFromULS, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete, onFilteredCountChange, awardCols, rowColors }: Props) {
const { t } = useI18n(); const { t } = useI18n();
const gridRef = useRef<any>(null); const gridRef = useRef<any>(null);
const [pickerOpen, setPickerOpen] = useState(false); const [pickerOpen, setPickerOpen] = useState(false);
@@ -716,6 +717,7 @@ export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal,
onUpdateFromCty={(ids) => onUpdateFromCty?.(ids)} onUpdateFromCty={(ids) => onUpdateFromCty?.(ids)}
onUpdateFromQRZ={(ids) => onUpdateFromQRZ?.(ids)} onUpdateFromQRZ={(ids) => onUpdateFromQRZ?.(ids)}
onUpdateFromClublog={onUpdateFromClublog} onUpdateFromClublog={onUpdateFromClublog}
onUpdateCountyFromULS={onUpdateCountyFromULS}
onSendTo={onSendTo} onSendTo={onSendTo}
onSendRecording={onSendRecording} onSendRecording={onSendRecording}
onSendEQSL={onSendEQSL} onSendEQSL={onSendEQSL}
+3 -1
View File
@@ -35,6 +35,7 @@ type Props = {
onUpdateFromCty?: (ids: number[]) => void; onUpdateFromCty?: (ids: number[]) => void;
onUpdateFromQRZ?: (ids: number[]) => void; onUpdateFromQRZ?: (ids: number[]) => void;
onUpdateFromClublog?: (ids: number[]) => void; onUpdateFromClublog?: (ids: number[]) => void;
onUpdateCountyFromULS?: (ids: number[]) => void;
onSendTo?: (service: string, ids: number[]) => void; onSendTo?: (service: string, ids: number[]) => void;
onSendRecording?: (ids: number[]) => void; onSendRecording?: (ids: number[]) => void;
onSendEQSL?: (ids: number[]) => void; onSendEQSL?: (ids: number[]) => void;
@@ -57,7 +58,7 @@ function fmtDate(s: any): string {
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}`; return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}`;
} }
export function WorkedBeforeGrid({ wb, myGrid, busy, currentCall, onRowDoubleClicked, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportCabrilloSelected, onDelete, awardCols }: Props) { export function WorkedBeforeGrid({ wb, myGrid, busy, currentCall, onRowDoubleClicked, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onUpdateCountyFromULS, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportCabrilloSelected, onDelete, awardCols }: Props) {
const { t } = useI18n(); const { t } = useI18n();
const gridRef = useRef<any>(null); const gridRef = useRef<any>(null);
const [pickerOpen, setPickerOpen] = useState(false); const [pickerOpen, setPickerOpen] = useState(false);
@@ -272,6 +273,7 @@ export function WorkedBeforeGrid({ wb, myGrid, busy, currentCall, onRowDoubleCli
onUpdateFromCty={(ids) => onUpdateFromCty?.(ids)} onUpdateFromCty={(ids) => onUpdateFromCty?.(ids)}
onUpdateFromQRZ={(ids) => onUpdateFromQRZ?.(ids)} onUpdateFromQRZ={(ids) => onUpdateFromQRZ?.(ids)}
onUpdateFromClublog={onUpdateFromClublog} onUpdateFromClublog={onUpdateFromClublog}
onUpdateCountyFromULS={onUpdateCountyFromULS}
onSendTo={onSendTo} onSendTo={onSendTo}
onSendRecording={onSendRecording} onSendRecording={onSendRecording}
onSendEQSL={onSendEQSL} onSendEQSL={onSendEQSL}
+2 -2
View File
@@ -420,7 +420,7 @@ const en: Dict = {
'awed.importReplace': 'Replace mine', 'awed.importReplace': 'Replace mine',
'awed.importCopy': 'Import as {code}-2', 'awed.importCopy': 'Import as {code}-2',
// QSO modals (context menu / bulk edit / QSL manager / QSO edit) // QSO modals (context menu / bulk edit / QSL manager / QSO edit)
'qctx.selected': '{n} QSO(s) selected', 'qctx.fixCountry': 'Fix country & zones from cty.dat', 'qctx.updateQrz': 'Update from the callsign databases', 'qctx.updateClublog': 'Update from ClubLog (exceptions)', 'qctx.sendQslEmail': 'Send OpsLog QSL by e-mail', 'qctx.sendRecording': 'Send recording by e-mail', 'qctx.bulkEdit': 'Bulk edit field… ({n})', 'qctx.exportSelectedAdif': 'Export selected to ADIF ({n})', 'qctx.exportSelectedFields': 'Export selected — choose fields… ({n})', 'qctx.exportFilteredAdif': 'Export filtered view to ADIF (no limit)', 'qctx.exportSelectedCabrillo': 'Export selected to Cabrillo ({n})', 'qctx.exportFilteredCabrillo': 'Export filtered view to Cabrillo (no limit)', 'qctx.sendTo': 'Send to {name}', 'qctx.delete': 'Delete {n} QSO(s)…', 'qctx.selected': '{n} QSO(s) selected', 'qctx.fixCountry': 'Fix country & zones from cty.dat', 'qctx.updateQrz': 'Update from the callsign databases', 'qctx.updateClublog': 'Update from ClubLog (exceptions)', 'qctx.updateUlsCounty': 'Update US county from the ULS database', 'qctx.updateUlsCountyTitle': 'Re-derives the county of the selected US contacts from the downloaded ULS database, replacing what is stored. Contacts outside the US, and callsigns the database does not hold, are left alone.', 'qctx.fromUlsLabel': 'from the ULS database', 'qctx.sendQslEmail': 'Send OpsLog QSL by e-mail', 'qctx.sendRecording': 'Send recording by e-mail', 'qctx.bulkEdit': 'Bulk edit field… ({n})', 'qctx.exportSelectedAdif': 'Export selected to ADIF ({n})', 'qctx.exportSelectedFields': 'Export selected — choose fields… ({n})', 'qctx.exportFilteredAdif': 'Export filtered view to ADIF (no limit)', 'qctx.exportSelectedCabrillo': 'Export selected to Cabrillo ({n})', 'qctx.exportFilteredCabrillo': 'Export filtered view to Cabrillo (no limit)', 'qctx.sendTo': 'Send to {name}', 'qctx.delete': 'Delete {n} QSO(s)…',
'exp.title': 'Export to ADIF', 'exp.desc': 'Choose which fields to write.', 'exp.title': 'Export to ADIF', 'exp.desc': 'Choose which fields to write.',
'exp.stdTitle': 'Standard ADIF fields', 'exp.stdDesc': 'Official ADIF 3.1.7 fields only — best for uploading to other logbooks (LoTW, QRZ, Club Log…).', 'exp.stdTitle': 'Standard ADIF fields', 'exp.stdDesc': 'Official ADIF 3.1.7 fields only — best for uploading to other logbooks (LoTW, QRZ, Club Log…).',
'exp.allTitle': 'All OpsLog fields', 'exp.allDesc': 'Everything, including OpsLog-specific and app-defined tags — a full backup you can re-import here.', 'exp.allTitle': 'All OpsLog fields', 'exp.allDesc': 'Everything, including OpsLog-specific and app-defined tags — a full backup you can re-import here.',
@@ -836,7 +836,7 @@ const fr: Dict = {
'awed.importKeepMine': 'Garder le mien', 'awed.importKeepMine': 'Garder le mien',
'awed.importReplace': 'Remplacer le mien', 'awed.importReplace': 'Remplacer le mien',
'awed.importCopy': 'Importer en {code}-2', 'awed.importCopy': 'Importer en {code}-2',
'qctx.selected': '{n} QSO sélectionné(s)', 'qctx.fixCountry': 'Corriger pays et zones depuis cty.dat', 'qctx.updateQrz': 'Mettre à jour depuis les annuaires', 'qctx.updateClublog': 'Mettre à jour depuis ClubLog (exceptions)', 'qctx.sendQslEmail': 'Envoyer la QSL OpsLog par e-mail', 'qctx.sendRecording': "Envoyer l'enregistrement par e-mail", 'qctx.bulkEdit': "Édition groupée d'un champ… ({n})", 'qctx.exportSelectedAdif': 'Exporter la sélection en ADIF ({n})', 'qctx.exportFilteredAdif': 'Exporter la vue filtrée en ADIF (sans limite)', 'qctx.exportSelectedCabrillo': 'Exporter la sélection en Cabrillo ({n})', 'qctx.exportFilteredCabrillo': 'Exporter la vue filtrée en Cabrillo (sans limite)', 'qctx.sendTo': 'Envoyer vers {name}', 'qctx.delete': 'Supprimer {n} QSO…', 'qctx.selected': '{n} QSO sélectionné(s)', 'qctx.fixCountry': 'Corriger pays et zones depuis cty.dat', 'qctx.updateQrz': 'Mettre à jour depuis les annuaires', 'qctx.updateClublog': 'Mettre à jour depuis ClubLog (exceptions)', 'qctx.updateUlsCounty': 'Mettre à jour le comté US depuis la base ULS', 'qctx.updateUlsCountyTitle': 'Recalcule le comté des contacts US sélectionnés depuis la base ULS téléchargée, en remplaçant la valeur enregistrée. Les contacts hors US et les indicatifs absents de la base ne sont pas touchés.', 'qctx.fromUlsLabel': 'depuis la base ULS', 'qctx.sendQslEmail': 'Envoyer la QSL OpsLog par e-mail', 'qctx.sendRecording': "Envoyer l'enregistrement par e-mail", 'qctx.bulkEdit': "Édition groupée d'un champ… ({n})", 'qctx.exportSelectedAdif': 'Exporter la sélection en ADIF ({n})', 'qctx.exportFilteredAdif': 'Exporter la vue filtrée en ADIF (sans limite)', 'qctx.exportSelectedCabrillo': 'Exporter la sélection en Cabrillo ({n})', 'qctx.exportFilteredCabrillo': 'Exporter la vue filtrée en Cabrillo (sans limite)', 'qctx.sendTo': 'Envoyer vers {name}', 'qctx.delete': 'Supprimer {n} QSO…',
'exp.title': 'Exporter en ADIF', 'exp.desc': 'Choisissez les champs à écrire.', 'exp.title': 'Exporter en ADIF', 'exp.desc': 'Choisissez les champs à écrire.',
'exp.stdTitle': 'Champs ADIF standard', 'exp.stdDesc': 'Uniquement les champs officiels ADIF 3.1.7 — idéal pour lenvoi vers dautres carnets (LoTW, QRZ, Club Log…).', 'exp.stdTitle': 'Champs ADIF standard', 'exp.stdDesc': 'Uniquement les champs officiels ADIF 3.1.7 — idéal pour lenvoi vers dautres carnets (LoTW, QRZ, Club Log…).',
'exp.allTitle': 'Tous les champs OpsLog', 'exp.allDesc': 'Tout, y compris les champs spécifiques à OpsLog et les balises applicatives — une sauvegarde complète ré-importable ici.', 'exp.allTitle': 'Tous les champs OpsLog', 'exp.allDesc': 'Tout, y compris les champs spécifiques à OpsLog et les balises applicatives — une sauvegarde complète ré-importable ici.',
+2
View File
@@ -1138,6 +1138,8 @@ export function UpdateAwardReferenceList(arg1:string):Promise<main.AwardRefMeta>
export function UpdateQSO(arg1:qso.QSO):Promise<void>; export function UpdateQSO(arg1:qso.QSO):Promise<void>;
export function UpdateQSOsCountyFromULS(arg1:Array<number>):Promise<number>;
export function UpdateQSOsFromClublog(arg1:Array<number>):Promise<number>; export function UpdateQSOsFromClublog(arg1:Array<number>):Promise<number>;
export function UpdateQSOsFromCty(arg1:Array<number>):Promise<number>; export function UpdateQSOsFromCty(arg1:Array<number>):Promise<number>;
+4
View File
@@ -2218,6 +2218,10 @@ export function UpdateQSO(arg1) {
return window['go']['main']['App']['UpdateQSO'](arg1); return window['go']['main']['App']['UpdateQSO'](arg1);
} }
export function UpdateQSOsCountyFromULS(arg1) {
return window['go']['main']['App']['UpdateQSOsCountyFromULS'](arg1);
}
export function UpdateQSOsFromClublog(arg1) { export function UpdateQSOsFromClublog(arg1) {
return window['go']['main']['App']['UpdateQSOsFromClublog'](arg1); return window['go']['main']['App']['UpdateQSOsFromClublog'](arg1);
} }