feat(qsl): set the QSL route per direction from the QSL Manager
The paper form had one "Via", which since the QSL_VIA split writes QSL_SENT_VIA. QSL_RCVD_VIA had no way in at all outside the per-QSO editor — a field with a column, an import and an export, and nothing to fill it. Each direction now has its own route dropdown, sitting with the status and date it belongs to: how the card was sent, beside Sent; how it came back, beside Received. Both apply to the whole selection, which is the shape of the job — a stack confirmed in one go usually arrived the same way, a bureau delivery being exactly that, and doing it one QSO at a time in the editor was the only option before. Both store the ADIF enumeration (B/D/E), and neither touches QSL_VIA — that holds the manager's callsign and belongs to the QSO, not to a stack of cards being confirmed together. The two columns were already available in the paper list: it renders through RecentQSOsGrid, which gained them with the field itself.
This commit is contained in:
@@ -486,6 +486,7 @@ var defaultBands = []string{
|
||||
"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m",
|
||||
"12m", "10m", "6m", "2m", "70cm", "23cm",
|
||||
}
|
||||
|
||||
// defaultModes is what a fresh install starts with — the modes most operators
|
||||
// actually use, in the order they are offered. AM and DIGITALVOICE are NOT here
|
||||
// on purpose: they stay in the Available catalogue for whoever wants them, but
|
||||
@@ -6217,9 +6218,13 @@ type QSLBulkUpdate struct {
|
||||
RcvdStatus string `json:"rcvd_status"` // Y|N|R|I — "" = unchanged
|
||||
SentDate string `json:"sent_date"` // YYYYMMDD — "" = unchanged
|
||||
RcvdDate string `json:"rcvd_date"` // YYYYMMDD — "" = unchanged
|
||||
Via string `json:"via"` // QSL_VIA — "" = unchanged
|
||||
Notes string `json:"notes"` // NOTES — "" = unchanged
|
||||
Comment string `json:"comment"` // COMMENT — "" = unchanged
|
||||
// Via and RcvdVia are the ADIF QSL Via enumeration (B bureau / D direct /
|
||||
// E electronic), NOT the manager — that is QSL_VIA and belongs on the QSO,
|
||||
// not on a stack of cards being confirmed together. "" = unchanged.
|
||||
Via string `json:"via"` // → QSL_SENT_VIA
|
||||
RcvdVia string `json:"rcvd_via"` // → QSL_RCVD_VIA
|
||||
Notes string `json:"notes"` // NOTES — "" = unchanged
|
||||
Comment string `json:"comment"` // COMMENT — "" = unchanged
|
||||
}
|
||||
|
||||
// BulkUpdateQSL applies paper-QSL sent/received status, dates and via to the
|
||||
@@ -6256,6 +6261,12 @@ func (a *App) BulkUpdateQSL(ids []int64, u QSLBulkUpdate) (int, error) {
|
||||
if v := adif.NormaliseQSLVia(u.Via); v != "" {
|
||||
q.QSLSentVia, changed = v, true
|
||||
}
|
||||
// How the card CAME BACK. A stack confirmed together usually arrived the
|
||||
// same way — a bureau delivery is exactly that — so it belongs here
|
||||
// beside the received date rather than one QSO at a time in the editor.
|
||||
if v := adif.NormaliseQSLVia(u.RcvdVia); v != "" {
|
||||
q.QSLRcvdVia, changed = v, true
|
||||
}
|
||||
if v := strings.TrimSpace(u.Notes); v != "" {
|
||||
q.Notes, changed = v, true
|
||||
}
|
||||
@@ -17399,14 +17410,14 @@ type clusterStatusCache struct {
|
||||
// callCounties holds callsign → "STATE,County" for stations already logged
|
||||
// with a county, so a spot shows the county the entry panel showed rather
|
||||
// than the one derived from the licence ZIP. See qso.CallCounties.
|
||||
callCounties map[string]string
|
||||
workedPOTA map[string]struct{}
|
||||
workedPfx map[string]struct{}
|
||||
workedGrids map[string]struct{} // "GRID|MODE", mode normalised like the rest
|
||||
normMode func(string) string // nil unless digital-mode grouping is on
|
||||
groupDigital bool // settings the maps were built under —
|
||||
sameSlot bool // a change rebuilds the snapshot
|
||||
slotHighlight bool // (same: the slot index is built for either)
|
||||
callCounties map[string]string
|
||||
workedPOTA map[string]struct{}
|
||||
workedPfx map[string]struct{}
|
||||
workedGrids map[string]struct{} // "GRID|MODE", mode normalised like the rest
|
||||
normMode func(string) string // nil unless digital-mode grouping is on
|
||||
groupDigital bool // settings the maps were built under —
|
||||
sameSlot bool // a change rebuilds the snapshot
|
||||
slotHighlight bool // (same: the slot index is built for either)
|
||||
}
|
||||
|
||||
// clusterStatusMaps returns the cached worked-index snapshot, building it once
|
||||
|
||||
+4
-2
@@ -8,7 +8,8 @@
|
||||
"Chase new: a panel listing the stations PSK Reporter is hearing within about 300 km of you that are new against your log — on FT8, FT4, FT2, PSK31 and RTTY, and only on the bands you have selected. One badge per row, the most valuable first (entity, band, mode, slot, prefix, square), with a filter for each, a toolbar button to show it and a cross to close it. Click a row to put the callsign in the entry and tune the rig.",
|
||||
"Serial ports: a port claimed by two devices in the Windows port map was listed twice in every port dropdown, and showed as “COM3COM3”. Listed once now, and in natural order — COM4 between COM3 and COM8, not after COM9.",
|
||||
"Motorized antenna: an antenna that lost its serial port stopped answering with nothing at all in the log. Every reason it fails to start or reconnect is now written down, once.",
|
||||
"UDP: an ADIF listener sharing a port with a WSJT source logged a line per decode — hundreds of them. It now says what the traffic really is, once, and how to fix it."
|
||||
"UDP: an ADIF listener sharing a port with a WSJT source logged a line per decode — hundreds of them. It now says what the traffic really is, once, and how to fix it.",
|
||||
"QSL Manager, paper QSL: the route is set per direction — bureau, direct or electronic, beside the sent status and beside the received one — and applies to the whole selection at once."
|
||||
],
|
||||
"fr": [
|
||||
"Envoi de spot : le commentaire porte désormais les références de diplôme après le mode — celles que vous avez attribuées (POTA, SOTA, IOTA…), pas le DXCC, la zone et le préfixe que chacun déduit de l’indicatif. Un auto-spot porte VOS références d’activation.",
|
||||
@@ -16,7 +17,8 @@
|
||||
"Chasse au nouveau : un panneau listant les stations que PSK Reporter entend à moins de 300 km de chez vous et qui sont nouvelles par rapport à votre log — en FT8, FT4, FT2, PSK31 et RTTY, et seulement sur les bandes que vous avez sélectionnées. Une seule indication par ligne, la plus précieuse d’abord (entité, bande, mode, créneau, préfixe, carré), avec un filtre pour chacune, un bouton dans la barre pour l’afficher et une croix pour le fermer. Un clic met l’indicatif en saisie et accorde la radio.",
|
||||
"Ports série : un port revendiqué par deux périphériques dans la table Windows apparaissait en double dans toutes les listes, et s’affichait « COM3COM3 ». Une seule fois désormais, et dans l’ordre naturel — COM4 entre COM3 et COM8, pas après COM9.",
|
||||
"Antenne motorisée : une antenne ayant perdu son port série cessait de répondre sans rien laisser dans le journal. Chaque raison d’un démarrage ou d’une reconnexion manquée est désormais écrite, une fois.",
|
||||
"UDP : un écouteur ADIF partageant un port avec une source WSJT écrivait une ligne par décodage — des centaines. Il dit maintenant ce qu’est vraiment ce trafic, une fois, et comment le corriger."
|
||||
"UDP : un écouteur ADIF partageant un port avec une source WSJT écrivait une ligne par décodage — des centaines. Il dit maintenant ce qu’est vraiment ce trafic, une fois, et comment le corriger.",
|
||||
"Gestionnaire QSL, QSL papier : la voie se règle par sens — bureau, direct ou électronique, à côté du statut envoyé et à côté du statut reçu — et s’applique à toute la sélection d’un coup."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -54,7 +54,9 @@ const QSL_STATUSES = [
|
||||
{ v: 'I', label: 'qslm.ignore' },
|
||||
];
|
||||
|
||||
// QSL routing methods for the paper-QSL "Via" dropdown (was free text).
|
||||
// QSL routing methods for the paper-QSL route dropdowns, one per direction.
|
||||
// These are the ADIF QSL Via enumeration (B/D/E) and go to QSL_SENT_VIA and
|
||||
// QSL_RCVD_VIA — NOT to QSL_VIA, which holds the manager's callsign.
|
||||
const QSL_VIA_OPTIONS = [
|
||||
{ v: '_', label: 'qslm.leave' },
|
||||
{ v: 'B', label: 'qslm.viaBureau' },
|
||||
@@ -149,7 +151,8 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
||||
const [qslSent, setQslSent] = useState('_');
|
||||
const [qslRcvdDate, setQslRcvdDate] = useState('');
|
||||
const [qslSentDate, setQslSentDate] = useState('');
|
||||
const [qslVia, setQslVia] = useState('_');
|
||||
const [qslVia, setQslVia] = useState('_'); // how the card was SENT
|
||||
const [qslRcvdVia, setQslRcvdVia] = useState('_'); // how it CAME BACK
|
||||
const [qslNotes, setQslNotes] = useState('');
|
||||
const [qslComment, setQslComment] = useState('');
|
||||
|
||||
@@ -181,6 +184,7 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
||||
sent_date: ymd(qslSentDate),
|
||||
rcvd_date: ymd(qslRcvdDate),
|
||||
via: qslVia === '_' ? '' : qslVia,
|
||||
rcvd_via: qslRcvdVia === '_' ? '' : qslRcvdVia,
|
||||
notes: qslNotes,
|
||||
comment: qslComment,
|
||||
} as any);
|
||||
@@ -553,6 +557,10 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
||||
{/* Paper-QSL apply form */}
|
||||
{service === 'paper' && (
|
||||
<div className="flex items-end flex-wrap gap-3 px-3 py-2 border-t border-border bg-muted/20 shrink-0">
|
||||
{/* Each direction carries its own route, and each sits with the status
|
||||
and date it belongs to. A stack confirmed in one go usually arrived
|
||||
the same way — a bureau delivery is exactly that — which is why the
|
||||
route belongs on this form and not one QSO at a time in the editor. */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] uppercase tracking-wider text-muted-foreground">{t('qslm.qslReceived')}</label>
|
||||
<div className="flex gap-1.5">
|
||||
@@ -561,6 +569,10 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
||||
<SelectContent>{QSL_STATUSES.map((s) => <SelectItem key={s.v} value={s.v}>{t(s.label)}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
<Input type="date" className="h-8 w-36" value={qslRcvdDate} onChange={(e) => setQslRcvdDate(e.target.value)} title={t('qslm.qslRcvdDateTitle')} />
|
||||
<Select value={qslRcvdVia} onValueChange={setQslRcvdVia}>
|
||||
<SelectTrigger className="h-8 w-32" title={t('qslm.rcvdViaTitle')}><SelectValue /></SelectTrigger>
|
||||
<SelectContent>{QSL_VIA_OPTIONS.map((o) => <SelectItem key={o.v} value={o.v}>{t(o.label)}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -571,15 +583,12 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
||||
<SelectContent>{QSL_STATUSES.map((s) => <SelectItem key={s.v} value={s.v}>{t(s.label)}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
<Input type="date" className="h-8 w-36" value={qslSentDate} onChange={(e) => setQslSentDate(e.target.value)} title={t('qslm.qslSentDateTitle')} />
|
||||
<Select value={qslVia} onValueChange={setQslVia}>
|
||||
<SelectTrigger className="h-8 w-32" title={t('qslm.sentViaTitle')}><SelectValue /></SelectTrigger>
|
||||
<SelectContent>{QSL_VIA_OPTIONS.map((o) => <SelectItem key={o.v} value={o.v}>{t(o.label)}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] uppercase tracking-wider text-muted-foreground">{t('qslm.via')}</label>
|
||||
<Select value={qslVia} onValueChange={setQslVia}>
|
||||
<SelectTrigger className="h-8 w-32"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>{QSL_VIA_OPTIONS.map((o) => <SelectItem key={o.v} value={o.v}>{t(o.label)}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] uppercase tracking-wider text-muted-foreground">{t('qslm.notes')}</label>
|
||||
<Input className="h-8 w-40" value={qslNotes} onChange={(e) => setQslNotes(e.target.value)} placeholder={t('qslm.notesPlaceholder')} />
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2739,6 +2739,7 @@ export namespace main {
|
||||
sent_date: string;
|
||||
rcvd_date: string;
|
||||
via: string;
|
||||
rcvd_via: string;
|
||||
notes: string;
|
||||
comment: string;
|
||||
|
||||
@@ -2753,6 +2754,7 @@ export namespace main {
|
||||
this.sent_date = source["sent_date"];
|
||||
this.rcvd_date = source["rcvd_date"];
|
||||
this.via = source["via"];
|
||||
this.rcvd_via = source["rcvd_via"];
|
||||
this.notes = source["notes"];
|
||||
this.comment = source["comment"];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user