fix: bug correction on Awards columns persistence after relaunch

This commit is contained in:
2026-07-16 08:48:12 +02:00
parent b83a4f4455
commit e8691a43ba
2 changed files with 59 additions and 9 deletions
+58 -9
View File
@@ -262,6 +262,30 @@ export function RecentQSOsGrid({ rows, selectAllSignal, onRowDoubleClicked, onRo
// auto-save during that window. Set in the memo (runs at render, before the
// column events) and cleared by the re-apply effect below.
const restoringRef = useRef(true);
// Award-column visibility is stored as an explicit set of award CODES, NOT via
// AG Grid's column-state round-trip. Those columns load asynchronously and
// race the state restore, and AG Grid preserves an existing column's visibility
// across a columnDefs rebuild instead of re-reading `hide` — which is exactly
// why previously-shown award columns kept vanishing on reopen. A dedicated set
// is deterministic: it drives `hide` directly and is re-enforced below.
const AWARD_SHOWN_KEY = 'hamlog.awardColsShown';
const [awardShown, setAwardShown] = useState<Set<string>>(() => new Set((loadLocal(AWARD_SHOWN_KEY) ?? []).map((s) => String(s).toUpperCase())));
useEffect(() => {
// Fresh machine: hydrate the set from the portable DB copy, then seed the cache.
if (loadLocal(AWARD_SHOWN_KEY)) return;
loadRemote(AWARD_SHOWN_KEY).then((remote) => {
if (remote && remote.length) {
seedLocal(AWARD_SHOWN_KEY, remote);
setAwardShown(new Set(remote.map((s: any) => String(s).toUpperCase())));
}
});
}, []);
const persistAwardShown = useCallback((next: Set<string>) => {
setAwardShown(next);
saveState(AWARD_SHOWN_KEY, [...next]);
}, []);
const columnDefs = useMemo<ColDef<QSOForm>[]>(() => {
restoringRef.current = true;
const base = COL_CATALOG.map((c) => {
@@ -274,16 +298,26 @@ export function RecentQSOsGrid({ rows, selectAllSignal, onRowDoubleClicked, onRo
headerTooltip: t('rqg.awardTip', { name: a.name }),
width: 110,
cellClass: 'text-[11px]',
// Hidden by DEFAULT (award columns are opt-in). Without this, AG Grid shows
// them whenever the saved column state doesn't cover them — a newly-added
// award, an empty cache, or a rebuild that runs before the saved state is
// re-applied — which is why "all award columns" kept reappearing. The user's
// saved state (a column they explicitly showed) still overrides this.
hide: true,
// Visibility comes from the persisted award-code set, so a column the user
// showed reappears on reopen and one they didn't stays hidden.
hide: !awardShown.has(a.code.toUpperCase()),
valueGetter: (p) => (p.data as any)?.award_refs?.[a.code.toUpperCase()] ?? '',
}));
return [...base, ...awards];
}, [awardCols, COL_CATALOG, t]);
}, [awardCols, COL_CATALOG, t, awardShown]);
// Enforce award-column visibility via the API after the columns (re)appear —
// colDef.hide alone isn't honoured by AG Grid for a column that already exists,
// so we set it explicitly whenever the award set or the loaded columns change.
useEffect(() => {
const api = gridRef.current?.api;
if (!api || !awardCols?.length) return;
for (const a of awardCols) {
const want = awardShown.has(a.code.toUpperCase());
const col = api.getColumn(`award_${a.code}`);
if (col && col.isVisible() !== want) api.setColumnsVisible([`award_${a.code}`], want);
}
}, [awardCols, awardShown]);
const defaultColDef = useMemo<ColDef>(() => ({
sortable: true,
@@ -342,10 +376,22 @@ export function RecentQSOsGrid({ rows, selectAllSignal, onRowDoubleClicked, onRo
// state for "which columns are visible" — AG Grid's column state is the
// source of truth, and saveColumnState persists it.
function isColVisible(colId: string): boolean {
// Award columns: the persisted set is the source of truth (see awardShown).
if (colId.startsWith('award_')) return awardShown.has(colId.slice('award_'.length).toUpperCase());
const col = gridRef.current?.api?.getColumn(colId);
return col ? col.isVisible() : !!COL_CATALOG.find((c) => c.colId === colId)?.defaultVisible;
}
function setColVisible(colId: string, visible: boolean) {
// Award columns are driven by the persisted code set — update it and let the
// enforce effect apply visibility. This is what makes them stick across reopen.
if (colId.startsWith('award_')) {
const code = colId.slice('award_'.length).toUpperCase();
const next = new Set(awardShown);
if (visible) next.add(code); else next.delete(code);
persistAwardShown(next);
gridRef.current?.api?.setColumnsVisible([colId], visible);
return;
}
const api = gridRef.current?.api;
if (!api) return;
api.setColumnsVisible([colId], visible);
@@ -373,6 +419,9 @@ export function RecentQSOsGrid({ rows, selectAllSignal, onRowDoubleClicked, onRo
api.setColumnsVisible(visible, true);
api.setColumnsVisible(hidden, false);
saveColumnState();
// Award columns are opt-in: reset hides them all.
persistAwardShown(new Set());
(awardCols ?? []).forEach((a) => api.setColumnsVisible([`award_${a.code}`], false));
}
return (
@@ -469,8 +518,8 @@ export function RecentQSOsGrid({ rows, selectAllSignal, onRowDoubleClicked, onRo
<div className="flex items-center justify-between mb-2 pb-1.5 border-b border-border/60">
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">{t('rqg.grpAwards')}</span>
<div className="flex gap-0.5">
<button className="text-[10px] text-primary hover:underline px-1" onClick={() => { awardCols.forEach((a) => setColVisible(`award_${a.code}`, true)); }}>{t('rqg.all')}</button>
<button className="text-[10px] text-muted-foreground hover:underline px-1" onClick={() => { awardCols.forEach((a) => setColVisible(`award_${a.code}`, false)); }}>{t('rqg.none')}</button>
<button className="text-[10px] text-primary hover:underline px-1" onClick={() => { persistAwardShown(new Set(awardCols.map((a) => a.code.toUpperCase()))); awardCols.forEach((a) => gridRef.current?.api?.setColumnsVisible([`award_${a.code}`], true)); }}>{t('rqg.all')}</button>
<button className="text-[10px] text-muted-foreground hover:underline px-1" onClick={() => { persistAwardShown(new Set()); awardCols.forEach((a) => gridRef.current?.api?.setColumnsVisible([`award_${a.code}`], false)); }}>{t('rqg.none')}</button>
</div>
</div>
<div className="flex flex-col gap-1">