fix(watchlist): the auto-contest FIELD — the backend shipped without its UI
The wiring script died on an app.go anchor mid-run: the backend half was committed, the frontend half (the pattern field, the changed-event refresh, the i18n keys) never ran. Replayed; the field sits after Add.
This commit is contained in:
@@ -20,7 +20,9 @@ import { useI18n } from '@/lib/i18n';
|
|||||||
import {
|
import {
|
||||||
WatchlistEntries, WatchlistAdd, WatchlistRemove, WatchlistSetNotify,
|
WatchlistEntries, WatchlistAdd, WatchlistRemove, WatchlistSetNotify,
|
||||||
WatchlistSetContest, WatchlistWorkedSlots,
|
WatchlistSetContest, WatchlistWorkedSlots,
|
||||||
|
GetWatchlistContestPattern, SetWatchlistContestPattern,
|
||||||
} from '../../wailsjs/go/main/App';
|
} from '../../wailsjs/go/main/App';
|
||||||
|
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
||||||
import type { ClusterSpot, SpotStatusEntry } from '@/components/ClusterGrid';
|
import type { ClusterSpot, SpotStatusEntry } from '@/components/ClusterGrid';
|
||||||
import { inferSpotMode, spotStatusKey } from '@/lib/spot';
|
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[]); }
|
try { setEntries(((await WatchlistEntries()) ?? []) as any as WLEntry[]); }
|
||||||
catch (e: any) { setError(String(e?.message ?? e)); }
|
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
|
// Live spots per entry — prefix-matched, newest first, deduped per band+mode
|
||||||
// (one line per slot; the freshest spot represents it).
|
// (one line per slot; the freshest spot represents it).
|
||||||
@@ -191,6 +204,11 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
|
|||||||
<Button size="sm" className="h-8" onClick={() => void add()} disabled={!addCall.trim()}>
|
<Button size="sm" className="h-8" onClick={() => void add()} disabled={!addCall.trim()}>
|
||||||
<Plus className="size-3.5" /> {t('wl.add')}
|
<Plus className="size-3.5" /> {t('wl.add')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Input className="h-8 w-28 font-mono uppercase" placeholder={t('wl.patternPh')}
|
||||||
|
title={t('wl.patternHint')}
|
||||||
|
value={pattern}
|
||||||
|
onChange={(e) => setPattern(e.target.value.toUpperCase())}
|
||||||
|
onBlur={() => { void SetWatchlistContestPattern(pattern.trim()); }} />
|
||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="size-3.5 absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
<Search className="size-3.5 absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const en: Dict = {
|
|||||||
'cwd.tipOnIdle': 'CW decoder — on, idle until CW mode · click to disable',
|
'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)',
|
'cwd.tipOff': 'CW decoder · click to enable (decodes RX audio in CW mode)',
|
||||||
'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist',
|
'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.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.searchPh': 'Search…', 'wl.famAll': 'All', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
||||||
'wl.activeOnly': 'Active only', 'wl.neededOnly': 'Needed only',
|
'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.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)',
|
'cwd.tipOff': 'Décodeur CW · clic pour activer (décode l’audio RX en mode CW)',
|
||||||
'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist',
|
'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.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.searchPh': 'Chercher…', 'wl.famAll': 'Tous', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
||||||
'wl.activeOnly': 'Actifs seulement', 'wl.neededOnly': 'Manquants seulement',
|
'wl.activeOnly': 'Actifs seulement', 'wl.neededOnly': 'Manquants seulement',
|
||||||
|
|||||||
Reference in New Issue
Block a user