feat(chase): the global hunt — new, or new plus unconfirmed, every category
The gap between worked and confirmed becomes visible everywhere. A Chase setting on the DX Cluster page picks the mode; in new-plus-unconfirmed the verdicts for DXCC/band/mode/slot, prefix, county and state are judged against CONFIRMED-only ledgers built from the operator's chosen sources (LoTW, card, eQSL, QRZ.com — HRDLog has no confirmation field in ADIF to offer), and a need that exists only because a QSL never arrived is flagged Unconf: the cluster and decode badges render it dimmed, the convention the grid hunt introduced. The grid's own Chase selector folds into the global one; its square-matching scope stays where it was, being grid-specific. One snapshot key change rebuilds the cache when any of it flips.
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
GetLookupSettings, SaveLookupSettings, ClearLookupCache, TestLookupProvider,
|
||||
GetListsSettings, SaveListsSettings,
|
||||
GetCATSettings, SaveCATSettings, GetRadios, SaveRadios, SetActiveRadio, ActiveRadioID, DiscoverFlexRadios, DVKDelete,
|
||||
GetChaseSettings, SaveChaseSettings,
|
||||
GetAudioMonitorPref,
|
||||
ListProfiles, GetActiveProfile, SaveProfile, DeleteProfile, ActivateProfile, DuplicateProfile,
|
||||
GetRotators, SaveRotators, TestRotatorDevice, RotatorPark, RotatorStop,
|
||||
@@ -1718,6 +1719,8 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
// DVK voice-keyer messages (F1–F6).
|
||||
type DVKMsg = { slot: number; label: string; has_audio: boolean; duration_sec: number };
|
||||
type DVKStat = { recording: boolean; playing: boolean; rec_slot: number };
|
||||
const [chaseCfg, setChaseCfg] = useState<{ mode: string; sources: string[] }>({ mode: 'new', sources: ['lotw', 'card', 'eqsl'] });
|
||||
useEffect(() => { GetChaseSettings().then((c: any) => setChaseCfg({ mode: c?.mode ?? 'new', sources: c?.sources ?? ['lotw', 'card', 'eqsl'] })).catch(() => {}); }, []);
|
||||
const [dvkMsgs, setDvkMsgs] = useState<DVKMsg[]>([]);
|
||||
const [dvkStat, setDvkStat] = useState<DVKStat>({ recording: false, playing: false, rec_slot: 0 });
|
||||
const [dvkErr, setDvkErr] = useState('');
|
||||
@@ -5459,19 +5462,42 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-xs text-muted-foreground w-28 shrink-0">{t('gsc.hunt')}</span>
|
||||
<Select value={gridScope.hunt} onValueChange={(v) => saveGridScope({ ...gridScope, hunt: v })}>
|
||||
<SelectTrigger className="h-7 w-64 text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="new">{t('gsc.huntNew')}</SelectItem>
|
||||
<SelectItem value="new_unconfirmed">{t('gsc.huntUnconf')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* The GLOBAL hunt: every category (DXCC, band, mode, slot, prefix,
|
||||
county, state, grid) judged as new-only or new-plus-unconfirmed,
|
||||
against the confirmation sources the operator trusts. */}
|
||||
<div className="rounded-lg border border-border p-3 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground w-28 shrink-0">{t('chg.mode')}</span>
|
||||
<Select value={chaseCfg.mode} onValueChange={(v) => { const next = { ...chaseCfg, mode: v }; setChaseCfg(next); void SaveChaseSettings(next as any); }}>
|
||||
<SelectTrigger className="h-7 w-64 text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="new">{t('gsc.huntNew')}</SelectItem>
|
||||
<SelectItem value="new_unconfirmed">{t('gsc.huntUnconf')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{chaseCfg.mode === 'new_unconfirmed' && (
|
||||
<div className="flex items-center gap-4 flex-wrap pl-2">
|
||||
<span className="text-xs text-muted-foreground">{t('chg.sources')}</span>
|
||||
{([['lotw', 'LoTW'], ['card', t('chg.card')], ['eqsl', 'eQSL'], ['qrz', 'QRZ.com']] as const).map(([k, label]) => (
|
||||
<label key={k} className="flex items-center gap-1.5 text-xs cursor-pointer">
|
||||
<Checkbox checked={chaseCfg.sources.includes(k)}
|
||||
onCheckedChange={(c) => {
|
||||
const sources = c ? [...chaseCfg.sources, k] : chaseCfg.sources.filter((x) => x !== k);
|
||||
const next = { ...chaseCfg, sources };
|
||||
setChaseCfg(next); void SaveChaseSettings(next as any);
|
||||
}} />
|
||||
{label}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Chase new — its own option, NOT nested under grid chasing. Chasing
|
||||
squares and chasing entities are different wants; they only share
|
||||
the PSK Reporter feed, which either one brings up. */}
|
||||
|
||||
Reference in New Issue
Block a user