feat: implement persistent AG Grid column state
This commit is contained in:
@@ -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],
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user