Skip to content

Commit

Permalink
feat: persist resized column widths (twentyhq#1017)
Browse files Browse the repository at this point in the history
* feat: persist resized column widths

Closes twentyhq#981

* test: mock company and person view fields
  • Loading branch information
thaisguigon authored and AdityaPimpalkar committed Aug 3, 2023
1 parent b08bd6e commit 3aef22b
Show file tree
Hide file tree
Showing 18 changed files with 342 additions and 51 deletions.
59 changes: 57 additions & 2 deletions front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,7 @@ export type Mutation = {
challenge: LoginToken;
createEvent: Analytics;
createFavorites: Array<Favorite>;
createManyViewField: AffectedRows;
createOneActivity: Activity;
createOneComment: Comment;
createOneCompany: Company;
Expand Down Expand Up @@ -1015,6 +1016,9 @@ export type MutationCreateEventArgs = {

export type MutationCreateFavoritesArgs = {
data: Array<FavoriteCreateManyInput>;
}
export type MutationCreateManyViewFieldArgs = {
data: Array<ViewFieldCreateManyInput>;
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
};

Expand Down Expand Up @@ -2180,6 +2184,15 @@ export type ViewField = {
sizeInPx: Scalars['Int'];
};

export type ViewFieldCreateManyInput = {
fieldName: Scalars['String'];
id?: InputMaybe<Scalars['String']>;
index: Scalars['Int'];
isVisible: Scalars['Boolean'];
objectName: Scalars['String'];
sizeInPx: Scalars['Int'];
};

export type ViewFieldOrderByWithRelationInput = {
fieldName?: InputMaybe<SortOrder>;
id?: InputMaybe<SortOrder>;
Expand Down Expand Up @@ -2756,8 +2769,16 @@ export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }

export type DeleteUserAccountMutation = { __typename?: 'Mutation', deleteUserAccount: { __typename?: 'User', id: string } };

export type CreateViewFieldsMutationVariables = Exact<{
data: Array<ViewFieldCreateManyInput> | ViewFieldCreateManyInput;
}>;


export type CreateViewFieldsMutation = { __typename?: 'Mutation', createManyViewField: { __typename?: 'AffectedRows', count: number } };

export type GetViewFieldsQueryVariables = Exact<{
where?: InputMaybe<ViewFieldWhereInput>;
orderBy?: InputMaybe<Array<ViewFieldOrderByWithRelationInput> | ViewFieldOrderByWithRelationInput>;
}>;


Expand Down Expand Up @@ -5259,9 +5280,42 @@ export function useDeleteUserAccountMutation(baseOptions?: Apollo.MutationHookOp
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>;
export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
export const CreateViewFieldsDocument = gql`
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
createManyViewField(data: $data) {
count
}
}
`;
export type CreateViewFieldsMutationFn = Apollo.MutationFunction<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>;

/**
* __useCreateViewFieldsMutation__
*
* To run a mutation, you first call `useCreateViewFieldsMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateViewFieldsMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createViewFieldsMutation, { data, loading, error }] = useCreateViewFieldsMutation({
* variables: {
* data: // value for 'data'
* },
* });
*/
export function useCreateViewFieldsMutation(baseOptions?: Apollo.MutationHookOptions<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>(CreateViewFieldsDocument, options);
}
export type CreateViewFieldsMutationHookResult = ReturnType<typeof useCreateViewFieldsMutation>;
export type CreateViewFieldsMutationResult = Apollo.MutationResult<CreateViewFieldsMutation>;
export type CreateViewFieldsMutationOptions = Apollo.BaseMutationOptions<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>;
export const GetViewFieldsDocument = gql`
query GetViewFields($where: ViewFieldWhereInput) {
viewFields: findManyViewField(where: $where) {
query GetViewFields($where: ViewFieldWhereInput, $orderBy: [ViewFieldOrderByWithRelationInput!]) {
viewFields: findManyViewField(where: $where, orderBy: $orderBy) {
id
fieldName
isVisible
Expand All @@ -5284,6 +5338,7 @@ export const GetViewFieldsDocument = gql`
* const { data, loading, error } = useGetViewFieldsQuery({
* variables: {
* where: // value for 'where'
* orderBy: // value for 'orderBy'
* },
* });
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <></>;
}
3 changes: 2 additions & 1 deletion front/src/modules/people/table/components/PeopleTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 30 additions & 4 deletions front/src/modules/ui/table/components/EntityTable.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();

Expand All @@ -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>
Expand All @@ -129,7 +152,10 @@ export function EntityTable<SortField>({
<StyledTableWrapper>
{viewFields.length > 0 && (
<StyledTable>
<EntityTableHeader viewFields={viewFields} />
<EntityTableHeader
onColumnResize={handleColumnResize}
viewFields={viewFields}
/>
<EntityTableBody />
</StyledTable>
)}
Expand Down
45 changes: 26 additions & 19 deletions front/src/modules/ui/table/components/EntityTableHeader.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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>
Expand Down
13 changes: 9 additions & 4 deletions front/src/modules/ui/table/components/GenericEntityTableData.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,40 @@
import { defaultOrderBy } from '@/people/queries';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import {
ViewFieldDefinition,
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);
},
});

Expand Down
Loading

0 comments on commit 3aef22b

Please sign in to comment.