diff --git a/src/components/Grid/useDataGridSource.tsx b/src/components/Grid/useDataGridSource.tsx index d4322917e..66dd118f9 100644 --- a/src/components/Grid/useDataGridSource.tsx +++ b/src/components/Grid/useDataGridSource.tsx @@ -18,6 +18,7 @@ import { useGridApiContext, useGridApiRef, } from '@mui/x-data-grid-pro'; +import { GridInitialStatePro } from '@mui/x-data-grid-pro/models/gridStatePro'; import { groupBy, Nil, setOf } from '@seedcompany/common'; import { useDebounceFn, @@ -39,6 +40,7 @@ import type { Get, Paths, SetNonNullable } from 'type-fest'; import { type PaginatedListInput, type SortableListInput } from '~/api'; import type { Order } from '~/api/schema/schema.graphql'; import { lowerCase, upperCase } from '~/common'; +import { usePersistedGridState } from '~/hooks/usePersistedGridState'; import { convertMuiFiltersToApi, FilterShape } from './convertMuiFiltersToApi'; type ListInput = SetNonNullable< @@ -99,6 +101,7 @@ export const useDataGridSource = < initialInput, keyArgs = defaultKeyArgs, apiRef: apiRefInput, + sessionStorageProps: sessionStateProps, }: { query: DocumentNode; variables: NoInfer; @@ -106,6 +109,10 @@ export const useDataGridSource = < initialInput?: Partial, 'page'>>; keyArgs?: string[]; apiRef?: MutableRefObject; + sessionStorageProps: { + key: string; + defaultValue: GridInitialStatePro; + }; }) => { const initialInputRef = useLatest(initialInput); // eslint-disable-next-line react-hooks/rules-of-hooks -- we'll assume this doesn't change between renders @@ -418,6 +425,12 @@ export const useDataGridSource = < } }); + const [savedGridState = {}, onStateChange] = usePersistedGridState({ + key: sessionStateProps.key, + apiRef: apiRef, + defaultValue: sessionStateProps.defaultValue, + }); + // DataGrid needs help when `rows` identity changes along with picking up // sorting responsibility ('client'). // Help it out by asking it to sort (again?) when we give it a different, @@ -433,10 +446,12 @@ export const useDataGridSource = < rows, loading, rowCount: total, + initialState: savedGridState, sortModel: view.sortModel, filterModel: view.filterModel, hideFooterPagination: true, onFetchRows: onFetchRows.run, + onStateChange, onSortModelChange, onFilterModelChange, paginationMode: total != null ? 'server' : 'client', // Not used, but prevents row count warning. diff --git a/src/scenes/Dashboard/ProgressReportsWidget/ProgressReportsExpandedGrid.tsx b/src/scenes/Dashboard/ProgressReportsWidget/ProgressReportsExpandedGrid.tsx index 2ebdd9242..816a8a97d 100644 --- a/src/scenes/Dashboard/ProgressReportsWidget/ProgressReportsExpandedGrid.tsx +++ b/src/scenes/Dashboard/ProgressReportsWidget/ProgressReportsExpandedGrid.tsx @@ -7,11 +7,8 @@ import { GridToolbarFilterButton, useGridApiRef, } from '@mui/x-data-grid-pro'; -import { GridInitialStatePro } from '@mui/x-data-grid-pro/models/gridStatePro'; import { entries } from '@seedcompany/common'; -import { useDebounceFn } from 'ahooks'; -import { isEqual } from 'lodash'; -import { useEffect, useMemo, useRef } from 'react'; +import { useMemo } from 'react'; import { extendSx } from '~/common'; import { getInitialVisibility, @@ -21,7 +18,6 @@ import { Toolbar, useFilterToggle, } from '~/components/Grid'; -import { useSessionStorage } from '~/hooks/useSessionStorage'; import { CollapseAllButton, ExpandAllButton, @@ -120,12 +116,6 @@ export const ProgressReportsExpandedGrid = ( props: Omit ) => { const apiRef = useGridApiRef(); - const [savedGridState, setSavedGridState] = - useSessionStorage( - `progress-reports-grid-state`, - initialState - ); - const prevState = useRef(null); const { expanded, onMouseDown, onRowClick } = useExpandedSetup(); @@ -137,22 +127,6 @@ export const ProgressReportsExpandedGrid = ( }), [onMouseDown] ); - - const onStateChange = useDebounceFn( - () => { - const gridState = apiRef.current.exportState(); - if (!isEqual(gridState, prevState.current)) { - prevState.current = gridState; - setSavedGridState(gridState); - } - }, - { wait: 500, maxWait: 500 } - ); - - useEffect(() => { - apiRef.current.restoreState(savedGridState); - }, [savedGridState, apiRef]); - return ( expanded.has(params.id) ? 'auto' : COLLAPSED_ROW_HEIGHT } diff --git a/src/scenes/Dashboard/ProgressReportsWidget/ProgressReportsGrid.tsx b/src/scenes/Dashboard/ProgressReportsWidget/ProgressReportsGrid.tsx index aac337118..779fce86b 100644 --- a/src/scenes/Dashboard/ProgressReportsWidget/ProgressReportsGrid.tsx +++ b/src/scenes/Dashboard/ProgressReportsWidget/ProgressReportsGrid.tsx @@ -177,6 +177,7 @@ export interface ProgressReportsGridProps extends DataGridProps { export const ProgressReportsGrid = ({ quarter, + initialState = {}, ...props }: ProgressReportsGridProps) => { const source = useMemo(() => { @@ -210,9 +211,14 @@ export const ProgressReportsGrid = ({ }, } as const; }, [quarter]); + const [dataGridProps] = useDataGridSource({ ...source, apiRef: props.apiRef, + sessionStorageProps: { + key: 'progress-reports-grid', + defaultValue: initialState, + }, }); const slots = useMemo( diff --git a/src/scenes/Partners/Detail/Tabs/Engagements/PartnerDetailEngagements.tsx b/src/scenes/Partners/Detail/Tabs/Engagements/PartnerDetailEngagements.tsx index 794f73b9a..5fa88dcd8 100644 --- a/src/scenes/Partners/Detail/Tabs/Engagements/PartnerDetailEngagements.tsx +++ b/src/scenes/Partners/Detail/Tabs/Engagements/PartnerDetailEngagements.tsx @@ -2,10 +2,8 @@ import { DataGridPro as DataGrid, DataGridProProps as DataGridProps, } from '@mui/x-data-grid-pro'; -import { GridInitialStatePro } from '@mui/x-data-grid-pro/models/gridStatePro'; -import { useDebounceFn } from 'ahooks'; -import { isEqual, merge } from 'lodash'; -import { useEffect, useMemo, useRef } from 'react'; +import { merge } from 'lodash'; +import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { EngagementDataGridRowFragment as Engagement, @@ -21,17 +19,10 @@ import { useDataGridSource, } from '~/components/Grid'; import { TabPanelContent } from '~/components/Tabs'; -import { useSessionStorage } from '~/hooks/useSessionStorage'; import { PartnerDetailEngagementsDocument } from './PartnerDetailEngagements.graphql'; export const PartnerDetailEngagements = () => { const { partnerId = '' } = useParams(); - const [savedGridState, setSavedGridState] = - useSessionStorage( - `partners-engagements-grid-state-${partnerId}`, - EngagementInitialState - ); - const prevState = useRef(null); const [props] = useDataGridSource({ query: PartnerDetailEngagementsDocument, @@ -40,6 +31,10 @@ export const PartnerDetailEngagements = () => { initialInput: { sort: EngagementColumns[0]!.field, }, + sessionStorageProps: { + key: `partners-engagements-grid-state-${partnerId}`, + defaultValue: EngagementInitialState, + }, }); const slots = useMemo( @@ -54,21 +49,6 @@ export const PartnerDetailEngagements = () => { [props.slotProps] ); - const onStateChange = useDebounceFn( - () => { - const gridState = props.apiRef.current.exportState(); - if (!isEqual(gridState, prevState.current)) { - prevState.current = gridState; - setSavedGridState(gridState); - } - }, - { wait: 500, maxWait: 500 } - ); - - useEffect(() => { - props.apiRef.current.restoreState(savedGridState); - }, [savedGridState, props.apiRef]); - return ( @@ -77,10 +57,8 @@ export const PartnerDetailEngagements = () => { slots={slots} slotProps={slotProps} columns={EngagementColumns} - initialState={EngagementInitialState} headerFilters hideFooter - onStateChange={onStateChange.run} sx={[flexLayout, noHeaderFilterButtons, noFooter]} /> diff --git a/src/scenes/Partners/Detail/Tabs/Projects/PartnerDetailProjects.tsx b/src/scenes/Partners/Detail/Tabs/Projects/PartnerDetailProjects.tsx index 78eb80d09..475249b66 100644 --- a/src/scenes/Partners/Detail/Tabs/Projects/PartnerDetailProjects.tsx +++ b/src/scenes/Partners/Detail/Tabs/Projects/PartnerDetailProjects.tsx @@ -3,10 +3,8 @@ import { DataGridProProps as DataGridProps, GridColDef, } from '@mui/x-data-grid-pro'; -import { GridInitialStatePro } from '@mui/x-data-grid-pro/models/gridStatePro'; -import { useDebounceFn } from 'ahooks'; -import { isEqual, merge } from 'lodash'; -import { useEffect, useMemo, useRef } from 'react'; +import { merge } from 'lodash'; +import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { PartnerTypeLabels, PartnerTypeList } from '~/api/schema.graphql'; import { unmatchedIndexThrow } from '~/common'; @@ -25,7 +23,6 @@ import { ProjectToolbar, } from '~/components/ProjectDataGrid'; import { TabPanelContent } from '~/components/Tabs'; -import { useSessionStorage } from '~/hooks/useSessionStorage'; import { PartnerProjectDataGridRowFragment as PartnerProject, PartnerProjectsDocument, @@ -33,12 +30,6 @@ import { export const PartnerDetailProjects = () => { const { partnerId = '' } = useParams(); - const [savedGridState, setSavedGridState] = - useSessionStorage( - `partners-projects-grid-state-${partnerId}`, - ProjectInitialState - ); - const prevState = useRef(null); const [props] = useDataGridSource({ query: PartnerProjectsDocument, variables: { partnerId }, @@ -46,6 +37,10 @@ export const PartnerDetailProjects = () => { initialInput: { sort: 'name', }, + sessionStorageProps: { + key: `partners-projects-grid-state-${partnerId}`, + defaultValue: ProjectInitialState, + }, }); const slots = useMemo( @@ -60,21 +55,6 @@ export const PartnerDetailProjects = () => { [props.slotProps] ); - const onStateChange = useDebounceFn( - () => { - const gridState = props.apiRef.current.exportState(); - if (!isEqual(gridState, prevState.current)) { - prevState.current = gridState; - setSavedGridState(gridState); - } - }, - { wait: 500, maxWait: 500 } - ); - - useEffect(() => { - props.apiRef.current.restoreState(savedGridState); - }, [savedGridState, props.apiRef]); - return ( @@ -83,10 +63,8 @@ export const PartnerDetailProjects = () => { slots={slots} slotProps={slotProps} columns={PartnerProjectColumns} - initialState={ProjectInitialState} headerFilters hideFooter - onStateChange={onStateChange.run} sx={[flexLayout, noHeaderFilterButtons, noFooter]} /> diff --git a/src/scenes/Projects/List/EngagementsPanel.tsx b/src/scenes/Projects/List/EngagementsPanel.tsx index d2ac90250..6644e28e3 100644 --- a/src/scenes/Projects/List/EngagementsPanel.tsx +++ b/src/scenes/Projects/List/EngagementsPanel.tsx @@ -2,10 +2,8 @@ import { DataGridPro as DataGrid, DataGridProProps as DataGridProps, } from '@mui/x-data-grid-pro'; -import { GridInitialStatePro } from '@mui/x-data-grid-pro/models/gridStatePro'; -import { useDebounceFn } from 'ahooks'; -import { isEqual, merge } from 'lodash'; -import { useEffect, useMemo, useRef } from 'react'; +import { merge } from 'lodash'; +import { useMemo } from 'react'; import { EngagementDataGridRowFragment as Engagement, EngagementColumns, @@ -19,17 +17,9 @@ import { noHeaderFilterButtons, useDataGridSource, } from '~/components/Grid'; -import { useSessionStorage } from '~/hooks/useSessionStorage'; import { EngagementListDocument } from './EngagementList.graphql'; export const EngagementsPanel = () => { - const [savedGridState, setSavedGridState] = - useSessionStorage( - 'engagements-grid-state', - EngagementInitialState - ); - const prevState = useRef(null); - const [dataGridProps] = useDataGridSource({ query: EngagementListDocument, variables: {}, @@ -37,6 +27,10 @@ export const EngagementsPanel = () => { initialInput: { sort: EngagementColumns[0]!.field, }, + sessionStorageProps: { + key: 'engagements-grid', + defaultValue: EngagementInitialState, + }, }); const slots = useMemo( @@ -52,21 +46,6 @@ export const EngagementsPanel = () => { [dataGridProps.slotProps] ); - const onStateChange = useDebounceFn( - () => { - const gridState = dataGridProps.apiRef.current.exportState(); - if (!isEqual(gridState, prevState.current)) { - prevState.current = gridState; - setSavedGridState(gridState); - } - }, - { wait: 500, maxWait: 500 } - ); - - useEffect(() => { - dataGridProps.apiRef.current.restoreState(savedGridState); - }, [savedGridState, dataGridProps.apiRef]); - return ( {...DefaultDataGridStyles} @@ -74,10 +53,8 @@ export const EngagementsPanel = () => { slots={slots} slotProps={slotProps} columns={EngagementColumns} - initialState={savedGridState} headerFilters hideFooter - onStateChange={onStateChange.run} sx={[flexLayout, noHeaderFilterButtons, noFooter]} /> ); diff --git a/src/scenes/Projects/List/ProjectsPanel.tsx b/src/scenes/Projects/List/ProjectsPanel.tsx index 08cc29d0c..ab9a111e0 100644 --- a/src/scenes/Projects/List/ProjectsPanel.tsx +++ b/src/scenes/Projects/List/ProjectsPanel.tsx @@ -2,10 +2,8 @@ import { DataGridPro as DataGrid, DataGridProProps as DataGridProps, } from '@mui/x-data-grid-pro'; -import { GridInitialStatePro } from '@mui/x-data-grid-pro/models/gridStatePro'; -import { useDebounceFn } from 'ahooks'; -import { isEqual, merge } from 'lodash'; -import { useEffect, useMemo, useRef } from 'react'; +import { merge } from 'lodash'; +import { useMemo } from 'react'; import { DefaultDataGridStyles, flexLayout, @@ -19,17 +17,9 @@ import { ProjectInitialState, ProjectToolbar, } from '~/components/ProjectDataGrid'; -import { useSessionStorage } from '~/hooks/useSessionStorage'; import { ProjectListDocument } from './ProjectList.graphql'; export const ProjectsPanel = () => { - const [savedGridState, setSavedGridState] = - useSessionStorage( - 'projects-grid-state', - ProjectInitialState - ); - const prevState = useRef(null); - const [dataGridProps] = useDataGridSource({ query: ProjectListDocument, variables: {}, @@ -37,6 +27,10 @@ export const ProjectsPanel = () => { initialInput: { sort: 'name', }, + sessionStorageProps: { + key: 'projects-grid', + defaultValue: ProjectInitialState, + }, }); const slots = useMemo( @@ -52,21 +46,6 @@ export const ProjectsPanel = () => { [dataGridProps.slotProps] ); - const onStateChange = useDebounceFn( - () => { - const gridState = dataGridProps.apiRef.current.exportState(); - if (!isEqual(gridState, prevState.current)) { - prevState.current = gridState; - setSavedGridState(gridState); - } - }, - { wait: 500, maxWait: 500 } - ); - - useEffect(() => { - dataGridProps.apiRef.current.restoreState(savedGridState); - }, [savedGridState, dataGridProps.apiRef]); - return ( {...DefaultDataGridStyles} @@ -74,10 +53,8 @@ export const ProjectsPanel = () => { slots={slots} slotProps={slotProps} columns={ProjectColumns} - initialState={savedGridState} headerFilters hideFooter - onStateChange={onStateChange.run} sx={[flexLayout, noHeaderFilterButtons, noFooter]} /> );