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);
},
});