import { useEffect, useMemo, useState } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { useI18n } from '@/lib/i18n'; import { writeUiPref } from '@/lib/uiPref'; import { ADIFFields, ADIFExtraFields } from '@/../wailsjs/go/main/App'; import { adif } from '@/../wailsjs/go/models'; type FieldDef = adif.FieldDef; const PREF_KEY = 'opslog.exportFields'; // One category card with its checkboxes + All/None. Defined at MODULE scope (not // inside ExportFieldsDialog) so its component identity is stable across renders — // an inner component is re-created every render, remounting the whole subtree and // making the All/None buttons and checkboxes feel dead. function GroupCard({ title, tags, warn, sel, allLabel, noneLabel, onAll, onNone, onToggle }: { title: string; tags: string[]; warn?: boolean; sel: Set; allLabel: string; noneLabel: string; onAll: (tags: string[]) => void; onNone: (tags: string[]) => void; onToggle: (name: string, on: boolean) => void; }) { return (
{title}
{tags.map((name) => ( ))}
); } // ExportFieldsDialog lets the operator pick exactly which ADIF fields an export // writes. Two groups: the official ADIF 3.1.7 dictionary (grouped by category) // and the OpsLog / non-standard tags actually present in the log's extras. The // chosen set is returned to the parent (which runs the selected/global export) // and persisted so it's the default next time. export function ExportFieldsDialog({ open, count, onExport, onClose }: { open: boolean; count: number; // how many QSOs the pending export covers onExport: (fields: string[]) => void; onClose: () => void; }) { const { t } = useI18n(); const [official, setOfficial] = useState([]); const [extras, setExtras] = useState([]); const [sel, setSel] = useState>(new Set()); const defaultSet = (flds: FieldDef[]) => new Set(flds.filter((d) => d.promoted && !d.deprecated).map((d) => d.name)); useEffect(() => { if (!open) return; let cancelled = false; (async () => { const [f, e] = await Promise.all([ADIFFields(), ADIFExtraFields()]); if (cancelled) return; const flds = (f ?? []) as FieldDef[]; setOfficial(flds); setExtras((e ?? []) as string[]); let saved: string[] | null = null; try { const raw = localStorage.getItem(PREF_KEY); if (raw) saved = JSON.parse(raw); } catch { /* ignore */ } setSel(saved && saved.length ? new Set(saved.map((x) => x.toUpperCase())) : defaultSet(flds)); })().catch(() => {}); return () => { cancelled = true; }; }, [open]); // Official fields grouped by category (deprecated ones are import-only → hidden). const groups = useMemo(() => { const m = new Map(); for (const d of official) { if (d.deprecated) continue; const g = d.category || 'Misc'; (m.get(g) ?? m.set(g, []).get(g)!).push(d); } return [...m.entries()]; }, [official]); const toggle = (name: string, on: boolean) => setSel((s) => { const n = new Set(s); if (on) n.add(name); else n.delete(name); return n; }); const setMany = (names: string[], on: boolean) => setSel((s) => { const n = new Set(s); for (const x of names) { if (on) n.add(x); else n.delete(x); } return n; }); const doExport = () => { const fields = [...sel]; writeUiPref(PREF_KEY, JSON.stringify(fields)); onExport(fields); }; const allLabel = t('exf.all'); const noneLabel = t('exf.none'); const cardProps = { sel, allLabel, noneLabel, onAll: (tags: string[]) => setMany(tags, true), onNone: (tags: string[]) => setMany(tags, false), onToggle: toggle, }; return ( { if (!o) onClose(); }}> {t('exf.title')} {t('exf.desc')}
{t('exf.chosen', { n: sel.size })}
{/* OpsLog / non-standard group first (most relevant to keep or drop). */} {extras.length > 0 && } {groups.map(([g, list]) => ( d.name)} {...cardProps} /> ))}
); }