import { useState } from 'react'; import { Antenna, X, ListFilter } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; export type AGAntenna = { index: number; name: string; bands?: number }; export type AGStatus = { connected: boolean; host?: string; last_error?: string; port_a: number; port_b: number; tx_a?: boolean; tx_b?: boolean; antennas: AGAntenna[]; }; // AG_BAND_BITS maps a band name to its bit in the 4O3A Antenna Genius band // bitmask (standard HF+6m order, bit 0 = 160m). Used to show only the antennas // configured for the current band, like the native app. If the mapping ever // proves off on real hardware, the debug log (antgenius: antenna raw …) shows the // device's actual mask so it can be corrected. const AG_BAND_BITS: Record = { '160m': 1 << 0, '80m': 1 << 1, '60m': 0, '40m': 1 << 2, '30m': 1 << 3, '20m': 1 << 4, '17m': 1 << 5, '15m': 1 << 6, '12m': 1 << 7, '10m': 1 << 8, '6m': 1 << 9, }; function bandBit(band?: string): number { if (!band) return 0; return AG_BAND_BITS[band.trim().toLowerCase()] ?? 0; } // Format an antenna name: first letter uppercase, the rest lowercase // (e.g. "DX COMMANDER" → "Dx commander"). function pretty(name: string): string { const t = name.trim(); if (!t) return t; return t.charAt(0).toUpperCase() + t.slice(1).toLowerCase(); } // AntGeniusPanel — antenna-switch widget for a 4O3A Antenna Genius, styled to // match the app's light theme with soft gradients + glows. Each antenna row has // a port-A button (left) and port-B button (right). Colours: green = selected on // port A, blue = selected on port B, red (pulsing) = that port is transmitting. // Clicking an already-selected port deselects it (port → None). export function AntGeniusPanel({ status, onActivate, onClose, band }: { status: AGStatus; onActivate: (port: number, antenna: number) => void; // antenna 0 = deselect onClose: () => void; band?: string; // current operating band (e.g. "20m") for the band filter }) { const { t } = useI18n(); const allAntennas = status.antennas ?? []; // Band filter (persisted, default ON): show only antennas whose band mask // includes the current band — matching the native 4O3A app. Antennas with an // unknown mask (0) always show, and if the filter would hide EVERYTHING (e.g. a // band we can't map, or masks the device didn't send) we fall back to the full // list so the panel is never mysteriously empty. const [bandFilter, setBandFilter] = useState(() => localStorage.getItem('opslog.agBandFilter') !== '0'); const toggleBandFilter = () => setBandFilter((v) => { const n = !v; localStorage.setItem('opslog.agBandFilter', n ? '1' : '0'); return n; }); const bit = bandBit(band); let list = allAntennas; if (bandFilter && bit !== 0) { const filtered = allAntennas.filter((a) => !a.bands || (a.bands & bit) !== 0); if (filtered.length > 0) list = filtered; } const PortBtn = ({ port, index, active, tx }: { port: 1 | 2; index: number; active: boolean; tx: boolean }) => { const letter = port === 1 ? 'A' : 'B'; const cls = tx ? 'bg-gradient-to-b from-red-500 to-rose-600 text-white border-red-400/50 shadow-[0_0_10px_rgba(244,63,94,0.5)] animate-pulse' : active ? (port === 1 ? 'bg-gradient-to-b from-emerald-400 to-emerald-600 text-white border-emerald-300/60 shadow-[0_0_9px_rgba(16,185,129,0.45)]' : 'bg-gradient-to-b from-sky-400 to-sky-600 text-white border-sky-300/60 shadow-[0_0_9px_rgba(14,165,233,0.45)]') : 'bg-card text-muted-foreground border-border hover:bg-muted hover:text-foreground'; return ( ); }; return (
Antenna Genius {status.connected ? t('agp.online') : t('agp.offline')}
{!status.connected ? (
{t('agp.connecting')}
{status.last_error &&
{status.last_error}
}
) : list.length === 0 ? (
{t('agp.noAntennas')}
) : list.map((a) => { const aActive = status.port_a === a.index; const bActive = status.port_b === a.index; const aTx = aActive && !!status.tx_a; const bTx = bActive && !!status.tx_b; const nameCls = (aTx || bTx) ? 'bg-gradient-to-r from-red-500 to-rose-600 text-white border-red-400/40 shadow-[0_0_11px_rgba(244,63,94,0.35)]' : aActive ? 'bg-gradient-to-r from-emerald-500 to-emerald-600 text-white border-emerald-400/40 shadow-[0_0_11px_rgba(16,185,129,0.3)]' : bActive ? 'bg-gradient-to-r from-sky-500 to-sky-600 text-white border-sky-400/40 shadow-[0_0_11px_rgba(14,165,233,0.3)]' : 'bg-card/70 text-foreground/80 border-border hover:bg-muted/60'; return (
{pretty(a.name)}
); })}
); }