fix(cluster): NEW CALL is not NEW SLOT
The slot-highlight option reused the new-slot status, which already meant something else and something narrower: the ENTITY was worked on this band and on this mode, but never on the two together. What the option surfaces is a fact about the CALLSIGN - never worked on this band and mode - and the entity may be long since confirmed there. Overloading it cost exactly what it always costs. Forty consecutive rows read NEW SLOT, the genuine new-slot rows drowned in them, and the Status column stopped carrying information at all. new-call is its own status now, with its own badge, its own tooltip spelling out that it is the callsign and not the entity, and its own filter chip. It keeps the caution colour for the moment: it sits at the same "worth a look, not a new one" level as new-slot, and the colour rework will separate them. The status filter had to move onto the displayed status too. It read the raw backend entry, where new-call by construction never appears, so the chip would have selected nothing at all.
This commit is contained in:
+11
-2
@@ -89,6 +89,7 @@ import { ExportFieldsDialog } from '@/components/ExportFieldsDialog';
|
||||
import { ShutdownProgress } from '@/components/ShutdownProgress';
|
||||
import { ClusterGrid } from '@/components/ClusterGrid';
|
||||
import { cleanSpotter, inferSpotMode, spotModeCategory, spotStatusKey } from '@/lib/spot';
|
||||
import { applySpotDisplay, readSpotDisplayOptions } from '@/lib/spotDisplay';
|
||||
import { WorkedBeforeGrid } from '@/components/WorkedBeforeGrid';
|
||||
import { NetControlPanel } from '@/components/NetControlPanel';
|
||||
import { ContestPanel, CONTEST_DEFAULT, type ContestSession } from '@/components/ContestPanel';
|
||||
@@ -1484,7 +1485,9 @@ export default function App() {
|
||||
// county or a new park is not a DXCC state, it is another dimension of the
|
||||
// same spot — which is why the grid shows them as separate badges. Filtering
|
||||
// is an OR across all of them, as it already was for a worked callsign.
|
||||
type SpotFilterKey = SpotStatusKey | 'new-pota' | 'new-county' | 'new-pfx';
|
||||
// 'new-call' is a DISPLAY status, produced by the slot-highlight option rather
|
||||
// than by the backend, so it is named here and not in SpotStatusKey.
|
||||
type SpotFilterKey = SpotStatusKey | 'new-pota' | 'new-county' | 'new-pfx' | 'new-call';
|
||||
const [clusterStatusFilter, setClusterStatusFilter] = useState<Set<SpotFilterKey>>(() => lsSet<SpotFilterKey>('opslog.clusterStatusFilter'));
|
||||
// Mode filter chips. Empty set = show every mode. Categories map the
|
||||
// inferred per-spot mode onto SSB (phone) / CW / DATA (digital).
|
||||
@@ -4681,7 +4684,10 @@ export default function App() {
|
||||
}
|
||||
if (clusterStatusFilter.size > 0) {
|
||||
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
||||
const e = spotStatus[k];
|
||||
// Filter on what is DISPLAYED, not on the raw backend status: NEW CALL is
|
||||
// produced by the slot-highlight option, so a filter reading the raw
|
||||
// entry would offer a chip that never matches a single row.
|
||||
const e = applySpotDisplay(spotStatus[k], readSpotDisplayOptions());
|
||||
const st = (e?.status || '') as SpotStatusKey;
|
||||
// WORKED means "I've worked THIS callsign" — the blue WKD-CALL flag —
|
||||
// NOT the entity status 'worked' (entity/band/mode already worked, which
|
||||
@@ -4810,6 +4816,9 @@ export default function App() {
|
||||
{ k: 'new-band' as SpotFilterKey, label: 'NEW BAND', cls: 'bg-warning-muted text-warning-muted-foreground border-warning-border' },
|
||||
{ k: 'new-mode' as SpotFilterKey, label: 'NEW MODE', cls: 'bg-caution-muted text-caution-muted-foreground border-caution-border' },
|
||||
{ k: 'new-slot' as SpotFilterKey, label: 'NEW SLOT', cls: 'bg-caution-muted text-caution-muted-foreground border-caution-border' },
|
||||
// NEW CALL is about the CALLSIGN, not the entity: never worked on this
|
||||
// band and mode. Only appears when the slot-highlight option is on.
|
||||
{ k: 'new-call' as SpotFilterKey, label: 'NEW CALL', cls: 'bg-caution-muted text-caution-muted-foreground border-caution-border' },
|
||||
// Same colours as the badges in the grid — a filter that does not
|
||||
// look like what it selects has to be learned twice.
|
||||
{ k: 'new-pota' as SpotFilterKey, label: 'NEW POTA', cls: 'bg-success-muted text-success-muted-foreground border-success-border' },
|
||||
|
||||
@@ -158,6 +158,7 @@ function statusLabel(s: string, t: (k: string) => string, muted = false): string
|
||||
case 'new-band': return t('bmp.statusNewBand');
|
||||
case 'new-mode': return t('bmp.statusNewMode');
|
||||
case 'new-slot': return t('bmp.statusNewSlot');
|
||||
case 'new-call': return t('bmp.statusNewCall');
|
||||
case 'worked': return t('bmp.statusWorked');
|
||||
// An empty status means the entity could not be resolved — EXCEPT when the
|
||||
// "no colour on worked stations" option emptied it on purpose. Saying
|
||||
@@ -201,6 +202,7 @@ function statusStyle(s: string, muted = false): { pill: string; bar: string; lin
|
||||
line: 'stroke-warning',
|
||||
dot: 'fill-warning',
|
||||
};
|
||||
case 'new-call':
|
||||
case 'new-slot': return {
|
||||
pill: 'bg-caution-muted text-caution-muted-foreground border-caution-border hover:bg-caution-muted',
|
||||
bar: 'bg-caution',
|
||||
@@ -320,6 +322,7 @@ export function BandMap({ band, spots, spotStatus: spotStatusRaw, currentFreqHz,
|
||||
case 'new': return 0;
|
||||
case 'new-band': return 1;
|
||||
case 'new-slot': return 2;
|
||||
case 'new-call': return 2;
|
||||
case 'worked': return 4;
|
||||
default: return 3;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ function statusColor(s: SpotStatusEntry | undefined): string | null {
|
||||
// (still loading, or entity unknown) are NOT dimmed — that would flicker.
|
||||
function isDull(s: SpotStatusEntry | undefined): boolean {
|
||||
if (!s || !s.status) return false;
|
||||
if (s.status === 'new' || s.status === 'new-band' || s.status === 'new-mode' || s.status === 'new-slot') return false;
|
||||
if (s.status === 'new' || s.status === 'new-band' || s.status === 'new-mode' || s.status === 'new-slot' || s.status === 'new-call') return false;
|
||||
return !(s.worked_call || s.new_pota || s.new_county || s.new_pfx);
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ const makeColCatalog = (t: TFn): ColEntry[] => [
|
||||
else if (s?.status === 'new-band') parts.push(t('clg2.newBand'));
|
||||
else if (s?.status === 'new-mode') parts.push(t('clg2.newMode'));
|
||||
else if (s?.status === 'new-slot') parts.push(t('clg2.newSlot'));
|
||||
else if (s?.status === 'new-call') parts.push(t('clg2.newCall'));
|
||||
else if (s?.worked_call) parts.push(t('clg2.wkdCall'));
|
||||
if (s?.new_county) parts.push(t('clg2.newCounty'));
|
||||
if (s?.new_pota) parts.push(t('clg2.newPota'));
|
||||
@@ -227,6 +228,7 @@ const makeColCatalog = (t: TFn): ColEntry[] => [
|
||||
: s?.status === 'new-band' ? t('clg2.newBand')
|
||||
: s?.status === 'new-mode' ? t('clg2.newMode')
|
||||
: s?.status === 'new-slot' ? t('clg2.newSlot')
|
||||
: s?.status === 'new-call' ? t('clg2.newCall')
|
||||
: t('clg2.wkdCall');
|
||||
parts.push({ text: label, color: main });
|
||||
}
|
||||
@@ -251,6 +253,7 @@ const makeColCatalog = (t: TFn): ColEntry[] => [
|
||||
if (s?.status === 'new') return t('clg2.tipNewDxcc', { country: s?.country ?? '' });
|
||||
if (s?.status === 'new-band') return t('clg2.tipNewBand');
|
||||
if (s?.status === 'new-slot') return t('clg2.tipNewSlotBand');
|
||||
if (s?.status === 'new-call') return t('clg2.tipNewCall');
|
||||
if (s?.worked_call) return t('clg2.tipWorkedCall');
|
||||
return undefined;
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -48,7 +48,7 @@ type Entry = {
|
||||
// in front of you.
|
||||
function bringsNothingNew(s: Entry): boolean {
|
||||
if (!s || !s.status) return false; // unresolved — never hide, it would flicker
|
||||
if (s.status === 'new' || s.status === 'new-band' || s.status === 'new-mode' || s.status === 'new-slot') return false;
|
||||
if (s.status === 'new' || s.status === 'new-band' || s.status === 'new-mode' || s.status === 'new-slot' || s.status === 'new-call') return false;
|
||||
return !(s.new_pota || s.new_county || s.new_pfx);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export function applySpotDisplay<T extends Entry>(s: T, o: SpotDisplayOptions):
|
||||
// entity is precisely the spot the second option exists to surface. Promoting
|
||||
// first protects it for free: bringsNothingNew() returns false on new-slot.
|
||||
if (o.slotHighlight && e.worked_slot === false && (!e.status || e.status === 'worked')) {
|
||||
e = { ...e, status: 'new-slot' } as NonNullable<T>;
|
||||
e = { ...e, status: 'new-call' } as NonNullable<T>;
|
||||
}
|
||||
|
||||
if (o.muteWorked && bringsNothingNew(e)) {
|
||||
|
||||
Reference in New Issue
Block a user