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:
@ -35,11 +35,12 @@ export function CompanyTable() {
|
||||
return (
|
||||
<>
|
||||
<GenericEntityTableData
|
||||
objectName="company"
|
||||
getRequestResultKey="companies"
|
||||
useGetRequest={useGetCompaniesQuery}
|
||||
orderBy={orderBy}
|
||||
whereFilters={whereFilters}
|
||||
viewFields={companyViewFields}
|
||||
viewFieldDefinitions={companyViewFields}
|
||||
filterDefinitionArray={companiesFilters}
|
||||
/>
|
||||
<EntityTable
|
||||
|
||||
@ -1,11 +1,30 @@
|
||||
import { companyViewFields } from '@/companies/constants/companyViewFields';
|
||||
import { useEffect } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
||||
import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState';
|
||||
import { viewFieldsFamilyState } from '@/ui/table/states/viewFieldsState';
|
||||
|
||||
import { companyViewFields } from '../../constants/companyViewFields';
|
||||
|
||||
import { mockedCompaniesData } from './companies-mock-data';
|
||||
|
||||
export function CompanyTableMockData() {
|
||||
const setEntityTableDimensions = useSetRecoilState(
|
||||
entityTableDimensionsState,
|
||||
);
|
||||
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
|
||||
const setEntityTableData = useSetEntityTableData();
|
||||
|
||||
setEntityTableData(mockedCompaniesData, companyViewFields, []);
|
||||
setEntityTableData(mockedCompaniesData, []);
|
||||
|
||||
useEffect(() => {
|
||||
setViewFields(companyViewFields);
|
||||
setEntityTableDimensions((prevState) => ({
|
||||
...prevState,
|
||||
numberOfColumns: companyViewFields.length,
|
||||
}));
|
||||
}, [setEntityTableDimensions, setViewFields]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -36,11 +36,12 @@ export function PeopleTable() {
|
||||
return (
|
||||
<>
|
||||
<GenericEntityTableData
|
||||
objectName="person"
|
||||
getRequestResultKey="people"
|
||||
useGetRequest={useGetPeopleQuery}
|
||||
orderBy={orderBy}
|
||||
whereFilters={whereFilters}
|
||||
viewFields={peopleViewFields}
|
||||
viewFieldDefinitions={peopleViewFields}
|
||||
filterDefinitionArray={peopleFilters}
|
||||
/>
|
||||
<EntityTable
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
81
front/src/modules/ui/table/hooks/useLoadView.ts
Normal file
81
front/src/modules/ui/table/hooks/useLoadView.ts
Normal 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) ?? ''],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -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);
|
||||
},
|
||||
[],
|
||||
|
||||
9
front/src/modules/views/queries/create.ts
Normal file
9
front/src/modules/views/queries/create.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_VIEW_FIELDS = gql`
|
||||
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
|
||||
createManyViewField(data: $data) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,8 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_VIEW_FIELDS = gql`
|
||||
query GetViewFields($where: ViewFieldWhereInput) {
|
||||
viewFields: findManyViewField(where: $where) {
|
||||
query GetViewFields(
|
||||
$where: ViewFieldWhereInput
|
||||
$orderBy: [ViewFieldOrderByWithRelationInput!]
|
||||
) {
|
||||
viewFields: findManyViewField(where: $where, orderBy: $orderBy) {
|
||||
id
|
||||
fieldName
|
||||
isVisible
|
||||
|
||||
Reference in New Issue
Block a user