fix: ADIF export field picker All/None buttons dead — hoist GroupCard

GroupCard was defined inside ExportFieldsDialog, so it got a new component
identity every render and React remounted the whole grid on each sel change,
making the per-group All/None buttons (and checkboxes) feel unresponsive. Hoist
GroupCard to module scope with sel + handlers passed as props. Changelog 0.21.1.
This commit is contained in:
2026-07-25 00:51:32 +02:00
parent e6a6f04ccf
commit 3f15608c59
2 changed files with 42 additions and 21 deletions
+38 -19
View File
@@ -12,6 +12,34 @@ 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<string>;
allLabel: string; noneLabel: string;
onAll: (tags: string[]) => void; onNone: (tags: string[]) => void; onToggle: (name: string, on: boolean) => void;
}) {
return (
<div className={`rounded-md border p-2 ${warn ? 'border-warning-border/50 bg-warning-muted/20' : 'border-border/60'}`}>
<div className="flex items-center justify-between mb-1 gap-2">
<span className={`text-[11px] font-semibold uppercase tracking-wide ${warn ? 'text-warning-muted-foreground' : 'text-muted-foreground'}`}>{title}</span>
<span className="flex gap-1.5 shrink-0">
<button type="button" className="text-[10px] text-primary hover:underline" onClick={() => onAll(tags)}>{allLabel}</button>
<button type="button" className="text-[10px] text-muted-foreground hover:underline" onClick={() => onNone(tags)}>{noneLabel}</button>
</span>
</div>
{tags.map((name) => (
<label key={name} className="flex items-center gap-1.5 text-[11px] cursor-pointer py-0.5">
<Checkbox checked={sel.has(name)} onCheckedChange={(c) => onToggle(name, !!c)} />
<span className="font-mono break-all">{name}</span>
</label>
))}
</div>
);
}
// 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
@@ -69,23 +97,14 @@ export function ExportFieldsDialog({ open, count, onExport, onClose }: {
onExport(fields);
};
const GroupCard = ({ title, tags, warn }: { title: string; tags: string[]; warn?: boolean }) => (
<div className={`rounded-md border p-2 ${warn ? 'border-warning-border/50 bg-warning-muted/20' : 'border-border/60'}`}>
<div className="flex items-center justify-between mb-1 gap-2">
<span className={`text-[11px] font-semibold uppercase tracking-wide ${warn ? 'text-warning-muted-foreground' : 'text-muted-foreground'}`}>{title}</span>
<span className="flex gap-1.5 shrink-0">
<button type="button" className="text-[10px] text-primary hover:underline" onClick={() => setMany(tags, true)}>{t('exf.all')}</button>
<button type="button" className="text-[10px] text-muted-foreground hover:underline" onClick={() => setMany(tags, false)}>{t('exf.none')}</button>
</span>
</div>
{tags.map((name) => (
<label key={name} className="flex items-center gap-1.5 text-[11px] cursor-pointer py-0.5">
<Checkbox checked={sel.has(name)} onCheckedChange={(c) => toggle(name, !!c)} />
<span className="font-mono break-all">{name}</span>
</label>
))}
</div>
);
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 (
<Dialog open={open} onOpenChange={(o) => { if (!o) onClose(); }}>
@@ -104,9 +123,9 @@ export function ExportFieldsDialog({ open, count, onExport, onClose }: {
<div className="grid grid-cols-3 gap-3 max-h-[56vh] overflow-y-auto pr-1">
{/* OpsLog / non-standard group first (most relevant to keep or drop). */}
{extras.length > 0 && <GroupCard title={t('exf.opslogGroup')} tags={extras} warn />}
{extras.length > 0 && <GroupCard title={t('exf.opslogGroup')} tags={extras} warn {...cardProps} />}
{groups.map(([g, list]) => (
<GroupCard key={g} title={g} tags={list.map((d) => d.name)} />
<GroupCard key={g} title={g} tags={list.map((d) => d.name)} {...cardProps} />
))}
</div>