From 366d9df6341e1fafe1b375c7b9f08b19629a75a7 Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Sat, 8 Aug 2026 17:36:48 +0200 Subject: [PATCH] fix(grid): keep the award columns' position in the saved layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Award columns were filtered out of the persisted column state entirely, so applyColumnState({applyOrder:true}) had nowhere to put them and AG Grid appended what the state does not mention. A column the operator dragged to sit AFTER an award column therefore jumped back in front of it on every reload. Only the `hide` property is dropped now. Visibility stays owned by the awardShown code-set — that is what stopped shown award columns from vanishing on an awardCols rebuild — while the position survives the round-trip. --- frontend/src/components/RecentQSOsGrid.tsx | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/RecentQSOsGrid.tsx b/frontend/src/components/RecentQSOsGrid.tsx index c58dd67..3611a42 100644 --- a/frontend/src/components/RecentQSOsGrid.tsx +++ b/frontend/src/components/RecentQSOsGrid.tsx @@ -282,12 +282,22 @@ const GRP_KEYS: Record = { }; export const groupLabel = (t: TFn, g: string): string => t(GRP_KEYS[g] ?? g); -// Award columns are governed SOLELY by the awardShown code-set, never by AG -// Grid's saved column state. Stripping them here (on both save and restore) -// stops a stale saved state from re-hiding a shown award column on every -// awardCols rebuild — the desync that made award columns vanish mid-session. -const stripAwardCols = (st: any[] | null | undefined): any[] => - (st ?? []).filter((s) => !String(s?.colId ?? '').startsWith('award_')); +// Award-column VISIBILITY is governed SOLELY by the awardShown code-set, never +// by AG Grid's saved column state: dropping `hide` here (on both save and +// restore) stops a stale saved state from re-hiding a shown award column on +// every awardCols rebuild — the desync that made award columns vanish +// mid-session. +// +// Their POSITION, however, has to stay in the state. Filtering the entries out +// entirely left applyColumnState({applyOrder:true}) with no place for them, and +// AG Grid appends what the state doesn't mention — so a column the operator +// dragged to sit AFTER an award column jumped back in front of it on reload. +const sanitizeAwardCols = (st: any[] | null | undefined): any[] => + (st ?? []).map((s) => { + if (!String(s?.colId ?? '').startsWith('award_')) return s; + const { hide: _hide, ...rest } = s as any; + return rest; + }); export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal, rowDragCall, passOrder, onGridApi, storageKey, onRowDoubleClicked, onRowClicked, onRowSelected, onRowSelectedQso, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete, onFilteredCountChange, awardCols }: Props) { const { t } = useI18n(); @@ -470,12 +480,12 @@ export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal, function onGridReady(e: GridReadyEvent) { onGridApi?.(e.api); const local = loadLocal(colStateKey); - if (local) e.api.applyColumnState({ state: sanitizeState(stripAwardCols(local)) as ColumnState[], applyOrder: true }); + if (local) e.api.applyColumnState({ state: sanitizeState(sanitizeAwardCols(local)) as ColumnState[], applyOrder: true }); // Fall back to the portable DB copy when the local cache is empty // (fresh machine / after a reinstall), then re-seed the cache. loadRemote(colStateKey).then((remote) => { if (remote && !local) { - e.api.applyColumnState({ state: sanitizeState(stripAwardCols(remote)) as ColumnState[], applyOrder: true }); + e.api.applyColumnState({ state: sanitizeState(sanitizeAwardCols(remote)) as ColumnState[], applyOrder: true }); seedLocal(colStateKey, remote); } }); @@ -499,7 +509,7 @@ export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal, if (restoringRef.current) return; // ignore the events fired by a column rebuild const state = gridRef.current?.api?.getColumnState(); if (!state) return; - saveState(colStateKey, stripAwardCols(state)); + saveState(colStateKey, sanitizeAwardCols(state)); // Award columns are stripped above, so persist their widths on the side. let changed = false; for (const s of state) { @@ -527,7 +537,7 @@ export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal, useEffect(() => { const api = gridRef.current?.api; const local = loadLocal(colStateKey); - if (api && local) api.applyColumnState({ state: sanitizeState(stripAwardCols(local)) as ColumnState[], applyOrder: true }); + if (api && local) api.applyColumnState({ state: sanitizeState(sanitizeAwardCols(local)) as ColumnState[], applyOrder: true }); // Re-enable saving once AG Grid has settled the column events from the rebuild. const t = window.setTimeout(() => { restoringRef.current = false; }, 0); return () => window.clearTimeout(t);