style(qsl): paper and LoTW first, then alphabetical

The channel list had grown in the order channels were added, so finding
one meant remembering when it arrived. Paper QSL and LoTW lead — they
are the two that carry an ARRL award — and the rest sort by name. One
ordering function feeds both the picker and the status table, so the two
cannot drift apart; the three extras-backed channels (OpsLog card,
HAMLOG.online, HamQTH) now sit in that same list instead of being
appended by hand after it.
This commit is contained in:
2026-08-31 18:48:40 +02:00
parent 77b95289e7
commit 7bab30aa71
2 changed files with 52 additions and 38 deletions
+48 -36
View File
@@ -80,6 +80,19 @@ const CONF_LABEL_KEYS: Record<string, string> = {
QSL: 'qedit.confQslPaper',
};
// Reading order for the channel list: the two that carry an ARRL award first —
// paper QSL and LoTW — then everything else alphabetically, so a channel is
// found by its name rather than by remembering the order it was added in.
const CONF_FIRST: Record<string, number> = { QSL: 0, LOTW: 1 };
function confOrder<T extends { key: string; label: string }>(rows: T[]): T[] {
return rows.slice().sort((a, b) => {
const ra = CONF_FIRST[a.key] ?? 2;
const rb = CONF_FIRST[b.key] ?? 2;
if (ra !== rb) return ra - rb;
return a.label.localeCompare(b.label);
});
}
// OpsLog's own card. Kept out of CONFIRMATIONS on purpose — that list maps QSO
// columns and this channel is backed by ADIF extras — but it still belongs in
// the channel picker and the status table alongside the rest.
@@ -756,15 +769,19 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], b
<Select value={confSel} onValueChange={setConfSel}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{CONFIRMATIONS.map((c) => <SelectItem key={c.key} value={c.key}>{CONF_LABEL_KEYS[c.key] ? t(CONF_LABEL_KEYS[c.key]) : c.label}</SelectItem>)}
{/* Listed here but NOT in CONFIRMATIONS: that table maps
{confOrder([
...CONFIRMATIONS.map((c) => ({ key: c.key, label: CONF_LABEL_KEYS[c.key] ? t(CONF_LABEL_KEYS[c.key]) : c.label })),
{ key: OPSLOG_CONF, label: t('qedit.confOpsLog') },
{ key: HAMLOG_CONF, label: 'HAMLOG.online' },
{ key: HAMQTH_CONF, label: 'HamQTH' },
]).map((c) => <SelectItem key={c.key} value={c.key}>{c.label}</SelectItem>)}
{/* The three above that are NOT in CONFIRMATIONS — the
QSO columns, and this channel lives in the ADIF
extras. It gets its own editor below rather than the
generic sent/received/date grid, which has no field
to bind to. */}
<SelectItem value={OPSLOG_CONF}>{t('qedit.confOpsLog')}</SelectItem>
<SelectItem value={HAMLOG_CONF}>HAMLOG.online</SelectItem>
<SelectItem value={HAMQTH_CONF}>HamQTH</SelectItem>
OpsLog card, HAMLOG.online and HamQTH — are backed by
ADIF extras rather than QSO columns, and each gets its
own editor below instead of the generic
sent/received/date grid, which has no field to bind
to. */}
</SelectContent>
</Select>
</div>
@@ -865,37 +882,32 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], b
</tr>
</thead>
<tbody>
{CONFIRMATIONS.map((c) => (
{/* The extras-backed channels (OpsLog card,
HAMLOG.online, HamQTH) sit in the same ordered list
as the column-backed ones: the reader is looking for
a name, not for a storage detail. A dash in RECEIVED
means the channel has nothing to receive — Club Log
and HamQTH publish no confirmations — which is not
the same statement as "N". */}
{confOrder([
...CONFIRMATIONS.map((c) => ({
key: c.key,
label: CONF_LABEL_KEYS[c.key] ? t(CONF_LABEL_KEYS[c.key]) : c.label,
sent: val(c.sent),
rcvd: c.rcvd ? val(c.rcvd) : null,
})),
{ key: OPSLOG_CONF, label: t('qedit.confOpsLog'), sent: opslogQslSent ? 'Y' : 'N', rcvd: qslReceived ? 'Y' : 'N' },
{ key: HAMLOG_CONF, label: 'HAMLOG.online', sent: exVal(HAMLOG_KEYS.sent), rcvd: exVal(HAMLOG_KEYS.rcvd) },
{ key: HAMQTH_CONF, label: 'HamQTH', sent: exVal(HAMQTH_KEYS.sent), rcvd: null },
]).map((c) => (
<tr key={c.key} className="text-xs">
<td className="font-medium pr-3 py-0.5 whitespace-nowrap">{CONF_LABEL_KEYS[c.key] ? t(CONF_LABEL_KEYS[c.key]) : c.label}</td>
<td className="w-24"><StatusCell value={val(c.sent)} /></td>
<td className="w-24">{c.rcvd ? <StatusCell value={val(c.rcvd)} /> : <span className="block text-center text-[11px] text-muted-foreground"></span>}</td>
<td className="font-medium pr-3 py-0.5 whitespace-nowrap">{c.label}</td>
<td className="w-24"><StatusCell value={c.sent} /></td>
<td className="w-24">{c.rcvd === null
? <span className="block text-center text-[11px] text-muted-foreground"></span>
: <StatusCell value={c.rcvd} />}</td>
</tr>
))}
{/* OpsLog's own card, read from the ADIF extras rather
than a QSO column — hence a hand-written row instead
of a CONFIRMATIONS entry. "Sent" is stamped by OpsLog
when the card actually goes out, so it stays
read-only here: an operator ticking it by hand would
be recording something that never happened. */}
<tr className="text-xs">
<td className="font-medium pr-3 py-0.5 whitespace-nowrap">{t('qedit.confOpsLog')}</td>
<td className="w-24"><StatusCell value={opslogQslSent ? 'Y' : 'N'} /></td>
<td className="w-24"><StatusCell value={qslReceived ? 'Y' : 'N'} /></td>
</tr>
{/* HAMLOG.online — extras again, same hand-written row. */}
<tr className="text-xs">
<td className="font-medium pr-3 py-0.5 whitespace-nowrap">HAMLOG.online</td>
<td className="w-24"><StatusCell value={exVal(HAMLOG_KEYS.sent)} /></td>
<td className="w-24"><StatusCell value={exVal(HAMLOG_KEYS.rcvd)} /></td>
</tr>
{/* HamQTH — sent only; the dash says there is nothing
to receive, not that nothing was received. */}
<tr className="text-xs">
<td className="font-medium pr-3 py-0.5 whitespace-nowrap">HamQTH</td>
<td className="w-24"><StatusCell value={exVal(HAMQTH_KEYS.sent)} /></td>
<td className="w-24"><span className="block text-center text-[11px] text-muted-foreground"></span></td>
</tr>
</tbody>
</table>
</div>