mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 09:02:11 +03:00
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:
parent
552fb2378b
commit
3807d62aeb
@ -885,6 +885,7 @@ export type Mutation = {
|
|||||||
allowImpersonation: WorkspaceMember;
|
allowImpersonation: WorkspaceMember;
|
||||||
challenge: LoginToken;
|
challenge: LoginToken;
|
||||||
createEvent: Analytics;
|
createEvent: Analytics;
|
||||||
|
createManyViewField: AffectedRows;
|
||||||
createOneActivity: Activity;
|
createOneActivity: Activity;
|
||||||
createOneComment: Comment;
|
createOneComment: Comment;
|
||||||
createOneCompany: Company;
|
createOneCompany: Company;
|
||||||
@ -934,6 +935,12 @@ export type MutationCreateEventArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationCreateManyViewFieldArgs = {
|
||||||
|
data: Array<ViewFieldCreateManyInput>;
|
||||||
|
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationCreateOneActivityArgs = {
|
export type MutationCreateOneActivityArgs = {
|
||||||
data: ActivityCreateInput;
|
data: ActivityCreateInput;
|
||||||
};
|
};
|
||||||
@ -2076,6 +2083,15 @@ export type ViewField = {
|
|||||||
sizeInPx: Scalars['Int'];
|
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 = {
|
export type ViewFieldOrderByWithRelationInput = {
|
||||||
fieldName?: InputMaybe<SortOrder>;
|
fieldName?: InputMaybe<SortOrder>;
|
||||||
id?: InputMaybe<SortOrder>;
|
id?: InputMaybe<SortOrder>;
|
||||||
@ -2640,8 +2656,16 @@ export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }
|
|||||||
|
|
||||||
export type DeleteUserAccountMutation = { __typename?: 'Mutation', deleteUserAccount: { __typename?: 'User', id: string } };
|
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<{
|
export type GetViewFieldsQueryVariables = Exact<{
|
||||||
where?: InputMaybe<ViewFieldWhereInput>;
|
where?: InputMaybe<ViewFieldWhereInput>;
|
||||||
|
orderBy?: InputMaybe<Array<ViewFieldOrderByWithRelationInput> | ViewFieldOrderByWithRelationInput>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
@ -5050,9 +5074,42 @@ export function useDeleteUserAccountMutation(baseOptions?: Apollo.MutationHookOp
|
|||||||
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
|
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
|
||||||
export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>;
|
export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>;
|
||||||
export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
|
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`
|
export const GetViewFieldsDocument = gql`
|
||||||
query GetViewFields($where: ViewFieldWhereInput) {
|
query GetViewFields($where: ViewFieldWhereInput, $orderBy: [ViewFieldOrderByWithRelationInput!]) {
|
||||||
viewFields: findManyViewField(where: $where) {
|
viewFields: findManyViewField(where: $where, orderBy: $orderBy) {
|
||||||
id
|
id
|
||||||
fieldName
|
fieldName
|
||||||
isVisible
|
isVisible
|
||||||
@ -5075,6 +5132,7 @@ export const GetViewFieldsDocument = gql`
|
|||||||
* const { data, loading, error } = useGetViewFieldsQuery({
|
* const { data, loading, error } = useGetViewFieldsQuery({
|
||||||
* variables: {
|
* variables: {
|
||||||
* where: // value for 'where'
|
* where: // value for 'where'
|
||||||
|
* orderBy: // value for 'orderBy'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
@ -35,11 +35,12 @@ export function CompanyTable() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GenericEntityTableData
|
<GenericEntityTableData
|
||||||
|
objectName="company"
|
||||||
getRequestResultKey="companies"
|
getRequestResultKey="companies"
|
||||||
useGetRequest={useGetCompaniesQuery}
|
useGetRequest={useGetCompaniesQuery}
|
||||||
orderBy={orderBy}
|
orderBy={orderBy}
|
||||||
whereFilters={whereFilters}
|
whereFilters={whereFilters}
|
||||||
viewFields={companyViewFields}
|
viewFieldDefinitions={companyViewFields}
|
||||||
filterDefinitionArray={companiesFilters}
|
filterDefinitionArray={companiesFilters}
|
||||||
/>
|
/>
|
||||||
<EntityTable
|
<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 { 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';
|
import { mockedCompaniesData } from './companies-mock-data';
|
||||||
|
|
||||||
export function CompanyTableMockData() {
|
export function CompanyTableMockData() {
|
||||||
|
const setEntityTableDimensions = useSetRecoilState(
|
||||||
|
entityTableDimensionsState,
|
||||||
|
);
|
||||||
|
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
|
||||||
const setEntityTableData = useSetEntityTableData();
|
const setEntityTableData = useSetEntityTableData();
|
||||||
|
|
||||||
setEntityTableData(mockedCompaniesData, companyViewFields, []);
|
setEntityTableData(mockedCompaniesData, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setViewFields(companyViewFields);
|
||||||
|
setEntityTableDimensions((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
numberOfColumns: companyViewFields.length,
|
||||||
|
}));
|
||||||
|
}, [setEntityTableDimensions, setViewFields]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
@ -36,11 +36,12 @@ export function PeopleTable() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GenericEntityTableData
|
<GenericEntityTableData
|
||||||
|
objectName="person"
|
||||||
getRequestResultKey="people"
|
getRequestResultKey="people"
|
||||||
useGetRequest={useGetPeopleQuery}
|
useGetRequest={useGetPeopleQuery}
|
||||||
orderBy={orderBy}
|
orderBy={orderBy}
|
||||||
whereFilters={whereFilters}
|
whereFilters={whereFilters}
|
||||||
viewFields={peopleViewFields}
|
viewFieldDefinitions={peopleViewFields}
|
||||||
filterDefinitionArray={peopleFilters}
|
filterDefinitionArray={peopleFilters}
|
||||||
/>
|
/>
|
||||||
<EntityTable
|
<EntityTable
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import * as React from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
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 { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||||
|
import { useUpdateViewFieldMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||||
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
||||||
@ -102,8 +103,11 @@ export function EntityTable<SortField>({
|
|||||||
useUpdateEntityMutation,
|
useUpdateEntityMutation,
|
||||||
}: OwnProps<SortField>) {
|
}: OwnProps<SortField>) {
|
||||||
const viewFields = useRecoilValue(viewFieldsFamilyState);
|
const viewFields = useRecoilValue(viewFieldsFamilyState);
|
||||||
|
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
|
||||||
|
|
||||||
const tableBodyRef = React.useRef<HTMLDivElement>(null);
|
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
|
||||||
|
|
||||||
|
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useMapKeyboardToSoftFocus();
|
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 (
|
return (
|
||||||
<EntityUpdateMutationHookContext.Provider value={useUpdateEntityMutation}>
|
<EntityUpdateMutationHookContext.Provider value={useUpdateEntityMutation}>
|
||||||
<StyledTableWithHeader>
|
<StyledTableWithHeader>
|
||||||
@ -129,7 +152,10 @@ export function EntityTable<SortField>({
|
|||||||
<StyledTableWrapper>
|
<StyledTableWrapper>
|
||||||
{viewFields.length > 0 && (
|
{viewFields.length > 0 && (
|
||||||
<StyledTable>
|
<StyledTable>
|
||||||
<EntityTableHeader viewFields={viewFields} />
|
<EntityTableHeader
|
||||||
|
onColumnResize={handleColumnResize}
|
||||||
|
viewFields={viewFields}
|
||||||
|
/>
|
||||||
<EntityTableBody />
|
<EntityTableBody />
|
||||||
</StyledTable>
|
</StyledTable>
|
||||||
)}
|
)}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { PointerEvent, useCallback, useState } from 'react';
|
import { PointerEvent, useCallback, useMemo, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField';
|
import type {
|
||||||
|
ViewFieldDefinition,
|
||||||
|
ViewFieldMetadata,
|
||||||
|
} from '../types/ViewField';
|
||||||
|
|
||||||
import { ColumnHead } from './ColumnHead';
|
import { ColumnHead } from './ColumnHead';
|
||||||
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
||||||
@ -40,18 +43,22 @@ const StyledResizeHandler = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
|
onColumnResize: (resizedFieldId: string, width: number) => void;
|
||||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
|
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EntityTableHeader({ viewFields }: OwnProps) {
|
export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) {
|
||||||
const initialColumnWidths = viewFields.reduce<Record<string, number>>(
|
const columnWidths = useMemo(
|
||||||
|
() =>
|
||||||
|
viewFields.reduce<Record<string, number>>(
|
||||||
(result, viewField) => ({
|
(result, viewField) => ({
|
||||||
...result,
|
...result,
|
||||||
[viewField.id]: viewField.columnSize,
|
[viewField.id]: viewField.columnSize,
|
||||||
}),
|
}),
|
||||||
{},
|
{},
|
||||||
|
),
|
||||||
|
[viewFields],
|
||||||
);
|
);
|
||||||
const [columnWidths, setColumnWidths] = useState(initialColumnWidths);
|
|
||||||
const [isResizing, setIsResizing] = useState(false);
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
|
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
|
||||||
number | null
|
number | null
|
||||||
@ -82,16 +89,16 @@ export function EntityTableHeader({ viewFields }: OwnProps) {
|
|||||||
setIsResizing(false);
|
setIsResizing(false);
|
||||||
if (!resizedFieldId) return;
|
if (!resizedFieldId) return;
|
||||||
|
|
||||||
const newColumnWidths = {
|
const nextWidth = Math.round(
|
||||||
...columnWidths,
|
Math.max(columnWidths[resizedFieldId] + offset, COLUMN_MIN_WIDTH),
|
||||||
[resizedFieldId]: Math.max(
|
);
|
||||||
columnWidths[resizedFieldId] + offset,
|
|
||||||
COLUMN_MIN_WIDTH,
|
if (nextWidth !== columnWidths[resizedFieldId]) {
|
||||||
),
|
onColumnResize(resizedFieldId, nextWidth);
|
||||||
};
|
}
|
||||||
setColumnWidths(newColumnWidths);
|
|
||||||
setOffset(0);
|
setOffset(0);
|
||||||
}, [offset, setIsResizing, columnWidths, resizedFieldId]);
|
}, [resizedFieldId, columnWidths, offset, onColumnResize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { defaultOrderBy } from '@/people/queries';
|
||||||
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||||
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
||||||
import {
|
import {
|
||||||
@ -5,31 +6,35 @@ import {
|
|||||||
ViewFieldMetadata,
|
ViewFieldMetadata,
|
||||||
} from '@/ui/table/types/ViewField';
|
} from '@/ui/table/types/ViewField';
|
||||||
|
|
||||||
import { defaultOrderBy } from '../../../people/queries';
|
import { useLoadView } from '../hooks/useLoadView';
|
||||||
|
|
||||||
export function GenericEntityTableData({
|
export function GenericEntityTableData({
|
||||||
|
objectName,
|
||||||
useGetRequest,
|
useGetRequest,
|
||||||
getRequestResultKey,
|
getRequestResultKey,
|
||||||
orderBy = defaultOrderBy,
|
orderBy = defaultOrderBy,
|
||||||
whereFilters,
|
whereFilters,
|
||||||
viewFields,
|
viewFieldDefinitions,
|
||||||
filterDefinitionArray,
|
filterDefinitionArray,
|
||||||
}: {
|
}: {
|
||||||
|
objectName: 'company' | 'person';
|
||||||
useGetRequest: any;
|
useGetRequest: any;
|
||||||
getRequestResultKey: string;
|
getRequestResultKey: string;
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
whereFilters?: any;
|
whereFilters?: any;
|
||||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
|
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||||
filterDefinitionArray: FilterDefinition[];
|
filterDefinitionArray: FilterDefinition[];
|
||||||
}) {
|
}) {
|
||||||
const setEntityTableData = useSetEntityTableData();
|
const setEntityTableData = useSetEntityTableData();
|
||||||
|
|
||||||
|
useLoadView({ objectName, viewFieldDefinitions });
|
||||||
|
|
||||||
useGetRequest({
|
useGetRequest({
|
||||||
variables: { orderBy, where: whereFilters },
|
variables: { orderBy, where: whereFilters },
|
||||||
onCompleted: (data: any) => {
|
onCompleted: (data: any) => {
|
||||||
const entities = data[getRequestResultKey] ?? [];
|
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 { TableContext } from '@/ui/table/states/TableContext';
|
||||||
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
|
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
|
||||||
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
|
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';
|
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||||
|
|
||||||
export function useSetEntityTableData() {
|
export function useSetEntityTableData() {
|
||||||
@ -24,7 +19,6 @@ export function useSetEntityTableData() {
|
|||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
<T extends { id: string }>(
|
<T extends { id: string }>(
|
||||||
newEntityArray: T[],
|
newEntityArray: T[],
|
||||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[],
|
|
||||||
filters: FilterDefinition[],
|
filters: FilterDefinition[],
|
||||||
) => {
|
) => {
|
||||||
for (const entity of newEntityArray) {
|
for (const entity of newEntityArray) {
|
||||||
@ -49,15 +43,13 @@ export function useSetEntityTableData() {
|
|||||||
|
|
||||||
resetTableRowSelection();
|
resetTableRowSelection();
|
||||||
|
|
||||||
set(entityTableDimensionsState, {
|
set(entityTableDimensionsState, (prevState) => ({
|
||||||
numberOfColumns: viewFields.length,
|
...prevState,
|
||||||
numberOfRows: entityIds.length,
|
numberOfRows: entityIds.length,
|
||||||
});
|
}));
|
||||||
|
|
||||||
set(availableFiltersScopedState(tableContextScopeId), filters);
|
set(availableFiltersScopedState(tableContextScopeId), filters);
|
||||||
|
|
||||||
set(viewFieldsFamilyState, viewFields);
|
|
||||||
|
|
||||||
set(isFetchingEntityTableDataState, false);
|
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';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const GET_VIEW_FIELDS = gql`
|
export const GET_VIEW_FIELDS = gql`
|
||||||
query GetViewFields($where: ViewFieldWhereInput) {
|
query GetViewFields(
|
||||||
viewFields: findManyViewField(where: $where) {
|
$where: ViewFieldWhereInput
|
||||||
|
$orderBy: [ViewFieldOrderByWithRelationInput!]
|
||||||
|
) {
|
||||||
|
viewFields: findManyViewField(where: $where, orderBy: $orderBy) {
|
||||||
id
|
id
|
||||||
fieldName
|
fieldName
|
||||||
isVisible
|
isVisible
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
SEARCH_USER_QUERY,
|
SEARCH_USER_QUERY,
|
||||||
} from '@/search/queries/search';
|
} from '@/search/queries/search';
|
||||||
import { GET_CURRENT_USER } from '@/users/queries';
|
import { GET_CURRENT_USER } from '@/users/queries';
|
||||||
|
import { GET_VIEW_FIELDS } from '@/views/queries/select';
|
||||||
import {
|
import {
|
||||||
GetCompaniesQuery,
|
GetCompaniesQuery,
|
||||||
GetPeopleQuery,
|
GetPeopleQuery,
|
||||||
@ -24,8 +25,11 @@ import {
|
|||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
import { mockedActivities } from './mock-data/activities';
|
import { mockedActivities } from './mock-data/activities';
|
||||||
import { mockedCompaniesData } from './mock-data/companies';
|
import {
|
||||||
import { mockedPeopleData } from './mock-data/people';
|
mockedCompaniesData,
|
||||||
|
mockedCompanyViewFields,
|
||||||
|
} from './mock-data/companies';
|
||||||
|
import { mockedPeopleData, mockedPersonViewFields } from './mock-data/people';
|
||||||
import { mockedPipelineProgressData } from './mock-data/pipeline-progress';
|
import { mockedPipelineProgressData } from './mock-data/pipeline-progress';
|
||||||
import { mockedPipelinesData } from './mock-data/pipelines';
|
import { mockedPipelinesData } from './mock-data/pipelines';
|
||||||
import { mockedUsersData } from './mock-data/users';
|
import { mockedUsersData } from './mock-data/users';
|
||||||
@ -206,4 +210,20 @@ export const graphqlMocks = [
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
graphql.query(getOperationName(GET_VIEW_FIELDS) ?? '', (req, res, ctx) => {
|
||||||
|
const {
|
||||||
|
where: {
|
||||||
|
objectName: { equals: objectName },
|
||||||
|
},
|
||||||
|
} = req.variables;
|
||||||
|
|
||||||
|
return res(
|
||||||
|
ctx.data({
|
||||||
|
viewFields:
|
||||||
|
objectName === 'company'
|
||||||
|
? mockedCompanyViewFields
|
||||||
|
: mockedPersonViewFields,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Company, User } from '../../generated/graphql';
|
import { companyViewFields } from '@/companies/constants/companyViewFields';
|
||||||
|
import { Company, User, ViewField } from '~/generated/graphql';
|
||||||
|
|
||||||
type MockedCompany = Pick<
|
type MockedCompany = Pick<
|
||||||
Company,
|
Company,
|
||||||
@ -118,3 +119,15 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const mockedCompanyViewFields = companyViewFields.map<ViewField>(
|
||||||
|
(viewFieldDefinition) => ({
|
||||||
|
__typename: 'ViewField',
|
||||||
|
fieldName: viewFieldDefinition.columnLabel,
|
||||||
|
id: viewFieldDefinition.id,
|
||||||
|
index: viewFieldDefinition.columnOrder,
|
||||||
|
isVisible: true,
|
||||||
|
objectName: 'company',
|
||||||
|
sizeInPx: viewFieldDefinition.columnSize,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Company, Person } from '~/generated/graphql';
|
import { peopleViewFields } from '@/people/constants/peopleViewFields';
|
||||||
|
import { Company, Person, ViewField } from '~/generated/graphql';
|
||||||
|
|
||||||
type RequiredAndNotNull<T> = {
|
type RequiredAndNotNull<T> = {
|
||||||
[P in keyof T]-?: Exclude<T[P], null | undefined>;
|
[P in keyof T]-?: Exclude<T[P], null | undefined>;
|
||||||
@ -116,3 +117,15 @@ export const mockedPeopleData: MockedPerson[] = [
|
|||||||
city: 'Paris',
|
city: 'Paris',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const mockedPersonViewFields = peopleViewFields.map<ViewField>(
|
||||||
|
(viewFieldDefinition) => ({
|
||||||
|
__typename: 'ViewField',
|
||||||
|
fieldName: viewFieldDefinition.columnLabel,
|
||||||
|
id: viewFieldDefinition.id,
|
||||||
|
index: viewFieldDefinition.columnOrder,
|
||||||
|
isVisible: true,
|
||||||
|
objectName: 'person',
|
||||||
|
sizeInPx: viewFieldDefinition.columnSize,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
@ -132,6 +132,7 @@ export class AbilityFactory {
|
|||||||
|
|
||||||
// ViewField
|
// ViewField
|
||||||
can(AbilityAction.Read, 'ViewField', { workspaceId: workspace.id });
|
can(AbilityAction.Read, 'ViewField', { workspaceId: workspace.id });
|
||||||
|
can(AbilityAction.Create, 'ViewField', { workspaceId: workspace.id });
|
||||||
can(AbilityAction.Update, 'ViewField', { workspaceId: workspace.id });
|
can(AbilityAction.Update, 'ViewField', { workspaceId: workspace.id });
|
||||||
|
|
||||||
return build();
|
return build();
|
||||||
|
@ -95,6 +95,7 @@ import {
|
|||||||
UpdateAttachmentAbilityHandler,
|
UpdateAttachmentAbilityHandler,
|
||||||
} from './handlers/attachment.ability-handler';
|
} from './handlers/attachment.ability-handler';
|
||||||
import {
|
import {
|
||||||
|
CreateViewFieldAbilityHandler,
|
||||||
ReadViewFieldAbilityHandler,
|
ReadViewFieldAbilityHandler,
|
||||||
UpdateViewFieldAbilityHandler,
|
UpdateViewFieldAbilityHandler,
|
||||||
} from './handlers/view-field.ability-handler';
|
} from './handlers/view-field.ability-handler';
|
||||||
@ -184,6 +185,7 @@ import {
|
|||||||
DeletePipelineProgressAbilityHandler,
|
DeletePipelineProgressAbilityHandler,
|
||||||
// ViewField
|
// ViewField
|
||||||
ReadViewFieldAbilityHandler,
|
ReadViewFieldAbilityHandler,
|
||||||
|
CreateViewFieldAbilityHandler,
|
||||||
UpdateViewFieldAbilityHandler,
|
UpdateViewFieldAbilityHandler,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
@ -268,6 +270,7 @@ import {
|
|||||||
DeletePipelineProgressAbilityHandler,
|
DeletePipelineProgressAbilityHandler,
|
||||||
// ViewField
|
// ViewField
|
||||||
ReadViewFieldAbilityHandler,
|
ReadViewFieldAbilityHandler,
|
||||||
|
CreateViewFieldAbilityHandler,
|
||||||
UpdateViewFieldAbilityHandler,
|
UpdateViewFieldAbilityHandler,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -28,6 +28,29 @@ export class ReadViewFieldAbilityHandler implements IAbilityHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CreateViewFieldAbilityHandler implements IAbilityHandler {
|
||||||
|
constructor(private readonly prismaService: PrismaService) {}
|
||||||
|
|
||||||
|
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||||
|
const gqlContext = GqlExecutionContext.create(context);
|
||||||
|
const args = gqlContext.getArgs();
|
||||||
|
|
||||||
|
const allowed = await relationAbilityChecker(
|
||||||
|
'ViewField',
|
||||||
|
ability,
|
||||||
|
this.prismaService.client,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!allowed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ability.can(AbilityAction.Create, 'ViewField');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UpdateViewFieldAbilityHandler implements IAbilityHandler {
|
export class UpdateViewFieldAbilityHandler implements IAbilityHandler {
|
||||||
constructor(private readonly prismaService: PrismaService) {}
|
constructor(private readonly prismaService: PrismaService) {}
|
||||||
|
@ -2,17 +2,21 @@ import { UseGuards } from '@nestjs/common';
|
|||||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { accessibleBy } from '@casl/prisma';
|
import { accessibleBy } from '@casl/prisma';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma, Workspace } from '@prisma/client';
|
||||||
|
|
||||||
import { AppAbility } from 'src/ability/ability.factory';
|
import { AppAbility } from 'src/ability/ability.factory';
|
||||||
import {
|
import {
|
||||||
|
CreateViewFieldAbilityHandler,
|
||||||
ReadViewFieldAbilityHandler,
|
ReadViewFieldAbilityHandler,
|
||||||
UpdateViewFieldAbilityHandler,
|
UpdateViewFieldAbilityHandler,
|
||||||
} from 'src/ability/handlers/view-field.ability-handler';
|
} from 'src/ability/handlers/view-field.ability-handler';
|
||||||
|
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
||||||
|
import { CreateManyViewFieldArgs } from 'src/core/@generated/view-field/create-many-view-field.args';
|
||||||
import { FindManyViewFieldArgs } from 'src/core/@generated/view-field/find-many-view-field.args';
|
import { FindManyViewFieldArgs } from 'src/core/@generated/view-field/find-many-view-field.args';
|
||||||
import { UpdateOneViewFieldArgs } from 'src/core/@generated/view-field/update-one-view-field.args';
|
import { UpdateOneViewFieldArgs } from 'src/core/@generated/view-field/update-one-view-field.args';
|
||||||
import { ViewField } from 'src/core/@generated/view-field/view-field.model';
|
import { ViewField } from 'src/core/@generated/view-field/view-field.model';
|
||||||
import { ViewFieldService } from 'src/core/view/services/view-field.service';
|
import { ViewFieldService } from 'src/core/view/services/view-field.service';
|
||||||
|
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||||
import {
|
import {
|
||||||
PrismaSelect,
|
PrismaSelect,
|
||||||
@ -27,6 +31,21 @@ import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
|||||||
export class ViewFieldResolver {
|
export class ViewFieldResolver {
|
||||||
constructor(private readonly viewFieldService: ViewFieldService) {}
|
constructor(private readonly viewFieldService: ViewFieldService) {}
|
||||||
|
|
||||||
|
@Mutation(() => AffectedRows)
|
||||||
|
@UseGuards(AbilityGuard)
|
||||||
|
@CheckAbilities(CreateViewFieldAbilityHandler)
|
||||||
|
async createManyViewField(
|
||||||
|
@Args() args: CreateManyViewFieldArgs,
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.viewFieldService.createMany({
|
||||||
|
data: args.data.map((dataItem) => ({
|
||||||
|
...dataItem,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Query(() => [ViewField])
|
@Query(() => [ViewField])
|
||||||
@UseGuards(AbilityGuard)
|
@UseGuards(AbilityGuard)
|
||||||
@CheckAbilities(ReadViewFieldAbilityHandler)
|
@CheckAbilities(ReadViewFieldAbilityHandler)
|
||||||
|
Loading…
Reference in New Issue
Block a user