feat: persist resized column widths (#1017)

* feat: persist resized column widths

Closes #981

* test: mock company and person view fields
This commit is contained in:
Thaïs
2023-08-02 20:48:14 +02:00
committed by GitHub
parent 552fb2378b
commit 3807d62aeb
18 changed files with 345 additions and 51 deletions

View File

@ -1,9 +1,10 @@
import * as React from 'react';
import { useCallback, useRef } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
import { useUpdateViewFieldMutation } from '~/generated/graphql';
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
@ -102,8 +103,11 @@ export function EntityTable<SortField>({
useUpdateEntityMutation,
}: OwnProps<SortField>) {
const viewFields = useRecoilValue(viewFieldsFamilyState);
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
const tableBodyRef = React.useRef<HTMLDivElement>(null);
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
const tableBodyRef = useRef<HTMLDivElement>(null);
useMapKeyboardToSoftFocus();
@ -116,6 +120,25 @@ export function EntityTable<SortField>({
},
});
const handleColumnResize = useCallback(
(resizedFieldId: string, width: number) => {
setViewFields((previousViewFields) =>
previousViewFields.map((viewField) =>
viewField.id === resizedFieldId
? { ...viewField, columnSize: width }
: viewField,
),
);
updateViewFieldMutation({
variables: {
data: { sizeInPx: width },
where: { id: resizedFieldId },
},
});
},
[setViewFields, updateViewFieldMutation],
);
return (
<EntityUpdateMutationHookContext.Provider value={useUpdateEntityMutation}>
<StyledTableWithHeader>
@ -129,7 +152,10 @@ export function EntityTable<SortField>({
<StyledTableWrapper>
{viewFields.length > 0 && (
<StyledTable>
<EntityTableHeader viewFields={viewFields} />
<EntityTableHeader
onColumnResize={handleColumnResize}
viewFields={viewFields}
/>
<EntityTableBody />
</StyledTable>
)}

View File

@ -1,7 +1,10 @@
import { PointerEvent, useCallback, useState } from 'react';
import { PointerEvent, useCallback, useMemo, useState } from 'react';
import styled from '@emotion/styled';
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '../types/ViewField';
import { ColumnHead } from './ColumnHead';
import { SelectAllCheckbox } from './SelectAllCheckbox';
@ -40,18 +43,22 @@ const StyledResizeHandler = styled.div`
`;
type OwnProps = {
onColumnResize: (resizedFieldId: string, width: number) => void;
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
};
export function EntityTableHeader({ viewFields }: OwnProps) {
const initialColumnWidths = viewFields.reduce<Record<string, number>>(
(result, viewField) => ({
...result,
[viewField.id]: viewField.columnSize,
}),
{},
export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) {
const columnWidths = useMemo(
() =>
viewFields.reduce<Record<string, number>>(
(result, viewField) => ({
...result,
[viewField.id]: viewField.columnSize,
}),
{},
),
[viewFields],
);
const [columnWidths, setColumnWidths] = useState(initialColumnWidths);
const [isResizing, setIsResizing] = useState(false);
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
number | null
@ -82,16 +89,16 @@ export function EntityTableHeader({ viewFields }: OwnProps) {
setIsResizing(false);
if (!resizedFieldId) return;
const newColumnWidths = {
...columnWidths,
[resizedFieldId]: Math.max(
columnWidths[resizedFieldId] + offset,
COLUMN_MIN_WIDTH,
),
};
setColumnWidths(newColumnWidths);
const nextWidth = Math.round(
Math.max(columnWidths[resizedFieldId] + offset, COLUMN_MIN_WIDTH),
);
if (nextWidth !== columnWidths[resizedFieldId]) {
onColumnResize(resizedFieldId, nextWidth);
}
setOffset(0);
}, [offset, setIsResizing, columnWidths, resizedFieldId]);
}, [resizedFieldId, columnWidths, offset, onColumnResize]);
return (
<thead>

View File

@ -1,3 +1,4 @@
import { defaultOrderBy } from '@/people/queries';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import {
@ -5,31 +6,35 @@ import {
ViewFieldMetadata,
} from '@/ui/table/types/ViewField';
import { defaultOrderBy } from '../../../people/queries';
import { useLoadView } from '../hooks/useLoadView';
export function GenericEntityTableData({
objectName,
useGetRequest,
getRequestResultKey,
orderBy = defaultOrderBy,
whereFilters,
viewFields,
viewFieldDefinitions,
filterDefinitionArray,
}: {
objectName: 'company' | 'person';
useGetRequest: any;
getRequestResultKey: string;
orderBy?: any;
whereFilters?: any;
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
filterDefinitionArray: FilterDefinition[];
}) {
const setEntityTableData = useSetEntityTableData();
useLoadView({ objectName, viewFieldDefinitions });
useGetRequest({
variables: { orderBy, where: whereFilters },
onCompleted: (data: any) => {
const entities = data[getRequestResultKey] ?? [];
setEntityTableData(entities, viewFields, filterDefinitionArray);
setEntityTableData(entities, filterDefinitionArray);
},
});

View File

@ -0,0 +1,81 @@
import { getOperationName } from '@apollo/client/utilities';
import { useSetRecoilState } from 'recoil';
import { GET_VIEW_FIELDS } from '@/views/queries/select';
import {
SortOrder,
useCreateViewFieldsMutation,
useGetViewFieldsQuery,
} from '~/generated/graphql';
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
import { viewFieldsFamilyState } from '../states/viewFieldsState';
import {
ViewFieldDefinition,
ViewFieldMetadata,
ViewFieldTextMetadata,
} from '../types/ViewField';
const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = {
type: 'text',
placeHolder: '',
fieldName: '',
};
export const useLoadView = ({
objectName,
viewFieldDefinitions,
}: {
objectName: 'company' | 'person';
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
}) => {
const setEntityTableDimensions = useSetRecoilState(
entityTableDimensionsState,
);
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
const [createViewFieldsMutation] = useCreateViewFieldsMutation();
useGetViewFieldsQuery({
variables: {
orderBy: { index: SortOrder.Asc },
where: { objectName: { equals: objectName } },
},
onCompleted: (data) => {
if (data.viewFields.length) {
setViewFields(
data.viewFields.map<ViewFieldDefinition<ViewFieldMetadata>>(
(viewField) => ({
...(viewFieldDefinitions.find(
({ columnLabel }) => viewField.fieldName === columnLabel,
) || { metadata: DEFAULT_VIEW_FIELD_METADATA }),
id: viewField.id,
columnLabel: viewField.fieldName,
columnOrder: viewField.index,
columnSize: viewField.sizeInPx,
}),
),
);
setEntityTableDimensions((prevState) => ({
...prevState,
numberOfColumns: data.viewFields.length,
}));
return;
}
// Populate if empty
createViewFieldsMutation({
variables: {
data: viewFieldDefinitions.map((viewFieldDefinition) => ({
fieldName: viewFieldDefinition.columnLabel,
index: viewFieldDefinition.columnOrder,
isVisible: true,
objectName,
sizeInPx: viewFieldDefinition.columnSize,
})),
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
});
},
});
};

View File

@ -8,11 +8,6 @@ import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEnti
import { TableContext } from '@/ui/table/states/TableContext';
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { viewFieldsFamilyState } from '@/ui/table/states/viewFieldsState';
import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/table/types/ViewField';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
export function useSetEntityTableData() {
@ -24,7 +19,6 @@ export function useSetEntityTableData() {
({ set, snapshot }) =>
<T extends { id: string }>(
newEntityArray: T[],
viewFields: ViewFieldDefinition<ViewFieldMetadata>[],
filters: FilterDefinition[],
) => {
for (const entity of newEntityArray) {
@ -49,15 +43,13 @@ export function useSetEntityTableData() {
resetTableRowSelection();
set(entityTableDimensionsState, {
numberOfColumns: viewFields.length,
set(entityTableDimensionsState, (prevState) => ({
...prevState,
numberOfRows: entityIds.length,
});
}));
set(availableFiltersScopedState(tableContextScopeId), filters);
set(viewFieldsFamilyState, viewFields);
set(isFetchingEntityTableDataState, false);
},
[],