fix(grid): keep the award columns' position in the saved layout

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.
This commit is contained in:
2026-08-08 17:36:48 +02:00
parent f4b7c9dcf8
commit 366d9df634
+20 -10
View File
@@ -282,12 +282,22 @@ const GRP_KEYS: Record<string, string> = {
}; };
export const groupLabel = (t: TFn, g: string): string => t(GRP_KEYS[g] ?? g); 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 // Award-column VISIBILITY is governed SOLELY by the awardShown code-set, never
// Grid's saved column state. Stripping them here (on both save and restore) // by AG Grid's saved column state: dropping `hide` here (on both save and
// stops a stale saved state from re-hiding a shown award column on every // restore) stops a stale saved state from re-hiding a shown award column on
// awardCols rebuild — the desync that made award columns vanish mid-session. // every awardCols rebuild — the desync that made award columns vanish
const stripAwardCols = (st: any[] | null | undefined): any[] => // mid-session.
(st ?? []).filter((s) => !String(s?.colId ?? '').startsWith('award_')); //
// 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) { 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(); const { t } = useI18n();
@@ -470,12 +480,12 @@ export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal,
function onGridReady(e: GridReadyEvent) { function onGridReady(e: GridReadyEvent) {
onGridApi?.(e.api); onGridApi?.(e.api);
const local = loadLocal(colStateKey); 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 // Fall back to the portable DB copy when the local cache is empty
// (fresh machine / after a reinstall), then re-seed the cache. // (fresh machine / after a reinstall), then re-seed the cache.
loadRemote(colStateKey).then((remote) => { loadRemote(colStateKey).then((remote) => {
if (remote && !local) { 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); 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 if (restoringRef.current) return; // ignore the events fired by a column rebuild
const state = gridRef.current?.api?.getColumnState(); const state = gridRef.current?.api?.getColumnState();
if (!state) return; if (!state) return;
saveState(colStateKey, stripAwardCols(state)); saveState(colStateKey, sanitizeAwardCols(state));
// Award columns are stripped above, so persist their widths on the side. // Award columns are stripped above, so persist their widths on the side.
let changed = false; let changed = false;
for (const s of state) { for (const s of state) {
@@ -527,7 +537,7 @@ export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal,
useEffect(() => { useEffect(() => {
const api = gridRef.current?.api; const api = gridRef.current?.api;
const local = loadLocal(colStateKey); 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. // Re-enable saving once AG Grid has settled the column events from the rebuild.
const t = window.setTimeout(() => { restoringRef.current = false; }, 0); const t = window.setTimeout(() => { restoringRef.current = false; }, 0);
return () => window.clearTimeout(t); return () => window.clearTimeout(t);