import { useCallback, useEffect, useMemo, useState } from 'react'; import { Bell, Plus, Trash2, Volume2, Mail, Eye, X, Search } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; import { ListAlertRules, SaveAlertRule, DeleteAlertRule, GetAlertEmailTo, SetAlertEmailTo, } from '@/../wailsjs/go/main/App'; import { alerts } from '@/../wailsjs/go/models'; type Rule = alerts.Rule; const CONTINENTS = ['AF', 'AN', 'AS', 'EU', 'NA', 'OC', 'SA']; function emptyRule(): Rule { return alerts.Rule.createFrom({ id: '', name: 'New alert', enabled: true, calls: [], countries: [], continents: [], bands: [], modes: [], spotter_call: '', spotter_continents: [], spotter_countries: [], sound: true, visual: true, email: false, again_after_min: 0, skip_worked: false, }); } // MultiCheck — a scrollable checkbox list for a set of options with an optional // search box (used for the long country list). Empty selection = ALL. function MultiCheck({ options, selected, onToggle, searchable, height = 'h-52' }: { options: string[]; selected: string[]; onToggle: (v: string) => void; searchable?: boolean; height?: string; }) { const { t } = useI18n(); const [q, setQ] = useState(''); const shown = useMemo( () => (q ? options.filter((o) => o.toLowerCase().includes(q.toLowerCase())) : options), [options, q], ); const sel = new Set((selected ?? []).map((s) => s.toLowerCase())); return (
{searchable && (
setQ(e.target.value)} />
)}
{shown.map((o) => ( ))} {shown.length === 0 &&
{t('altm.noMatch')}
}
{(selected?.length ?? 0) === 0 ? t('altm.noneAll') : t('altm.nSelected', { n: selected!.length })}
); } export function AlertsModal({ onClose, bands, modes, countries }: { onClose: () => void; bands: string[]; modes: string[]; countries: string[]; }) { const { t } = useI18n(); const [rules, setRules] = useState([]); const [draft, setDraft] = useState(null); const [tab, setTab] = useState('def'); // active editor tab (reset to Definition on new/select) const [emailTo, setEmailTo] = useState(''); const [err, setErr] = useState(''); const refresh = useCallback(async () => { try { setRules(((await ListAlertRules()) ?? []) as Rule[]); } catch (e: any) { setErr(String(e?.message ?? e)); } }, []); useEffect(() => { refresh(); GetAlertEmailTo().then((v) => setEmailTo(v || '')).catch(() => {}); }, [refresh]); const patch = (p: Partial) => setDraft((d) => (d ? alerts.Rule.createFrom({ ...d, ...p }) : d)); const toggleIn = (key: keyof Rule, v: string) => setDraft((d) => { if (!d) return d; const cur = ((d as any)[key] as string[]) ?? []; const has = cur.some((x) => x.toLowerCase() === v.toLowerCase()); const next = has ? cur.filter((x) => x.toLowerCase() !== v.toLowerCase()) : [...cur, v]; return alerts.Rule.createFrom({ ...d, [key]: next }); }); async function save() { if (!draft) return; if (!draft.name.trim()) { setErr(t('altm.giveName')); return; } try { const saved = await SaveAlertRule(draft); await refresh(); setDraft(saved as Rule); setErr(''); } catch (e: any) { setErr(String(e?.message ?? e)); } } async function del() { if (!draft) return; if (!draft.id) { setDraft(null); return; } if (!window.confirm(t('altm.deleteConfirm', { name: draft.name }))) return; try { await DeleteAlertRule(draft.id); setDraft(null); await refresh(); } catch (e: any) { setErr(String(e?.message ?? e)); } } return ( { if (!o) onClose(); }}> {t('altm.title')} {t('altm.desc')}
{/* Rule list */}
{t('altm.rules')}
{rules.length === 0 &&
{t('altm.noRules')}
} {rules.map((r) => ( ))}
setEmailTo(e.target.value)} onBlur={() => SetAlertEmailTo(emailTo).catch(() => {})} />
{/* Editor */}
{!draft ? (
{t('altm.selectOrCreate')}
) : (
{t('altm.tabDef')} {t('altm.tabCall')} {t('altm.tabBandMode')} {t('altm.tabOrigin')}
patch({ name: e.target.value })} />
patch({ again_after_min: parseInt(e.target.value) || 0 })} /> {t('altm.againHint')}