From c887b77b5fb39ca04432af1b99e6d8e782b68852 Mon Sep 17 00:00:00 2001 From: rouggy Date: Sun, 23 Aug 2026 14:58:01 +0200 Subject: [PATCH] feat(export): right-click export can keep the OpsLog fields The right-click export always called ExportADIFSelected with includeAppFields=false, so it wrote strictly standard ADIF. That is the right default when the file is going to another logger, but it silently drops every APP_ tag -- and an award reference such as RDA@KR-04 lives in APP_OPSLOG_AWARDREFS and nowhere else. Backing up a selection, or moving it to another OpsLog, lost the awards. Adds a second entry rather than a flag on the first: the two exports answer different questions, and a checkbox in a context menu would have to be read before every export. --- changelog.json | 6 ++++-- frontend/src/App.tsx | 19 ++++++++++++++----- frontend/src/components/QSOContextMenu.tsx | 18 +++++++++++++++++- frontend/src/components/RecentQSOsGrid.tsx | 4 +++- frontend/src/components/WorkedBeforeGrid.tsx | 4 +++- frontend/src/lib/i18n.tsx | 4 ++-- 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/changelog.json b/changelog.json index 2300063..d5d9fca 100644 --- a/changelog.json +++ b/changelog.json @@ -15,7 +15,8 @@ "Removed the explanatory paragraph from the Confirmations page.", "HAMLOG.online confirmations are read from the site's own ADIF field, APP_HAMLOG_QSO_CFM, taken from a real export. Import a log downloaded from HAMLOG and its confirmations count for awards straight away, with nothing to rename and no mapping to configure.", "The four HAMLOG.online fields are filterable too, so 'never sent there' is a filter and not a guess.", - "RDA: a read-only comparison of the two district sources — the offline RDA database against the CNTY carried by a log imported from HAMLOG.online — reporting how many agree, how many differ, and listing the disagreements with the contact, both districts, and whether the database answer was a dated record or an assumption." + "RDA: a read-only comparison of the two district sources — the offline RDA database against the CNTY carried by a log imported from HAMLOG.online — reporting how many agree, how many differ, and listing the disagreements with the contact, both districts, and whether the database answer was a dated record or an assumption.", + "The QSO right-click menu can now export the selection to ADIF with the OpsLog fields, so the award references (RDA@KR-04 and the like) travel with the contacts. The existing entry still writes standard ADIF for other loggers." ], "fr": [ "Yaesu : une ligne ANT dans le panneau du poste sélectionne la prise d'antenne, et le choix est mémorisé pour la bande sur laquelle il a été fait — on change de bande, l'antenne suit. Aucune page à configurer : choisir l'antenne une fois sur une bande le dit très bien. Les postes à une seule prise n'affichent jamais la ligne.", @@ -30,7 +31,8 @@ "Suppression du paragraphe explicatif de la page Confirmations.", "Les confirmations HAMLOG.online sont lues dans le champ ADIF du site lui-même, APP_HAMLOG_QSO_CFM, relevé sur un export réel. Il suffit d'importer un journal téléchargé depuis HAMLOG pour que ses confirmations comptent aussitôt pour les diplômes, sans rien renommer ni configurer.", "Les quatre champs HAMLOG.online sont aussi filtrables, pour que « jamais envoyé là-bas » soit un filtre et non une supposition.", - "RDA : une comparaison en lecture seule des deux sources de district — la base RDA hors ligne face au CNTY d'un journal importé depuis HAMLOG.online — indiquant combien concordent, combien divergent, et listant les désaccords avec le contact, les deux districts, et si la réponse de la base était un enregistrement daté ou une hypothèse." + "RDA : une comparaison en lecture seule des deux sources de district — la base RDA hors ligne face au CNTY d'un journal importé depuis HAMLOG.online — indiquant combien concordent, combien divergent, et listant les désaccords avec le contact, les deux districts, et si la réponse de la base était un enregistrement daté ou une hypothèse.", + "Le menu contextuel des QSO peut maintenant exporter la sélection en ADIF avec les champs OpsLog, pour que les références de diplômes (RDA@KR-04 et compagnie) suivent les contacts. L'entrée existante écrit toujours de l'ADIF standard pour les autres carnets." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 21a8683..2f152c4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4229,11 +4229,20 @@ export default function App() { } // Right-click "Export selected to ADIF": only the highlighted rows. async function exportSelectedADIF(ids: number[], fields: string[] = []) { + return exportSelectedADIFAs(ids, false, fields); + } + // The same export, keeping the APP_OPSLOG_* tags. Award references are the + // reason it exists: RDA@KR-04 lives in APP_OPSLOG_AWARDREFS, and a standard + // export — rightly — leaves every APP_ tag behind. + async function exportSelectedADIFFull(ids: number[]) { + return exportSelectedADIFAs(ids, true, []); + } + async function exportSelectedADIFAs(ids: number[], includeApp: boolean, fields: string[]) { if (ids.length === 0) return; try { const path = await SaveADIFFile(); if (!path) return; - const r = await ExportADIFSelected(path, false, ids as any, fields); + const r = await ExportADIFSelected(path, includeApp, ids as any, fields); showToast(`Exported ${r.count} selected QSO${r.count > 1 ? 's' : ''} → ${r.path}`); } catch (e: any) { setError(String(e?.message ?? e)); } } @@ -5997,7 +6006,7 @@ export default function App() { openEdit(q.id as number)} onUpdateFromCty={bulkUpdateFromCty} onUpdateFromQRZ={bulkUpdateFromQRZ} onUpdateFromClublog={bulkUpdateFromClublog} onUpdateCountyFromULS={ulsReady ? bulkUpdateCountyFromULS : undefined} onSendTo={bulkSendTo} onSendRecording={bulkSendRecording} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)} - onBulkEdit={openBulkEdit} onExportSelected={exportSelectedADIF} onExportSelectedFields={exportSelectedFields} + onBulkEdit={openBulkEdit} onExportSelected={exportSelectedADIF} onExportSelectedFull={exportSelectedADIFFull} onExportSelectedFields={exportSelectedFields} onExportCabrilloSelected={exportSelectedCabrillo} onDelete={(ids) => setDeletingIds(ids)} /> ); @@ -6054,7 +6063,7 @@ export default function App() { onSendRecording={bulkSendRecording} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)} onBulkEdit={openBulkEdit} - onExportSelected={exportSelectedADIF} + onExportSelected={exportSelectedADIF} onExportSelectedFull={exportSelectedADIFFull} onExportSelectedFields={exportSelectedFields} onExportFiltered={exportFilteredADIF} onDelete={(ids) => setDeletingIds(ids)} @@ -7453,7 +7462,7 @@ export default function App() { onSendRecording={bulkSendRecording} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)} onBulkEdit={openBulkEdit} - onExportSelected={exportSelectedADIF} + onExportSelected={exportSelectedADIF} onExportSelectedFull={exportSelectedADIFFull} onExportSelectedFields={exportSelectedFields} onExportFiltered={exportFilteredADIF} onExportCabrilloSelected={exportSelectedCabrillo} @@ -7786,7 +7795,7 @@ export default function App() { openEdit(q.id as number)} onUpdateFromCty={bulkUpdateFromCty} onUpdateFromQRZ={bulkUpdateFromQRZ} onUpdateFromClublog={bulkUpdateFromClublog} onUpdateCountyFromULS={ulsReady ? bulkUpdateCountyFromULS : undefined} onSendTo={bulkSendTo} onSendRecording={bulkSendRecording} onSendEQSL={(ids) => setEqslQsoId(ids[0] ?? null)} - onBulkEdit={openBulkEdit} onExportSelected={exportSelectedADIF} onExportSelectedFields={exportSelectedFields} + onBulkEdit={openBulkEdit} onExportSelected={exportSelectedADIF} onExportSelectedFull={exportSelectedADIFFull} onExportSelectedFields={exportSelectedFields} onExportCabrilloSelected={exportSelectedCabrillo} onDelete={(ids) => setDeletingIds(ids)} /> diff --git a/frontend/src/components/QSOContextMenu.tsx b/frontend/src/components/QSOContextMenu.tsx index b63979c..b59ec1c 100644 --- a/frontend/src/components/QSOContextMenu.tsx +++ b/frontend/src/components/QSOContextMenu.tsx @@ -18,6 +18,8 @@ type Props = { onSendEQSL?: (ids: number[]) => void; onBulkEdit?: (ids: number[]) => void; onExportSelected?: (ids: number[]) => void; + // Same rows, but keeping the APP_OPSLOG_* tags — award references above all. + onExportSelectedFull?: (ids: number[]) => void; onExportSelectedFields?: (ids: number[]) => void; onExportFiltered?: () => void; onExportCabrilloSelected?: (ids: number[]) => void; @@ -40,7 +42,7 @@ const UPLOAD_TARGETS: { service: string; name: string }[] = [ // 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, // which used to dismiss the menu the instant it appeared.) -export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onUpdateCountyFromULS, 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, onExportSelectedFull, onExportSelectedFields, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete }: Props) { const { t } = useI18n(); const boxRef = useRef(null); // Starts at the cursor; the layout effect corrects it once the real size is @@ -190,6 +192,20 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ {t('qctx.exportSelectedAdif', { n })} )} + {/* The same rows WITH the OpsLog fields. + The export above is standard ADIF — what another logger should be + given — and it drops every APP_ tag, award references included. A + backup, or a move to another OpsLog, needs them: RDA@KR-04 lives in + APP_OPSLOG_AWARDREFS and nowhere else. */} + {onExportSelectedFull && ( + + )} {onExportSelectedFields && (