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', 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. // Colour-coded status cell for the confirmation grid.
function StatusCell({ value }: { value?: string }) { function StatusCell({ value }: { value?: string }) {
const { t } = useI18n(); const { t } = useI18n();
@@ -675,43 +680,69 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], b
<Label>{t('qedit.manageConf')}</Label> <Label>{t('qedit.manageConf')}</Label>
<Select value={confSel} onValueChange={setConfSel}> <Select value={confSel} onValueChange={setConfSel}>
<SelectTrigger><SelectValue /></SelectTrigger> <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> </Select>
</div> </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 {confSel === OPSLOG_CONF ? (
is backed by ADIF extras, not by QSO columns, and its /* OpsLog's own card. "Sent" is stamped when the card
"received" flag writes immediately rather than on Save actually goes out, so it is shown, not offered: ticking
(a removed extras key would not survive the merge). The it by hand would record something that never happened.
PSE/TNX stamp printed on the card is derived from it, so "Received" drives the PSE/TNX stamp printed on the card,
it is shown right here — that stamp is the whole reason which is the whole reason the flag exists — hence the
the flag exists. */} live indicator next to it. It writes immediately rather
<div className="border-t border-border/60 pt-3 space-y-1.5"> than on Save, because clearing an extras key would not
<label className="flex items-center gap-2 text-sm cursor-pointer"> survive the merge Save does. */
<Checkbox checked={qslReceived} onCheckedChange={(c) => toggleQslReceived(!!c)} /> <div className="space-y-3">
{t('qedit.qslReceived')} <div className="grid grid-cols-2 gap-3">
<span className="text-[11px] font-mono px-1.5 py-0.5 rounded bg-muted text-muted-foreground" title={t('qedit.pseTnxHint')}> <div>
{qslReceived ? 'TNX' : 'PSE QSL'} <Label>{t('qedit.sent')}</Label>
</span> <Input disabled value={opslogQslSent ? t('qedit.qslYes') : t('qedit.qslNo')} />
</label> </div>
<p className="text-[11px] text-muted-foreground">{t('qedit.pseTnxHint')}</p> <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> </div>
{/* Right: live status grid for every channel. {/* Right: live status grid for every channel.
File diff suppressed because one or more lines are too long