import { useMemo } from 'react'; import { Star, Radio } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { cn } from '@/lib/utils'; import type { WorkedBeforeView } from '@/types'; type WorkedBefore = WorkedBeforeView; interface Props { wb: WorkedBefore | null; busy: boolean; currentBand: string; currentMode: string; bands?: string[]; // operator's configured bands; falls back to DEFAULT_BANDS } // Compact column label for a band tag: keep the classic V/U for 2m/70cm, // strip the trailing "m" for meter bands (160m→160), and shorten cm bands // (13cm→13c) so the column stays narrow. function bandColLabel(tag: string): string { if (tag === '2m') return 'V'; if (tag === '70cm') return 'U'; if (tag.endsWith('cm')) return tag.replace('cm', 'c'); return tag.replace(/m$/, ''); } // Default 13-column band layout, used when the operator hasn't configured bands. const DEFAULT_BANDS: { tag: string; label: string }[] = [ { tag: '160m', label: '160' }, { tag: '80m', label: '80' }, { tag: '60m', label: '60' }, { tag: '40m', label: '40' }, { tag: '30m', label: '30' }, { tag: '20m', label: '20' }, { tag: '17m', label: '17' }, { tag: '15m', label: '15' }, { tag: '12m', label: '12' }, { tag: '10m', label: '10' }, { tag: '6m', label: '6' }, { tag: '2m', label: 'V' }, { tag: '70cm', label: 'U' }, ]; const CLASSES = ['PH', 'CW', 'DIG'] as const; const PHONE_MODES = new Set(['SSB','USB','LSB','AM','FM','DIGITALVOICE','PHONE']); function classMatchesMode(cls: string, mode: string): boolean { const u = (mode || '').toUpperCase(); if (cls === 'PH') return PHONE_MODES.has(u); if (cls === 'CW') return u === 'CW'; return u !== '' && u !== 'CW' && !PHONE_MODES.has(u); } const STATUS_CLASSES: Record = { call_c: 'bg-emerald-700 hover:bg-emerald-600', call_w: 'bg-emerald-300 hover:bg-emerald-200', dxcc_c: 'bg-indigo-800 hover:bg-indigo-700', dxcc_w: 'bg-indigo-300 hover:bg-indigo-200', }; // Legend entries, in the same colour order as the cells. swatch = the // background class (or a special ring marker for the current-entry cell). const LEGEND: { swatch: string; ring?: boolean; label: string }[] = [ { swatch: 'bg-emerald-700', label: 'Call confirmed' }, { swatch: 'bg-emerald-300', label: 'Call worked' }, { swatch: 'bg-indigo-800', label: 'Entity confirmed' }, { swatch: 'bg-indigo-300', label: 'Entity worked' }, { swatch: 'bg-stone-200', label: 'Not worked' }, { swatch: 'bg-stone-200', ring: true, label: 'Current entry' }, ]; function cellTitle(band: string, cls: string, status: string, current: boolean): string { const desc = status === 'call_c' ? 'This callsign confirmed' : status === 'call_w' ? 'This callsign worked (not confirmed)' : status === 'dxcc_c' ? 'Entity confirmed (other callsign)' : status === 'dxcc_w' ? 'Entity worked (other callsign)' : 'Never worked'; return `${band} ${cls}: ${desc}${current ? ' — current entry' : ''}`; } export function BandSlotGrid({ wb, busy, currentBand, currentMode, bands }: Props) { // Columns from the operator's configured bands (so the matrix shows only the // bands they actually use), falling back to the built-in default set. const cols = useMemo( () => (bands && bands.length ? bands.map((tag) => ({ tag, label: bandColLabel(tag) })) : DEFAULT_BANDS), [bands], ); const dxcc = wb?.dxcc ?? 0; const dxccName = wb?.dxcc_name ?? ''; const dxccCount = wb?.dxcc_count ?? 0; const callCount = wb?.count ?? 0; // QSOs with this exact callsign const hasDxcc = dxcc > 0; const newOne = hasDxcc && dxccCount === 0; const statusMap = useMemo(() => { const m = new Map(); for (const s of wb?.band_status ?? []) { m.set(`${s.band}|${s.class}`, s.status); } return m; }, [wb]); return (
{newOne ? ( <> NEW ONE {dxccName || `DXCC #${dxcc}`} {' '}· never worked this entity ) : hasDxcc ? ( <> {dxccName || `DXCC #${dxcc}`} {dxccCount}{' '} QSO{dxccCount > 1 ? 's' : ''} with this entity {callCount > 0 && ( <> {' · '} {callCount}{' '} with this call )} ) : busy ? ( looking up… ) : ( Type a callsign to see entity stats )}
))} {CLASSES.map((cls) => { const classCurrent = classMatchesMode(cls, currentMode); return ( {cols.map((b) => { const st = statusMap.get(`${b.tag}|${cls}`) ?? ''; const isCurrent = b.tag === currentBand && classCurrent; return ( ); })}
{cols.map((b) => ( {b.label}
{cls} ); })}
{/* Colour legend — sits in the spare room under the matrix. */}
{LEGEND.map((l) => ( {l.label} ))}
); }