fix(qsl): put the OpsLog card in the Manage Confirmation picker too

The previous commit added its row to the status table but left the picker
listing only the column-backed channels, so the one channel you could actually
tick was the one you could not select.

Selecting it swaps the left panel for an OpsLog editor instead of the generic
sent/received/date grid, which has no fields to bind to here: Sent is shown
read-only (OpsLog stamps it when the card goes out), Received is the tick, and
the PSE QSL / TNX indicator sits next to them since that stamp is what the flag
is for.
This commit is contained in:
2026-08-09 01:23:52 +02:00
parent 19ae00124f
commit f1b7a7e477
2 changed files with 67 additions and 36 deletions
+65 -34
View File
@@ -73,6 +73,11 @@ const CONF_LABEL_KEYS: Record<string, string> = {
QSL: 'qedit.confQslPaper',
};
// 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.
const OPSLOG_CONF = 'OPSLOG';
// Colour-coded status cell for the confirmation grid.
function StatusCell({ value }: { value?: string }) {
const { t } = useI18n();
@@ -675,43 +680,69 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], b
<Label>{t('qedit.manageConf')}</Label>
<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>)}</SelectContent>
<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
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>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-3">
<div><Label>{t('qedit.sent')}</Label><QslSelect value={val(def.sent)} onChange={(v) => put(def.sent, v)} /></div>
<div><Label>{t('qedit.received')}</Label>
{def.rcvd
? <QslSelect value={val(def.rcvd)} onChange={(v) => put(def.rcvd, v)} />
: <Input disabled value="—" />}
</div>
<div><Label>{t('qedit.dateSent')}</Label><AdifDateInput value={val(def.sentDate)} onChange={(v) => put(def.sentDate, v)} /></div>
<div><Label>{t('qedit.dateReceived')}</Label><AdifDateInput value={val(def.rcvdDate)} onChange={(v) => put(def.rcvdDate, v)} disabled={!def.rcvdDate} /></div>
{def.via && (
<div className="col-span-2"><Label>{t('qedit.via')}</Label><Input value={val(def.via)} onChange={(e) => put(def.via, e.target.value)} placeholder={t('qedit.viaPlaceholder')} /></div>
)}
</div>
<p className="text-[11px] text-muted-foreground">
{t('qedit.qslPanelHint')} <strong>{t('qedit.saveChanges')}</strong>.
</p>
{/* OpsLog's own card channel. Not a CONFIRMATIONS entry: it
is backed by ADIF extras, not by QSO columns, and its
"received" flag writes immediately rather than on Save
(a removed extras key would not survive the merge). The
PSE/TNX stamp printed on the card is derived from it, so
it is shown right here — that stamp is the whole reason
the flag exists. */}
<div className="border-t border-border/60 pt-3 space-y-1.5">
<label className="flex items-center gap-2 text-sm cursor-pointer">
<Checkbox checked={qslReceived} onCheckedChange={(c) => toggleQslReceived(!!c)} />
{t('qedit.qslReceived')}
<span className="text-[11px] font-mono px-1.5 py-0.5 rounded bg-muted text-muted-foreground" title={t('qedit.pseTnxHint')}>
{qslReceived ? 'TNX' : 'PSE QSL'}
</span>
</label>
<p className="text-[11px] text-muted-foreground">{t('qedit.pseTnxHint')}</p>
</div>
{confSel === OPSLOG_CONF ? (
/* OpsLog's own card. "Sent" is stamped when the card
actually goes out, so it is shown, not offered: ticking
it by hand would record something that never happened.
"Received" drives the PSE/TNX stamp printed on the card,
which is the whole reason the flag exists — hence the
live indicator next to it. It writes immediately rather
than on Save, because clearing an extras key would not
survive the merge Save does. */
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div>
<Label>{t('qedit.sent')}</Label>
<Input disabled value={opslogQslSent ? t('qedit.qslYes') : t('qedit.qslNo')} />
</div>
<div>
<Label>{t('qedit.received')}</Label>
<label className="flex h-9 items-center gap-2 text-sm cursor-pointer">
<Checkbox checked={qslReceived} onCheckedChange={(c) => toggleQslReceived(!!c)} />
{t('qedit.qslReceived')}
</label>
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-[11px] font-mono px-1.5 py-0.5 rounded bg-muted text-muted-foreground">
{qslReceived ? 'TNX' : 'PSE QSL'}
</span>
<span className="text-[11px] text-muted-foreground">{t('qedit.pseTnxHint')}</span>
</div>
<p className="text-[11px] text-muted-foreground">{t('qedit.opslogSentHint')}</p>
</div>
) : (
<>
<div className="grid grid-cols-2 gap-3">
<div><Label>{t('qedit.sent')}</Label><QslSelect value={val(def.sent)} onChange={(v) => put(def.sent, v)} /></div>
<div><Label>{t('qedit.received')}</Label>
{def.rcvd
? <QslSelect value={val(def.rcvd)} onChange={(v) => put(def.rcvd, v)} />
: <Input disabled value="—" />}
</div>
<div><Label>{t('qedit.dateSent')}</Label><AdifDateInput value={val(def.sentDate)} onChange={(v) => put(def.sentDate, v)} /></div>
<div><Label>{t('qedit.dateReceived')}</Label><AdifDateInput value={val(def.rcvdDate)} onChange={(v) => put(def.rcvdDate, v)} disabled={!def.rcvdDate} /></div>
{def.via && (
<div className="col-span-2"><Label>{t('qedit.via')}</Label><Input value={val(def.via)} onChange={(e) => put(def.via, e.target.value)} placeholder={t('qedit.viaPlaceholder')} /></div>
)}
</div>
<p className="text-[11px] text-muted-foreground">
{t('qedit.qslPanelHint')} <strong>{t('qedit.saveChanges')}</strong>.
</p>
</>
)}
</div>
{/* Right: live status grid for every channel.
File diff suppressed because one or more lines are too long