feat(hamlog): confirmation defaults and grid columns; drop a settings paragraph
Preferences → Confirmations gains a HAMLOG.online row, sent and received, with the same defaults every other service has: a new QSO can start at N so the backlog is meaningful from the first contact. The defaults reach it through a new setExtraDefault — fill() writes through a pointer to a string field and these two live in the extras map, which no pointer reaches. The QSO grid gains four HAMLOG columns (status and date, both directions), hidden by default: a column per service shown to everyone is how a grid becomes unreadable. And the paragraph explaining what "Sent" means is gone from that page.
This commit is contained in:
@@ -344,6 +344,8 @@ const (
|
|||||||
keyQSLDefaultHRDLogStatus = "qsl.hrdlog_status"
|
keyQSLDefaultHRDLogStatus = "qsl.hrdlog_status"
|
||||||
keyQSLDefaultQRZComStatus = "qsl.qrzcom_status"
|
keyQSLDefaultQRZComStatus = "qsl.qrzcom_status"
|
||||||
keyQSLDefaultQRZComCfm = "qsl.qrzcom_confirmed"
|
keyQSLDefaultQRZComCfm = "qsl.qrzcom_confirmed"
|
||||||
|
keyQSLDefaultHamlogStatus = "qsl.hamlog_status"
|
||||||
|
keyQSLDefaultHamlogCfm = "qsl.hamlog_confirmed"
|
||||||
|
|
||||||
// External services (logbook upload). QRZ.com first; Clublog / LoTW
|
// External services (logbook upload). QRZ.com first; Clublog / LoTW
|
||||||
// will add their own keys under the same extsvc.* prefix.
|
// will add their own keys under the same extsvc.* prefix.
|
||||||
@@ -420,6 +422,12 @@ type QSLDefaults struct {
|
|||||||
HRDLogStatus string `json:"hrdlog_status"`
|
HRDLogStatus string `json:"hrdlog_status"`
|
||||||
QRZComStatus string `json:"qrzcom_status"`
|
QRZComStatus string `json:"qrzcom_status"`
|
||||||
QRZComCfm string `json:"qrzcom_confirmed"` // QRZ.com download/confirmed status
|
QRZComCfm string `json:"qrzcom_confirmed"` // QRZ.com download/confirmed status
|
||||||
|
// HAMLOG.online lives in the ADIF extras rather than a column — the standard
|
||||||
|
// names a field for hamlog.EU and none for hamlog.ONLINE — but a default is
|
||||||
|
// a default: an operator who uploads everything wants new contacts to start
|
||||||
|
// at "N" here exactly as they do for Club Log.
|
||||||
|
HamlogStatus string `json:"hamlog_status"`
|
||||||
|
HamlogCfm string `json:"hamlog_confirmed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CATSettings is the user-tweakable rig-control configuration. Stored as
|
// CATSettings is the user-tweakable rig-control configuration. Stored as
|
||||||
@@ -10521,6 +10529,7 @@ func (a *App) GetQSLDefaults() (QSLDefaults, error) {
|
|||||||
keyQSLDefaultEQSLSent, keyQSLDefaultEQSLRcvd,
|
keyQSLDefaultEQSLSent, keyQSLDefaultEQSLRcvd,
|
||||||
keyQSLDefaultClublogStatus, keyQSLDefaultHRDLogStatus,
|
keyQSLDefaultClublogStatus, keyQSLDefaultHRDLogStatus,
|
||||||
keyQSLDefaultQRZComStatus, keyQSLDefaultQRZComCfm,
|
keyQSLDefaultQRZComStatus, keyQSLDefaultQRZComCfm,
|
||||||
|
keyQSLDefaultHamlogStatus, keyQSLDefaultHamlogCfm,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, err
|
return out, err
|
||||||
@@ -10535,6 +10544,8 @@ func (a *App) GetQSLDefaults() (QSLDefaults, error) {
|
|||||||
out.HRDLogStatus = m[keyQSLDefaultHRDLogStatus]
|
out.HRDLogStatus = m[keyQSLDefaultHRDLogStatus]
|
||||||
out.QRZComStatus = m[keyQSLDefaultQRZComStatus]
|
out.QRZComStatus = m[keyQSLDefaultQRZComStatus]
|
||||||
out.QRZComCfm = m[keyQSLDefaultQRZComCfm]
|
out.QRZComCfm = m[keyQSLDefaultQRZComCfm]
|
||||||
|
out.HamlogStatus = m[keyQSLDefaultHamlogStatus]
|
||||||
|
out.HamlogCfm = m[keyQSLDefaultHamlogCfm]
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10556,6 +10567,8 @@ func (a *App) SaveQSLDefaults(d QSLDefaults) error {
|
|||||||
keyQSLDefaultHRDLogStatus: strings.ToUpper(strings.TrimSpace(d.HRDLogStatus)),
|
keyQSLDefaultHRDLogStatus: strings.ToUpper(strings.TrimSpace(d.HRDLogStatus)),
|
||||||
keyQSLDefaultQRZComStatus: strings.ToUpper(strings.TrimSpace(d.QRZComStatus)),
|
keyQSLDefaultQRZComStatus: strings.ToUpper(strings.TrimSpace(d.QRZComStatus)),
|
||||||
keyQSLDefaultQRZComCfm: strings.ToUpper(strings.TrimSpace(d.QRZComCfm)),
|
keyQSLDefaultQRZComCfm: strings.ToUpper(strings.TrimSpace(d.QRZComCfm)),
|
||||||
|
keyQSLDefaultHamlogStatus: strings.ToUpper(strings.TrimSpace(d.HamlogStatus)),
|
||||||
|
keyQSLDefaultHamlogCfm: strings.ToUpper(strings.TrimSpace(d.HamlogCfm)),
|
||||||
} {
|
} {
|
||||||
if err := a.settings.Set(a.ctx, scope+k, v); err != nil {
|
if err := a.settings.Set(a.ctx, scope+k, v); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -10606,6 +10619,27 @@ func applyQSLDefaultsTo(q *qso.QSO, d QSLDefaults) {
|
|||||||
fill(&q.HRDLogUploadStatus, d.HRDLogStatus)
|
fill(&q.HRDLogUploadStatus, d.HRDLogStatus)
|
||||||
fill(&q.QRZComUploadStatus, d.QRZComStatus)
|
fill(&q.QRZComUploadStatus, d.QRZComStatus)
|
||||||
fill(&q.QRZComDownloadStatus, d.QRZComCfm)
|
fill(&q.QRZComDownloadStatus, d.QRZComCfm)
|
||||||
|
// The extras, which fill() cannot reach: it writes through a pointer to a
|
||||||
|
// string field, and these two are map entries. Only set when the QSO does not
|
||||||
|
// already carry them, which is the same rule fill() applies.
|
||||||
|
setExtraDefault(q, hamlogSentKey, d.HamlogStatus)
|
||||||
|
setExtraDefault(q, award.HamlogQSLKey, d.HamlogCfm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setExtraDefault writes a default into an ADIF extras key, leaving an existing
|
||||||
|
// value alone. A blank default means "say nothing", not "write an empty string":
|
||||||
|
// an empty extra would be carried into every export.
|
||||||
|
func setExtraDefault(q *qso.QSO, key, def string) {
|
||||||
|
def = strings.ToUpper(strings.TrimSpace(def))
|
||||||
|
if def == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if q.Extras == nil {
|
||||||
|
q.Extras = map[string]string{}
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(q.Extras[key]) == "" {
|
||||||
|
q.Extras[key] = def
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── External services (logbook upload) ─────────────────────────────────
|
// ── External services (logbook upload) ─────────────────────────────────
|
||||||
|
|||||||
+6
-2
@@ -10,7 +10,9 @@
|
|||||||
"The row colours in Appearance can be scoped to HAMLOG.online as well, alongside paper, LoTW, eQSL and QRZ.com.",
|
"The row colours in Appearance can be scoped to HAMLOG.online as well, alongside paper, LoTW, eQSL and QRZ.com.",
|
||||||
"HAMLOG.online appears in the QSL Manager too: pick it to see every QSO never sent there and upload the backlog in one go. A contact already sent carries the date it went, so a second sweep does not send it twice.",
|
"HAMLOG.online appears in the QSL Manager too: pick it to see every QSO never sent there and upload the backlog in one go. A contact already sent carries the date it went, so a second sweep does not send it twice.",
|
||||||
"Right-clicking QSOs offers HAMLOG.online among the upload targets, and the toast names each service properly instead of echoing its internal id.",
|
"Right-clicking QSOs offers HAMLOG.online among the upload targets, and the toast names each service properly instead of echoing its internal id.",
|
||||||
"Bulk edit can set HAMLOG.online sent and received, each with its status and its date — the same four fields every other service has there. A log uploaded to their site by hand had no way to be marked as sent, so OpsLog kept offering to upload every contact again."
|
"Bulk edit can set HAMLOG.online sent and received, each with its status and its date — the same four fields every other service has there. A log uploaded to their site by hand had no way to be marked as sent, so OpsLog kept offering to upload every contact again.",
|
||||||
|
"HAMLOG.online joins the Confirmations defaults and the QSO grid columns — sent and received, each with its status and date, hidden by default like the other extras-backed columns.",
|
||||||
|
"Removed the explanatory paragraph from the Confirmations page."
|
||||||
],
|
],
|
||||||
"fr": [
|
"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.",
|
"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.",
|
||||||
@@ -20,7 +22,9 @@
|
|||||||
"Les couleurs de lignes dans Apparence peuvent aussi être limitées à HAMLOG.online, aux côtés du papier, de LoTW, d'eQSL et de QRZ.com.",
|
"Les couleurs de lignes dans Apparence peuvent aussi être limitées à HAMLOG.online, aux côtés du papier, de LoTW, d'eQSL et de QRZ.com.",
|
||||||
"HAMLOG.online figure aussi dans le gestionnaire QSL : on le choisit pour voir tous les QSO jamais envoyés là-bas et expédier l'arriéré d'un coup. Un contact déjà envoyé porte la date de son départ, donc un second passage ne le renvoie pas.",
|
"HAMLOG.online figure aussi dans le gestionnaire QSL : on le choisit pour voir tous les QSO jamais envoyés là-bas et expédier l'arriéré d'un coup. Un contact déjà envoyé porte la date de son départ, donc un second passage ne le renvoie pas.",
|
||||||
"Le clic droit sur des QSO propose HAMLOG.online parmi les destinations d'envoi, et la notification nomme correctement chaque service au lieu d'afficher son identifiant interne.",
|
"Le clic droit sur des QSO propose HAMLOG.online parmi les destinations d'envoi, et la notification nomme correctement chaque service au lieu d'afficher son identifiant interne.",
|
||||||
"L'édition en masse peut renseigner HAMLOG.online envoyé et reçu, chacun avec son statut et sa date — les mêmes quatre champs que les autres services. Un journal téléversé à la main sur leur site n'avait aucun moyen d'être marqué comme envoyé, et OpsLog proposait donc de renvoyer tous les contacts."
|
"L'édition en masse peut renseigner HAMLOG.online envoyé et reçu, chacun avec son statut et sa date — les mêmes quatre champs que les autres services. Un journal téléversé à la main sur leur site n'avait aucun moyen d'être marqué comme envoyé, et OpsLog proposait donc de renvoyer tous les contacts.",
|
||||||
|
"HAMLOG.online rejoint les valeurs par défaut des Confirmations et les colonnes de la grille des QSO — envoyé et reçu, chacun avec son statut et sa date, masquées par défaut comme les autres colonnes issues des extras.",
|
||||||
|
"Suppression du paragraphe explicatif de la page Confirmations."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -207,6 +207,14 @@ export const makeColCatalog = (t: TFn, myGrid?: string): ColEntry[] => [
|
|||||||
{ group: 'QSL', label: t('rqg.c.opslog_qsl_card_sent'), colId: 'opslog_qsl_card_sent', headerName: t('rqg.c.opslog_qsl_card_sent'), width: 100, cellClass: qslStatusCellClass, valueGetter: (p) => { const e = (p.data as any)?.extras ?? {}; return (e['APP_OPSLOG_QSL_SENT'] || e['APP_OPSLOG_QSL_CARD_SENT']) ? 'Y' : 'N'; }, defaultVisible: true },
|
{ group: 'QSL', label: t('rqg.c.opslog_qsl_card_sent'), colId: 'opslog_qsl_card_sent', headerName: t('rqg.c.opslog_qsl_card_sent'), width: 100, cellClass: qslStatusCellClass, valueGetter: (p) => { const e = (p.data as any)?.extras ?? {}; return (e['APP_OPSLOG_QSL_SENT'] || e['APP_OPSLOG_QSL_CARD_SENT']) ? 'Y' : 'N'; }, defaultVisible: true },
|
||||||
// App-specific: operator marked a QSL as RECEIVED for this QSO (drives PSE/TNX).
|
// App-specific: operator marked a QSL as RECEIVED for this QSO (drives PSE/TNX).
|
||||||
{ group: 'QSL', label: t('rqg.c.opslog_qsl_card_rcvd'), colId: 'opslog_qsl_card_rcvd', headerName: t('rqg.c.opslog_qsl_card_rcvd'), width: 100, cellClass: qslStatusCellClass, valueGetter: (p) => { const e = (p.data as any)?.extras ?? {}; return e['APP_OPSLOG_QSL_RCVD'] ? 'Y' : 'N'; }, defaultVisible: false },
|
{ group: 'QSL', label: t('rqg.c.opslog_qsl_card_rcvd'), colId: 'opslog_qsl_card_rcvd', headerName: t('rqg.c.opslog_qsl_card_rcvd'), width: 100, cellClass: qslStatusCellClass, valueGetter: (p) => { const e = (p.data as any)?.extras ?? {}; return e['APP_OPSLOG_QSL_RCVD'] ? 'Y' : 'N'; }, defaultVisible: false },
|
||||||
|
// HAMLOG.online. No promoted column — the ADIF standard names a field for
|
||||||
|
// hamlog.EU and none for hamlog.ONLINE — so these read the extras, exactly
|
||||||
|
// like the OpsLog card columns above. Hidden by default: a column per
|
||||||
|
// service, shown to everyone, is how a grid becomes unreadable.
|
||||||
|
{ group: 'QSL', label: t('rqg.c.hamlog_sent'), colId: 'hamlog_sent', headerName: t('rqg.h.hamlog_sent'), width: 100, cellClass: qslStatusCellClass, valueGetter: (p) => { const e = (p.data as any)?.extras ?? {}; return e['APP_OPSLOG_HAMLOG_SENT'] || 'N'; }, defaultVisible: false },
|
||||||
|
{ group: 'QSL', label: t('rqg.c.hamlog_sent_date'), colId: 'hamlog_sent_date', headerName: t('rqg.h.hamlog_sent_date'), width: 110, valueGetter: (p) => fmtDateOnly((p.data as any)?.extras?.['APP_OPSLOG_HAMLOG_SENT_DATE']), defaultVisible: false },
|
||||||
|
{ group: 'QSL', label: t('rqg.c.hamlog_rcvd'), colId: 'hamlog_rcvd', headerName: t('rqg.h.hamlog_rcvd'), width: 100, cellClass: qslStatusCellClass, valueGetter: (p) => { const e = (p.data as any)?.extras ?? {}; return e['APP_OPSLOG_HAMLOG_QSL'] || 'N'; }, defaultVisible: false },
|
||||||
|
{ group: 'QSL', label: t('rqg.c.hamlog_rcvd_date'), colId: 'hamlog_rcvd_date', headerName: t('rqg.h.hamlog_rcvd_date'), width: 110, valueGetter: (p) => fmtDateOnly((p.data as any)?.extras?.['APP_OPSLOG_HAMLOG_QSL_DATE']), defaultVisible: false },
|
||||||
// App-specific: when the QSO's audio recording was e-mailed to the station.
|
// App-specific: when the QSO's audio recording was e-mailed to the station.
|
||||||
{ group: 'QSL', label: t('rqg.c.opslog_recording_sent'), colId: 'opslog_recording_sent', headerName: t('rqg.h.opslog_recording_sent'), width: 100, cellClass: qslStatusCellClass, valueGetter: (p) => { const e = (p.data as any)?.extras ?? {}; return e['APP_OPSLOG_RECORDING_SENT'] ? 'Y' : 'N'; }, defaultVisible: false },
|
{ group: 'QSL', label: t('rqg.c.opslog_recording_sent'), colId: 'opslog_recording_sent', headerName: t('rqg.h.opslog_recording_sent'), width: 100, cellClass: qslStatusCellClass, valueGetter: (p) => { const e = (p.data as any)?.extras ?? {}; return e['APP_OPSLOG_RECORDING_SENT'] ? 'Y' : 'N'; }, defaultVisible: false },
|
||||||
|
|
||||||
|
|||||||
@@ -1708,6 +1708,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
eqsl_sent: string; eqsl_rcvd: string;
|
eqsl_sent: string; eqsl_rcvd: string;
|
||||||
clublog_status: string; hrdlog_status: string; qrzcom_status: string;
|
clublog_status: string; hrdlog_status: string; qrzcom_status: string;
|
||||||
qrzcom_confirmed: string;
|
qrzcom_confirmed: string;
|
||||||
|
hamlog_status: string; hamlog_confirmed: string;
|
||||||
};
|
};
|
||||||
const [qslDefaults, setQslDefaults] = useState<QSLDefaults>({
|
const [qslDefaults, setQslDefaults] = useState<QSLDefaults>({
|
||||||
qsl_sent: '', qsl_rcvd: '',
|
qsl_sent: '', qsl_rcvd: '',
|
||||||
@@ -1715,6 +1716,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
eqsl_sent: '', eqsl_rcvd: '',
|
eqsl_sent: '', eqsl_rcvd: '',
|
||||||
clublog_status: '', hrdlog_status: '', qrzcom_status: '',
|
clublog_status: '', hrdlog_status: '', qrzcom_status: '',
|
||||||
qrzcom_confirmed: '',
|
qrzcom_confirmed: '',
|
||||||
|
hamlog_status: '', hamlog_confirmed: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// External services (logbook upload). One block per service; only QRZ is
|
// External services (logbook upload). One block per service; only QRZ is
|
||||||
@@ -5206,9 +5208,6 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-border/60 pt-3 space-y-3">
|
<div className="border-t border-border/60 pt-3 space-y-3">
|
||||||
<div className="text-[11px] text-muted-foreground">
|
|
||||||
{t('conf.note')}
|
|
||||||
</div>
|
|
||||||
{/* Clublog */}
|
{/* Clublog */}
|
||||||
<div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
|
<div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
|
||||||
<Label className="text-sm font-medium pb-1.5">Clublog</Label>
|
<Label className="text-sm font-medium pb-1.5">Clublog</Label>
|
||||||
@@ -5239,6 +5238,18 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
{renderSelect('qrzcom_confirmed', FULL_OPTIONS)}
|
{renderSelect('qrzcom_confirmed', FULL_OPTIONS)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* HAMLOG.online */}
|
||||||
|
<div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
|
||||||
|
<Label className="text-sm font-medium pb-1.5">HAMLOG.online</Label>
|
||||||
|
<div>
|
||||||
|
<Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">{t('conf.sent')}</Label>
|
||||||
|
{renderSelect('hamlog_status', FULL_OPTIONS)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">{t('conf.rcvd')}</Label>
|
||||||
|
{renderSelect('hamlog_confirmed', FULL_OPTIONS)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3032,6 +3032,8 @@ export namespace main {
|
|||||||
hrdlog_status: string;
|
hrdlog_status: string;
|
||||||
qrzcom_status: string;
|
qrzcom_status: string;
|
||||||
qrzcom_confirmed: string;
|
qrzcom_confirmed: string;
|
||||||
|
hamlog_status: string;
|
||||||
|
hamlog_confirmed: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new QSLDefaults(source);
|
return new QSLDefaults(source);
|
||||||
@@ -3049,6 +3051,8 @@ export namespace main {
|
|||||||
this.hrdlog_status = source["hrdlog_status"];
|
this.hrdlog_status = source["hrdlog_status"];
|
||||||
this.qrzcom_status = source["qrzcom_status"];
|
this.qrzcom_status = source["qrzcom_status"];
|
||||||
this.qrzcom_confirmed = source["qrzcom_confirmed"];
|
this.qrzcom_confirmed = source["qrzcom_confirmed"];
|
||||||
|
this.hamlog_status = source["hamlog_status"];
|
||||||
|
this.hamlog_confirmed = source["hamlog_confirmed"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class QSLEmailTemplates {
|
export class QSLEmailTemplates {
|
||||||
|
|||||||
Reference in New Issue
Block a user