feat: Added worked before in Net Control
This commit is contained in:
+45
-28
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AlertCircle, Antenna, CheckCircle2, Clock, Compass, Database, Ear, Eraser, Hash, Loader2, Lock,
|
||||
AlertCircle, Antenna, Bell, CheckCircle2, Clock, Compass, Database, Ear, Eraser, Hash, Loader2, Lock,
|
||||
Maximize2, Minimize2, Mic, MessageSquare, Pencil, RadioTower, RefreshCw, Satellite, Send, Settings, SlidersHorizontal, Square, Trash2, Unlock, X, Zap,
|
||||
} from 'lucide-react';
|
||||
|
||||
@@ -798,6 +798,7 @@ export default function App() {
|
||||
// ~10 min and is clickable to tune the rig to its freq/mode.
|
||||
type RecentAlert = { id: number; rule: string; call: string; band: string; mode: string; freq_hz: number; country: string; comment: string; at: number };
|
||||
const [recentAlerts, setRecentAlerts] = useState<RecentAlert[]>([]);
|
||||
const [alertsPanelOpen, setAlertsPanelOpen] = useState(false); // LED dropdown
|
||||
const ALERT_TTL_MS = 10 * 60 * 1000;
|
||||
useEffect(() => {
|
||||
const id = window.setInterval(() => {
|
||||
@@ -2722,6 +2723,48 @@ export default function App() {
|
||||
<Input value={grid} placeholder="JN05" className="font-mono" onChange={(e) => { setGrid(e.target.value); markEdited('grid'); }} />
|
||||
</div>
|
||||
);
|
||||
// A discreet spot-alert LED (bell) that fills the gap at the right of the Grid
|
||||
// row instead of the intrusive floating cards. It lights + shows a count when
|
||||
// alerts are pending; click it to see the last 3 (each clickable to tune).
|
||||
const hasAlerts = recentAlerts.length > 0;
|
||||
const alertLedBlock = (
|
||||
<div className="relative self-end mb-0.5 shrink-0">
|
||||
<button type="button" onClick={() => setAlertsPanelOpen((o) => !o)}
|
||||
title={hasAlerts ? t('alert.pending', { n: recentAlerts.length }) : t('alert.noneShort')}
|
||||
className={cn('relative inline-flex size-8 items-center justify-center rounded-full border transition-colors',
|
||||
hasAlerts ? 'border-warning bg-warning/15 text-warning' : 'border-border bg-card text-muted-foreground hover:bg-muted')}>
|
||||
<Bell className="size-4" />
|
||||
{hasAlerts && <span className="absolute -right-1 -top-1 inline-flex size-2.5 rounded-full bg-warning animate-ping" />}
|
||||
{hasAlerts && <span className="absolute -right-1 -top-1 inline-flex size-2.5 rounded-full bg-warning" />}
|
||||
</button>
|
||||
{alertsPanelOpen && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-40" onClick={() => setAlertsPanelOpen(false)} />
|
||||
<div className="absolute right-0 top-9 z-50 w-72 rounded-lg border border-border bg-card shadow-xl overflow-hidden">
|
||||
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border/60 bg-muted/30">
|
||||
<span className="text-[11px] font-bold uppercase tracking-wider text-muted-foreground">{t('alert.recent')}</span>
|
||||
{hasAlerts && <button type="button" className="text-[11px] text-muted-foreground hover:text-foreground" onClick={() => setRecentAlerts([])}>{t('alert.clear')}</button>}
|
||||
</div>
|
||||
{!hasAlerts ? (
|
||||
<div className="px-3 py-4 text-center text-xs text-muted-foreground">{t('alert.noneShort')}</div>
|
||||
) : recentAlerts.map((a) => (
|
||||
<button key={a.id} type="button" title={t('alert.tuneHint')}
|
||||
onClick={() => { handleSpotClick({ comment: a.comment, freq_hz: a.freq_hz, band: a.band, dx_call: a.call }); setAlertsPanelOpen(false); }}
|
||||
className="block w-full text-left px-3 py-2 border-b border-border/30 last:border-0 hover:bg-warning/10 transition-colors">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-bold text-sm truncate">{a.call}</span>
|
||||
{a.freq_hz > 0 && <span className="ml-auto shrink-0 text-[11px] font-mono tabular-nums text-warning">{(a.freq_hz / 1e6).toFixed(3)}</span>}
|
||||
</div>
|
||||
<div className="text-[11px] text-muted-foreground truncate">
|
||||
{a.rule}{a.band ? ` · ${a.band}` : ''}{a.mode ? ` · ${a.mode}` : ''}{a.country ? ` · ${a.country}` : ''}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
// Compact-strip Country (stacked label) + a narrow Comment.
|
||||
const countryBlockSm = (
|
||||
<div className="flex flex-col w-36">
|
||||
@@ -3613,6 +3656,7 @@ export default function App() {
|
||||
</div>
|
||||
{qthBlock}
|
||||
{gridBlock}
|
||||
{alertLedBlock}
|
||||
</div>
|
||||
|
||||
{/* Row 3: tight left detail column (Band/Mode/Country) and
|
||||
@@ -4391,33 +4435,6 @@ export default function App() {
|
||||
<AlertsModal onClose={() => setAlertsOpen(false)} bands={bands} modes={modes} countries={countries} />
|
||||
)}
|
||||
|
||||
{/* Persistent spot-alert tray (top-right). Each card stays ~10 min; click it
|
||||
to tune the rig to its freq/mode + fill the callsign; × dismisses it. */}
|
||||
{recentAlerts.length > 0 && (
|
||||
<div className="fixed top-16 right-3 z-[60] flex flex-col gap-2 w-72 max-w-[85vw]">
|
||||
{recentAlerts.map((a) => (
|
||||
<div key={a.id} className="flex items-stretch rounded-lg border border-warning/50 bg-card shadow-lg overflow-hidden">
|
||||
<button type="button"
|
||||
title={t('alert.tuneHint')}
|
||||
onClick={() => handleSpotClick({ comment: a.comment, freq_hz: a.freq_hz, band: a.band, dx_call: a.call })}
|
||||
className="flex-1 min-w-0 text-left px-3 py-2 hover:bg-warning/10 transition-colors">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span>🔔</span>
|
||||
<span className="font-bold text-sm truncate">{a.call}</span>
|
||||
{a.freq_hz > 0 && <span className="ml-auto shrink-0 text-[11px] font-mono tabular-nums text-warning">{(a.freq_hz / 1e6).toFixed(3)}</span>}
|
||||
</div>
|
||||
<div className="text-[11px] text-muted-foreground truncate">
|
||||
{a.rule}{a.band ? ` · ${a.band}` : ''}{a.mode ? ` · ${a.mode}` : ''}{a.country ? ` · ${a.country}` : ''}
|
||||
</div>
|
||||
</button>
|
||||
<button type="button" title={t('alert.dismiss')}
|
||||
onClick={() => setRecentAlerts((prev) => prev.filter((x) => x.id !== a.id))}
|
||||
className="shrink-0 px-2 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors">×</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showDuplicates && (
|
||||
<DuplicatesModal onClose={() => setShowDuplicates(false)} onDeleted={() => refresh()} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user