feat: implement persistent AG Grid column state

This commit is contained in:
Firman Ramdhani
2026-07-27 17:02:58 +07:00
parent a2616f2b2d
commit 75e0424e36
4 changed files with 87 additions and 4 deletions
@@ -16,4 +16,7 @@ export const fullPageStore = create<FullPageStoreState>((set) => ({
privileges: [], privileges: [],
setPrivileges: (privileges) => set({ privileges }), setPrivileges: (privileges) => set({ privileges }),
tableConfig: null,
setTableConfig: (config) => set({ tableConfig: config }),
})); }));
@@ -10,6 +10,10 @@ import {
MenuItemDef, MenuItemDef,
DefaultMenuItem, DefaultMenuItem,
StatusBar, StatusBar,
ColumnMovedEvent,
ColumnResizedEvent,
ColumnVisibleEvent,
SortChangedEvent,
} from 'ag-grid-community'; } from 'ag-grid-community';
import { Box, Group, TextInput, Indicator, CloseButton } from '@mantine/core'; import { Box, Group, TextInput, Indicator, CloseButton } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
@@ -181,6 +185,12 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
searchKey = 'search', searchKey = 'search',
filterConfig, filterConfig,
// Grid config props
onColumnMoved,
onColumnResized,
onColumnVisible,
onSortChanged,
// Bulk action props // Bulk action props
customBulkActions, customBulkActions,
batchSize, batchSize,
@@ -200,8 +210,16 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const { t } = useEnterpriseModuleTranslationContext(); const { t } = useEnterpriseModuleTranslationContext();
const { dataServices } = useEnterpriseModuleDataServiceContext<E>(); const { dataServices } = useEnterpriseModuleDataServiceContext<E>();
const { selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData } = const {
useEnterpriseModuleSelectionContext<E>(); selectedRows,
setSelectedRows,
metaData,
setMetaData,
filterData,
setFilterData,
tableConfig,
setTableConfig,
} = useEnterpriseModuleSelectionContext<E>();
const navigation = useEnterpriseModuleNavigationContext(); const navigation = useEnterpriseModuleNavigationContext();
const { config } = useEnterpriseModuleConfigContext(); const { config } = useEnterpriseModuleConfigContext();
@@ -791,6 +809,11 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
setSelectedRows([]); setSelectedRows([]);
if (params.api) { if (params.api) {
// Restore grid configuration on mount
if (tableConfig) {
params.api.applyColumnState({ state: tableConfig, applyOrder: true });
}
// Attach the server-side datasource to the grid API // Attach the server-side datasource to the grid API
params.api.setGridOption('serverSideDatasource', datasource); params.api.setGridOption('serverSideDatasource', datasource);
@@ -803,7 +826,7 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
} }
} }
}, },
[datasource, setSelectedRows, isPaginated, metaData], [datasource, setSelectedRows, isPaginated, metaData, tableConfig],
); );
// Triggered whenever the row selection in the grid changes // Triggered whenever the row selection in the grid changes
@@ -817,6 +840,50 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
[setSelectedRows], [setSelectedRows],
); );
// ---------------------------------------------------------------------------
// Save Grid Configuration State Handlers
// ---------------------------------------------------------------------------
const saveGridState = useCallback(() => {
if (!gridApiRef.current || !setTableConfig) return;
const state = gridApiRef.current.getColumnState();
setTableConfig(state);
}, [setTableConfig]);
const handleColumnMoved = useCallback(
(e: ColumnMovedEvent<E>) => {
saveGridState();
if (onColumnMoved) onColumnMoved(e);
},
[saveGridState, onColumnMoved],
);
const handleColumnResized = useCallback(
(e: ColumnResizedEvent<E>) => {
// Only save when resizing is finished to prevent excessive re-renders
if (e.finished) {
saveGridState();
}
if (onColumnResized) onColumnResized(e);
},
[saveGridState, onColumnResized],
);
const handleColumnVisible = useCallback(
(e: ColumnVisibleEvent<E>) => {
saveGridState();
if (onColumnVisible) onColumnVisible(e);
},
[saveGridState, onColumnVisible],
);
const handleSortChanged = useCallback(
(e: SortChangedEvent<E>) => {
saveGridState();
if (onSortChanged) onSortChanged(e);
},
[saveGridState, onSortChanged],
);
// Configures the context menu items available when right-clicking a cell // Configures the context menu items available when right-clicking a cell
const getContextMenuItems = useCallback((): const getContextMenuItems = useCallback(():
| (DefaultMenuItem | MenuItemDef)[] | (DefaultMenuItem | MenuItemDef)[]
@@ -923,6 +990,10 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
enableCellTextSelection={true} enableCellTextSelection={true}
onSelectionChanged={handleSelectionChanged} onSelectionChanged={handleSelectionChanged}
onGridReady={onGridReady} onGridReady={onGridReady}
onColumnMoved={handleColumnMoved}
onColumnResized={handleColumnResized}
onColumnVisible={handleColumnVisible}
onSortChanged={handleSortChanged}
getContextMenuItems={getContextMenuItems} getContextMenuItems={getContextMenuItems}
statusBar={statusBar} statusBar={statusBar}
domLayout={domLayout} domLayout={domLayout}
@@ -4,6 +4,7 @@ import type { ZodType } from 'zod';
import type { BaseEntity, BaseRemoteDataServices, StandardPaginationMeta } from '@repo/core-api/data-services'; import type { BaseEntity, BaseRemoteDataServices, StandardPaginationMeta } from '@repo/core-api/data-services';
import type { ModulePageHeaderProps } from '../components/module-page-header'; import type { ModulePageHeaderProps } from '../components/module-page-header';
import { PageActionsProps } from '../../../components'; import { PageActionsProps } from '../../../components';
import { ColumnState } from 'ag-grid-community';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Module Constants & Base Types // Module Constants & Base Types
@@ -140,6 +141,8 @@ export interface SelectionSlice<
setMetaData: (data: TMeta | null) => void; setMetaData: (data: TMeta | null) => void;
filterData: TFilter | null; filterData: TFilter | null;
setFilterData: (data: TFilter | null) => void; setFilterData: (data: TFilter | null) => void;
tableConfig: ColumnState[] | null;
setTableConfig: (config: ColumnState[] | null) => void;
} }
/** /**
@@ -156,6 +159,8 @@ export interface EnterpriseModuleState<
setFilterData: (data: TFilter | null) => void; setFilterData: (data: TFilter | null) => void;
selectedRows: E[]; selectedRows: E[];
setSelectedRows: (rows: E[]) => void; setSelectedRows: (rows: E[]) => void;
tableConfig: ColumnState[] | null;
setTableConfig: (config: ColumnState[] | null) => void;
privileges: string[]; privileges: string[];
setPrivileges: (privileges: string[]) => void; setPrivileges: (privileges: string[]) => void;
} }
@@ -82,6 +82,8 @@ export function EnterpriseModuleProvider<
const setMetaData = store((state: S) => state.setMetaData); const setMetaData = store((state: S) => state.setMetaData);
const filterData = store((state: S) => state.filterData); const filterData = store((state: S) => state.filterData);
const setFilterData = store((state: S) => state.setFilterData); const setFilterData = store((state: S) => state.setFilterData);
const tableConfig = store((state: S) => state.tableConfig);
const setTableConfig = store((state: S) => state.setTableConfig);
const selectionSlice = useMemo( const selectionSlice = useMemo(
() => ({ () => ({
@@ -91,8 +93,10 @@ export function EnterpriseModuleProvider<
setMetaData, setMetaData,
filterData, filterData,
setFilterData, setFilterData,
tableConfig,
setTableConfig,
}), }),
[selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData], [selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData, tableConfig, setTableConfig],
); );
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------