diff --git a/frontend/src/components/WatchlistTab.tsx b/frontend/src/components/WatchlistTab.tsx index f48574b..cdc64b3 100644 --- a/frontend/src/components/WatchlistTab.tsx +++ b/frontend/src/components/WatchlistTab.tsx @@ -20,7 +20,9 @@ import { useI18n } from '@/lib/i18n'; import { WatchlistEntries, WatchlistAdd, WatchlistRemove, WatchlistSetNotify, WatchlistSetContest, WatchlistWorkedSlots, + GetWatchlistContestPattern, SetWatchlistContestPattern, } from '../../wailsjs/go/main/App'; +import { EventsOn } from '../../wailsjs/runtime/runtime'; import type { ClusterSpot, SpotStatusEntry } from '@/components/ClusterGrid'; import { inferSpotMode, spotStatusKey } from '@/lib/spot'; @@ -72,7 +74,18 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P try { setEntries(((await WatchlistEntries()) ?? []) as any as WLEntry[]); } catch (e: any) { setError(String(e?.message ?? e)); } }, []); - useEffect(() => { void refresh(); }, [refresh]); + // Refreshed on mount, when the backend auto-adds (event), and on a slow tick + // so last-seen and the spot counters stay honest while the tab sits open. + useEffect(() => { + void refresh(); + const off = EventsOn('watchlist:changed', () => { void refresh(); }); + const id = window.setInterval(() => { void refresh(); }, 30_000); + return () => { off(); window.clearInterval(id); }; + }, [refresh]); + // The auto-contest pattern (DXHunter's contest_prefix): spots whose call + // contains it are added as contest entries by the backend. + const [pattern, setPattern] = useState(''); + useEffect(() => { GetWatchlistContestPattern().then((p: string) => setPattern(p ?? '')).catch(() => {}); }, []); // Live spots per entry — prefix-matched, newest first, deduped per band+mode // (one line per slot; the freshest spot represents it). @@ -191,6 +204,11 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P + setPattern(e.target.value.toUpperCase())} + onBlur={() => { void SetWatchlistContestPattern(pattern.trim()); }} />
diff --git a/frontend/src/lib/i18n.tsx b/frontend/src/lib/i18n.tsx index 3cecc67..2012ae1 100644 --- a/frontend/src/lib/i18n.tsx +++ b/frontend/src/lib/i18n.tsx @@ -39,7 +39,7 @@ const en: Dict = { 'cwd.tipOnIdle': 'CW decoder — on, idle until CW mode · click to disable', 'cwd.tipOff': 'CW decoder · click to enable (decodes RX audio in CW mode)', 'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist', - 'wl.addPh': 'Callsign or prefix', 'wl.add': 'Add', 'wl.contest': 'Contest', 'wl.addAsContest': 'as contest', + 'wl.addPh': 'Callsign or prefix', 'wl.add': 'Add', 'wl.contest': 'Contest', 'wl.addAsContest': 'as contest', 'wl.patternPh': 'Auto (WWA)', 'wl.patternHint': 'Auto-add as contest: any spotted callsign CONTAINING this text joins the watchlist as a contest entry by itself. Empty = off; collected entries stay.', 'wl.contestHint': 'Contest station: worked/needed is judged against the current UTC day — at 00:00 UTC every slot can be worked again.', 'wl.searchPh': 'Search…', 'wl.famAll': 'All', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest', 'wl.activeOnly': 'Active only', 'wl.neededOnly': 'Needed only', @@ -563,7 +563,7 @@ const fr: Dict = { 'cwd.tipOnIdle': 'Décodeur CW — actif, en veille hors mode CW · clic pour désactiver', 'cwd.tipOff': 'Décodeur CW · clic pour activer (décode l’audio RX en mode CW)', 'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist', - 'wl.addPh': 'Indicatif ou préfixe', 'wl.add': 'Ajouter', 'wl.contest': 'Contest', 'wl.addAsContest': 'comme contest', + 'wl.addPh': 'Indicatif ou préfixe', 'wl.add': 'Ajouter', 'wl.contest': 'Contest', 'wl.addAsContest': 'comme contest', 'wl.patternPh': 'Auto (WWA)', 'wl.patternHint': "Ajout auto comme contest : tout indicatif spotté CONTENANT ce texte rejoint la watchlist en entrée contest tout seul. Vide = désactivé ; les entrées déjà collectées restent.", 'wl.contestHint': "Station contest : contacté/manquant est jugé sur la journée UTC courante — à 00:00 UTC chaque créneau redevient à faire.", 'wl.searchPh': 'Chercher…', 'wl.famAll': 'Tous', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest', 'wl.activeOnly': 'Actifs seulement', 'wl.neededOnly': 'Manquants seulement',