diff --git a/docs/docs/developer/additional/windows-wsl-setup copy.mdx b/docs/docs/developer/additional/windows-wsl-setup.mdx
similarity index 94%
rename from docs/docs/developer/additional/windows-wsl-setup copy.mdx
rename to docs/docs/developer/additional/windows-wsl-setup.mdx
index d6a3a9f61c..84e4cdbc16 100644
--- a/docs/docs/developer/additional/windows-wsl-setup copy.mdx
+++ b/docs/docs/developer/additional/windows-wsl-setup.mdx
@@ -22,7 +22,7 @@ Upon restart, a powershell window will open and install Ubuntu. This may take a
You will be prompted to create a username and password for your Ubuntu installation.
-
+
## Setup your developer environment
diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx
index 82a519fb7b..b5019d5fd2 100644
--- a/front/src/generated/graphql.tsx
+++ b/front/src/generated/graphql.tsx
@@ -846,6 +846,20 @@ export type EnumPipelineProgressableTypeFilter = {
notIn?: InputMaybe>;
};
+export type EnumViewSortDirectionFilter = {
+ equals?: InputMaybe;
+ in?: InputMaybe>;
+ not?: InputMaybe;
+ notIn?: InputMaybe>;
+};
+
+export type EnumViewTypeFilter = {
+ equals?: InputMaybe;
+ in?: InputMaybe>;
+ not?: InputMaybe;
+ notIn?: InputMaybe>;
+};
+
export enum FileFolder {
Attachment = 'Attachment',
PersonPicture = 'PersonPicture',
@@ -902,6 +916,7 @@ export type Mutation = {
challenge: LoginToken;
createEvent: Analytics;
createManyViewField: AffectedRows;
+ createManyViewSort: AffectedRows;
createOneActivity: Activity;
createOneComment: Comment;
createOneCompany: Company;
@@ -913,6 +928,7 @@ export type Mutation = {
deleteManyCompany: AffectedRows;
deleteManyPerson: AffectedRows;
deleteManyPipelineProgress: AffectedRows;
+ deleteManyViewSort: AffectedRows;
deleteUserAccount: User;
deleteWorkspaceMember: WorkspaceMember;
impersonate: Verify;
@@ -924,6 +940,7 @@ export type Mutation = {
updateOnePipelineProgress?: Maybe;
updateOnePipelineStage?: Maybe;
updateOneViewField: ViewField;
+ updateOneViewSort: ViewSort;
updateUser: User;
updateWorkspace: Workspace;
uploadAttachment: Scalars['String'];
@@ -959,6 +976,12 @@ export type MutationCreateManyViewFieldArgs = {
};
+export type MutationCreateManyViewSortArgs = {
+ data: Array;
+ skipDuplicates?: InputMaybe;
+};
+
+
export type MutationCreateOneActivityArgs = {
data: ActivityCreateInput;
};
@@ -1009,6 +1032,11 @@ export type MutationDeleteManyPipelineProgressArgs = {
};
+export type MutationDeleteManyViewSortArgs = {
+ where?: InputMaybe;
+};
+
+
export type MutationDeleteWorkspaceMemberArgs = {
where: WorkspaceMemberWhereUniqueInput;
};
@@ -1067,6 +1095,12 @@ export type MutationUpdateOneViewFieldArgs = {
};
+export type MutationUpdateOneViewSortArgs = {
+ data: ViewSortUpdateInput;
+ where: ViewSortWhereUniqueInput;
+};
+
+
export type MutationUpdateUserArgs = {
data: UserUpdateInput;
where: UserWhereUniqueInput;
@@ -1178,6 +1212,20 @@ export type NestedEnumPipelineProgressableTypeFilter = {
notIn?: InputMaybe>;
};
+export type NestedEnumViewSortDirectionFilter = {
+ equals?: InputMaybe;
+ in?: InputMaybe>;
+ not?: InputMaybe;
+ notIn?: InputMaybe>;
+};
+
+export type NestedEnumViewTypeFilter = {
+ equals?: InputMaybe;
+ in?: InputMaybe>;
+ not?: InputMaybe;
+ notIn?: InputMaybe>;
+};
+
export type NestedIntFilter = {
equals?: InputMaybe;
gt?: InputMaybe;
@@ -1766,6 +1814,7 @@ export type Query = {
findManyPipelineStage: Array;
findManyUser: Array;
findManyViewField: Array;
+ findManyViewSort: Array;
findManyWorkspaceMember: Array;
findUniqueCompany: Company;
findUniquePerson: Person;
@@ -1863,6 +1912,16 @@ export type QueryFindManyViewFieldArgs = {
};
+export type QueryFindManyViewSortArgs = {
+ cursor?: InputMaybe;
+ distinct?: InputMaybe>;
+ orderBy?: InputMaybe>;
+ skip?: InputMaybe;
+ take?: InputMaybe;
+ where?: InputMaybe;
+};
+
+
export type QueryFindManyWorkspaceMemberArgs = {
cursor?: InputMaybe;
distinct?: InputMaybe>;
@@ -2161,6 +2220,20 @@ export type Verify = {
user: User;
};
+export type View = {
+ __typename?: 'View';
+ fields?: Maybe>;
+ id: Scalars['ID'];
+ name: Scalars['String'];
+ objectId: Scalars['String'];
+ sorts?: Maybe>;
+ type: ViewType;
+};
+
+export type ViewCreateNestedOneWithoutFieldsInput = {
+ connect?: InputMaybe;
+};
+
export type ViewField = {
__typename?: 'ViewField';
fieldName: Scalars['String'];
@@ -2169,6 +2242,8 @@ export type ViewField = {
isVisible: Scalars['Boolean'];
objectName: Scalars['String'];
sizeInPx: Scalars['Int'];
+ view?: Maybe;
+ viewId?: Maybe;
};
export type ViewFieldCreateInput = {
@@ -2178,6 +2253,7 @@ export type ViewFieldCreateInput = {
isVisible: Scalars['Boolean'];
objectName: Scalars['String'];
sizeInPx: Scalars['Int'];
+ view?: InputMaybe;
};
export type ViewFieldCreateManyInput = {
@@ -2187,6 +2263,17 @@ export type ViewFieldCreateManyInput = {
isVisible: Scalars['Boolean'];
objectName: Scalars['String'];
sizeInPx: Scalars['Int'];
+ viewId?: InputMaybe;
+};
+
+export type ViewFieldListRelationFilter = {
+ every?: InputMaybe;
+ none?: InputMaybe;
+ some?: InputMaybe;
+};
+
+export type ViewFieldOrderByRelationAggregateInput = {
+ _count?: InputMaybe;
};
export type ViewFieldOrderByWithRelationInput = {
@@ -2196,6 +2283,8 @@ export type ViewFieldOrderByWithRelationInput = {
isVisible?: InputMaybe;
objectName?: InputMaybe;
sizeInPx?: InputMaybe;
+ view?: InputMaybe;
+ viewId?: InputMaybe;
};
export enum ViewFieldScalarFieldEnum {
@@ -2205,6 +2294,7 @@ export enum ViewFieldScalarFieldEnum {
IsVisible = 'isVisible',
ObjectName = 'objectName',
SizeInPx = 'sizeInPx',
+ ViewId = 'viewId',
WorkspaceId = 'workspaceId'
}
@@ -2215,6 +2305,7 @@ export type ViewFieldUpdateInput = {
isVisible?: InputMaybe;
objectName?: InputMaybe;
sizeInPx?: InputMaybe;
+ view?: InputMaybe;
};
export type ViewFieldUpdateManyWithoutWorkspaceNestedInput = {
@@ -2233,10 +2324,156 @@ export type ViewFieldWhereInput = {
isVisible?: InputMaybe;
objectName?: InputMaybe;
sizeInPx?: InputMaybe;
+ view?: InputMaybe;
+ viewId?: InputMaybe;
};
export type ViewFieldWhereUniqueInput = {
id?: InputMaybe;
+ workspaceId_viewId_objectName_fieldName?: InputMaybe;
+};
+
+export type ViewFieldWorkspaceIdViewIdObjectNameFieldNameCompoundUniqueInput = {
+ fieldName: Scalars['String'];
+ objectName: Scalars['String'];
+ viewId: Scalars['String'];
+};
+
+export type ViewOrderByWithRelationInput = {
+ fields?: InputMaybe;
+ id?: InputMaybe;
+ name?: InputMaybe;
+ objectId?: InputMaybe;
+ sorts?: InputMaybe;
+ type?: InputMaybe;
+};
+
+export type ViewRelationFilter = {
+ is?: InputMaybe;
+ isNot?: InputMaybe;
+};
+
+export type ViewSort = {
+ __typename?: 'ViewSort';
+ direction: ViewSortDirection;
+ key: Scalars['String'];
+ name: Scalars['String'];
+ view: View;
+ viewId: Scalars['String'];
+};
+
+export type ViewSortCreateManyInput = {
+ direction: ViewSortDirection;
+ key: Scalars['String'];
+ name: Scalars['String'];
+ viewId: Scalars['String'];
+};
+
+export enum ViewSortDirection {
+ Asc = 'asc',
+ Desc = 'desc'
+}
+
+export type ViewSortListRelationFilter = {
+ every?: InputMaybe;
+ none?: InputMaybe;
+ some?: InputMaybe;
+};
+
+export type ViewSortOrderByRelationAggregateInput = {
+ _count?: InputMaybe;
+};
+
+export type ViewSortOrderByWithRelationInput = {
+ direction?: InputMaybe;
+ key?: InputMaybe;
+ name?: InputMaybe;
+ view?: InputMaybe;
+ viewId?: InputMaybe;
+};
+
+export enum ViewSortScalarFieldEnum {
+ Direction = 'direction',
+ Key = 'key',
+ Name = 'name',
+ ViewId = 'viewId',
+ WorkspaceId = 'workspaceId'
+}
+
+export type ViewSortUpdateInput = {
+ direction?: InputMaybe;
+ key?: InputMaybe;
+ name?: InputMaybe;
+ view?: InputMaybe;
+};
+
+export type ViewSortUpdateManyWithoutWorkspaceNestedInput = {
+ connect?: InputMaybe>;
+ disconnect?: InputMaybe>;
+ set?: InputMaybe>;
+};
+
+export type ViewSortViewIdKeyCompoundUniqueInput = {
+ key: Scalars['String'];
+ viewId: Scalars['String'];
+};
+
+export type ViewSortWhereInput = {
+ AND?: InputMaybe>;
+ NOT?: InputMaybe>;
+ OR?: InputMaybe>;
+ direction?: InputMaybe;
+ key?: InputMaybe;
+ name?: InputMaybe;
+ view?: InputMaybe;
+ viewId?: InputMaybe;
+};
+
+export type ViewSortWhereUniqueInput = {
+ viewId_key?: InputMaybe;
+};
+
+export enum ViewType {
+ Pipeline = 'Pipeline',
+ Table = 'Table'
+}
+
+export type ViewUpdateManyWithoutWorkspaceNestedInput = {
+ connect?: InputMaybe>;
+ disconnect?: InputMaybe>;
+ set?: InputMaybe>;
+};
+
+export type ViewUpdateOneRequiredWithoutSortsNestedInput = {
+ connect?: InputMaybe;
+};
+
+export type ViewUpdateOneWithoutFieldsNestedInput = {
+ connect?: InputMaybe;
+ disconnect?: InputMaybe;
+};
+
+export type ViewWhereInput = {
+ AND?: InputMaybe>;
+ NOT?: InputMaybe>;
+ OR?: InputMaybe>;
+ fields?: InputMaybe;
+ id?: InputMaybe;
+ name?: InputMaybe;
+ objectId?: InputMaybe;
+ sorts?: InputMaybe;
+ type?: InputMaybe;
+};
+
+export type ViewWhereUniqueInput = {
+ id?: InputMaybe;
+ workspaceId_type_objectId_name?: InputMaybe;
+};
+
+export type ViewWorkspaceIdTypeObjectIdNameCompoundUniqueInput = {
+ name: Scalars['String'];
+ objectId: Scalars['String'];
+ type: ViewType;
};
export type Workspace = {
@@ -2258,6 +2495,8 @@ export type Workspace = {
pipelines?: Maybe>;
updatedAt: Scalars['DateTime'];
viewFields?: Maybe>;
+ viewSorts?: Maybe>;
+ views?: Maybe>;
workspaceMember?: Maybe>;
};
@@ -2337,6 +2576,8 @@ export type WorkspaceUpdateInput = {
pipelines?: InputMaybe;
updatedAt?: InputMaybe;
viewFields?: InputMaybe;
+ viewSorts?: InputMaybe;
+ views?: InputMaybe;
workspaceMember?: InputMaybe;
};
@@ -2503,12 +2744,14 @@ export type UpdateOneCompanyMutationVariables = Exact<{
export type UpdateOneCompanyMutation = { __typename?: 'Mutation', updateOneCompany?: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } | null } | null };
+export type InsertCompanyFragmentFragment = { __typename?: 'Company', domainName: string, address: string, id: string, name: string, createdAt: string };
+
export type InsertOneCompanyMutationVariables = Exact<{
data: CompanyCreateInput;
}>;
-export type InsertOneCompanyMutation = { __typename?: 'Mutation', createOneCompany: { __typename?: 'Company', address: string, createdAt: string, domainName: string, linkedinUrl?: string | null, employees?: number | null, id: string, name: string } };
+export type InsertOneCompanyMutation = { __typename?: 'Mutation', createOneCompany: { __typename?: 'Company', domainName: string, address: string, id: string, name: string, createdAt: string } };
export type DeleteManyCompaniesMutationVariables = Exact<{
ids?: InputMaybe | Scalars['String']>;
@@ -2590,12 +2833,14 @@ export type UpdateOnePersonMutationVariables = Exact<{
export type UpdateOnePersonMutation = { __typename?: 'Mutation', updateOnePerson?: { __typename?: 'Person', id: string, city?: string | null, email?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, xUrl?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, phone?: string | null, createdAt: string, company?: { __typename?: 'Company', domainName: string, name: string, id: string } | null } | null };
+export type InsertPersonFragmentFragment = { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, createdAt: string };
+
export type InsertOnePersonMutationVariables = Exact<{
data: PersonCreateInput;
}>;
-export type InsertOnePersonMutation = { __typename?: 'Mutation', createOnePerson: { __typename?: 'Person', id: string, city?: string | null, email?: string | null, firstName?: string | null, lastName?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, xUrl?: string | null, displayName: string, phone?: string | null, createdAt: string, company?: { __typename?: 'Company', domainName: string, name: string, id: string } | null } };
+export type InsertOnePersonMutation = { __typename?: 'Mutation', createOnePerson: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, createdAt: string } };
export type DeleteManyPersonMutationVariables = Exact<{
ids?: InputMaybe | Scalars['String']>;
@@ -2774,6 +3019,20 @@ export type CreateViewFieldsMutationVariables = Exact<{
export type CreateViewFieldsMutation = { __typename?: 'Mutation', createManyViewField: { __typename?: 'AffectedRows', count: number } };
+export type CreateViewSortsMutationVariables = Exact<{
+ data: Array | ViewSortCreateManyInput;
+}>;
+
+
+export type CreateViewSortsMutation = { __typename?: 'Mutation', createManyViewSort: { __typename?: 'AffectedRows', count: number } };
+
+export type DeleteViewSortsMutationVariables = Exact<{
+ where: ViewSortWhereInput;
+}>;
+
+
+export type DeleteViewSortsMutation = { __typename?: 'Mutation', deleteManyViewSort: { __typename?: 'AffectedRows', count: number } };
+
export type GetViewFieldsQueryVariables = Exact<{
where?: InputMaybe;
orderBy?: InputMaybe | ViewFieldOrderByWithRelationInput>;
@@ -2782,6 +3041,13 @@ export type GetViewFieldsQueryVariables = Exact<{
export type GetViewFieldsQuery = { __typename?: 'Query', viewFields: Array<{ __typename?: 'ViewField', id: string, fieldName: string, isVisible: boolean, sizeInPx: number, index: number }> };
+export type GetViewSortsQueryVariables = Exact<{
+ where?: InputMaybe;
+}>;
+
+
+export type GetViewSortsQuery = { __typename?: 'Query', viewSorts: Array<{ __typename?: 'ViewSort', direction: ViewSortDirection, key: string, name: string }> };
+
export type UpdateViewFieldMutationVariables = Exact<{
data: ViewFieldUpdateInput;
where: ViewFieldWhereUniqueInput;
@@ -2790,6 +3056,14 @@ export type UpdateViewFieldMutationVariables = Exact<{
export type UpdateViewFieldMutation = { __typename?: 'Mutation', updateOneViewField: { __typename?: 'ViewField', id: string, fieldName: string, isVisible: boolean, sizeInPx: number, index: number } };
+export type UpdateViewSortMutationVariables = Exact<{
+ data: ViewSortUpdateInput;
+ where: ViewSortWhereUniqueInput;
+}>;
+
+
+export type UpdateViewSortMutation = { __typename?: 'Mutation', viewSort: { __typename?: 'ViewSort', direction: ViewSortDirection, key: string, name: string } };
+
export type GetWorkspaceMembersQueryVariables = Exact<{ [key: string]: never; }>;
@@ -2849,6 +3123,24 @@ export const ActivityUpdatePartsFragmentDoc = gql`
}
}
`;
+export const InsertCompanyFragmentFragmentDoc = gql`
+ fragment InsertCompanyFragment on Company {
+ domainName
+ address
+ id
+ name
+ createdAt
+}
+ `;
+export const InsertPersonFragmentFragmentDoc = gql`
+ fragment InsertPersonFragment on Person {
+ id
+ firstName
+ lastName
+ displayName
+ createdAt
+}
+ `;
export const CreateCommentDocument = gql`
mutation CreateComment($commentId: String!, $commentText: String!, $authorId: String!, $activityId: String!, $createdAt: DateTime!) {
createOneComment(
@@ -3886,16 +4178,10 @@ export type UpdateOneCompanyMutationOptions = Apollo.BaseMutationOptions;
/**
@@ -4372,25 +4658,10 @@ export type UpdateOnePersonMutationOptions = Apollo.BaseMutationOptions;
/**
@@ -5346,6 +5617,72 @@ export function useCreateViewFieldsMutation(baseOptions?: Apollo.MutationHookOpt
export type CreateViewFieldsMutationHookResult = ReturnType;
export type CreateViewFieldsMutationResult = Apollo.MutationResult;
export type CreateViewFieldsMutationOptions = Apollo.BaseMutationOptions;
+export const CreateViewSortsDocument = gql`
+ mutation CreateViewSorts($data: [ViewSortCreateManyInput!]!) {
+ createManyViewSort(data: $data) {
+ count
+ }
+}
+ `;
+export type CreateViewSortsMutationFn = Apollo.MutationFunction;
+
+/**
+ * __useCreateViewSortsMutation__
+ *
+ * To run a mutation, you first call `useCreateViewSortsMutation` within a React component and pass it any options that fit your needs.
+ * When your component renders, `useCreateViewSortsMutation` 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 [createViewSortsMutation, { data, loading, error }] = useCreateViewSortsMutation({
+ * variables: {
+ * data: // value for 'data'
+ * },
+ * });
+ */
+export function useCreateViewSortsMutation(baseOptions?: Apollo.MutationHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useMutation(CreateViewSortsDocument, options);
+ }
+export type CreateViewSortsMutationHookResult = ReturnType;
+export type CreateViewSortsMutationResult = Apollo.MutationResult;
+export type CreateViewSortsMutationOptions = Apollo.BaseMutationOptions;
+export const DeleteViewSortsDocument = gql`
+ mutation DeleteViewSorts($where: ViewSortWhereInput!) {
+ deleteManyViewSort(where: $where) {
+ count
+ }
+}
+ `;
+export type DeleteViewSortsMutationFn = Apollo.MutationFunction;
+
+/**
+ * __useDeleteViewSortsMutation__
+ *
+ * To run a mutation, you first call `useDeleteViewSortsMutation` within a React component and pass it any options that fit your needs.
+ * When your component renders, `useDeleteViewSortsMutation` 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 [deleteViewSortsMutation, { data, loading, error }] = useDeleteViewSortsMutation({
+ * variables: {
+ * where: // value for 'where'
+ * },
+ * });
+ */
+export function useDeleteViewSortsMutation(baseOptions?: Apollo.MutationHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useMutation(DeleteViewSortsDocument, options);
+ }
+export type DeleteViewSortsMutationHookResult = ReturnType;
+export type DeleteViewSortsMutationResult = Apollo.MutationResult;
+export type DeleteViewSortsMutationOptions = Apollo.BaseMutationOptions;
export const GetViewFieldsDocument = gql`
query GetViewFields($where: ViewFieldWhereInput, $orderBy: [ViewFieldOrderByWithRelationInput!]) {
viewFields: findManyViewField(where: $where, orderBy: $orderBy) {
@@ -5386,6 +5723,43 @@ export function useGetViewFieldsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpti
export type GetViewFieldsQueryHookResult = ReturnType;
export type GetViewFieldsLazyQueryHookResult = ReturnType;
export type GetViewFieldsQueryResult = Apollo.QueryResult;
+export const GetViewSortsDocument = gql`
+ query GetViewSorts($where: ViewSortWhereInput) {
+ viewSorts: findManyViewSort(where: $where) {
+ direction
+ key
+ name
+ }
+}
+ `;
+
+/**
+ * __useGetViewSortsQuery__
+ *
+ * To run a query within a React component, call `useGetViewSortsQuery` and pass it any options that fit your needs.
+ * When your component renders, `useGetViewSortsQuery` returns an object from Apollo Client that contains loading, error, and data properties
+ * you can use to render your UI.
+ *
+ * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
+ *
+ * @example
+ * const { data, loading, error } = useGetViewSortsQuery({
+ * variables: {
+ * where: // value for 'where'
+ * },
+ * });
+ */
+export function useGetViewSortsQuery(baseOptions?: Apollo.QueryHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useQuery(GetViewSortsDocument, options);
+ }
+export function useGetViewSortsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useLazyQuery(GetViewSortsDocument, options);
+ }
+export type GetViewSortsQueryHookResult = ReturnType;
+export type GetViewSortsLazyQueryHookResult = ReturnType;
+export type GetViewSortsQueryResult = Apollo.QueryResult;
export const UpdateViewFieldDocument = gql`
mutation UpdateViewField($data: ViewFieldUpdateInput!, $where: ViewFieldWhereUniqueInput!) {
updateOneViewField(data: $data, where: $where) {
@@ -5424,6 +5798,42 @@ export function useUpdateViewFieldMutation(baseOptions?: Apollo.MutationHookOpti
export type UpdateViewFieldMutationHookResult = ReturnType;
export type UpdateViewFieldMutationResult = Apollo.MutationResult;
export type UpdateViewFieldMutationOptions = Apollo.BaseMutationOptions;
+export const UpdateViewSortDocument = gql`
+ mutation UpdateViewSort($data: ViewSortUpdateInput!, $where: ViewSortWhereUniqueInput!) {
+ viewSort: updateOneViewSort(data: $data, where: $where) {
+ direction
+ key
+ name
+ }
+}
+ `;
+export type UpdateViewSortMutationFn = Apollo.MutationFunction;
+
+/**
+ * __useUpdateViewSortMutation__
+ *
+ * To run a mutation, you first call `useUpdateViewSortMutation` within a React component and pass it any options that fit your needs.
+ * When your component renders, `useUpdateViewSortMutation` 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 [updateViewSortMutation, { data, loading, error }] = useUpdateViewSortMutation({
+ * variables: {
+ * data: // value for 'data'
+ * where: // value for 'where'
+ * },
+ * });
+ */
+export function useUpdateViewSortMutation(baseOptions?: Apollo.MutationHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useMutation(UpdateViewSortDocument, options);
+ }
+export type UpdateViewSortMutationHookResult = ReturnType;
+export type UpdateViewSortMutationResult = Apollo.MutationResult;
+export type UpdateViewSortMutationOptions = Apollo.BaseMutationOptions;
export const GetWorkspaceMembersDocument = gql`
query GetWorkspaceMembers {
workspaceMembers: findManyWorkspaceMember {
diff --git a/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
index 90896062ca..535f735804 100644
--- a/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
+++ b/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
@@ -37,7 +37,7 @@ export function useOpenCreateActivityDrawer() {
return function openCreateActivityDrawer(
type: ActivityType,
- entity?: CommentableEntity,
+ entities?: CommentableEntity[],
) {
const now = new Date().toISOString();
@@ -52,23 +52,19 @@ export function useOpenCreateActivityDrawer() {
type: type,
activityTargets: {
createMany: {
- data: entity
- ? [
- {
- commentableId: entity.id,
- commentableType: entity.type,
- companyId:
- entity.type === CommentableType.Company
- ? entity.id
- : null,
- personId:
- entity.type === CommentableType.Person
- ? entity.id
- : null,
- id: v4(),
- createdAt: now,
- },
- ]
+ data: entities
+ ? entities.map((entity) => ({
+ commentableId: entity.id,
+ commentableType: entity.type,
+ companyId:
+ entity.type === CommentableType.Company
+ ? entity.id
+ : null,
+ personId:
+ entity.type === CommentableType.Person ? entity.id : null,
+ id: v4(),
+ createdAt: now,
+ }))
: [],
skipDuplicates: true,
},
@@ -85,7 +81,7 @@ export function useOpenCreateActivityDrawer() {
onCompleted(data) {
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
setViewableActivityId(data.createOneActivity.id);
- setCommentableEntityArray(entity ? [entity] : []);
+ setCommentableEntityArray(entities ?? []);
openRightDrawer(RightDrawerPages.CreateActivity);
},
});
diff --git a/front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts b/front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts
index b363d9af2a..554218d254 100644
--- a/front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts
+++ b/front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts
@@ -1,41 +1,19 @@
-import { getOperationName } from '@apollo/client/utilities/graphql/getFromAST';
-import { useRecoilState, useRecoilValue } from 'recoil';
-import { v4 } from 'uuid';
+import { useRecoilValue } from 'recoil';
-import { currentUserState } from '@/auth/states/currentUserState';
-import { GET_COMPANIES } from '@/companies/queries';
-import { GET_PEOPLE } from '@/people/queries';
-import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
-import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
-import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector';
-import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
-import {
- ActivityType,
- CommentableType,
- useCreateActivityMutation,
-} from '~/generated/graphql';
+import { ActivityType, CommentableType } from '~/generated/graphql';
-import { GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITY } from '../queries';
-import { commentableEntityArrayState } from '../states/commentableEntityArrayState';
-import { viewableActivityIdState } from '../states/viewableActivityIdState';
import { CommentableEntity } from '../types/CommentableEntity';
+import { useOpenCreateActivityDrawer } from './useOpenCreateActivityDrawer';
+
export function useOpenCreateActivityDrawerForSelectedRowIds() {
- const { openRightDrawer } = useRightDrawer();
- const [createActivityMutation] = useCreateActivityMutation();
- const currentUser = useRecoilValue(currentUserState);
- const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
-
- const setHotkeyScope = useSetHotkeyScope();
-
- const [, setCommentableEntityArray] = useRecoilState(
- commentableEntityArrayState,
- );
-
const selectedEntityIds = useRecoilValue(selectedRowIdsSelector);
+ const openCreateActivityDrawer = useOpenCreateActivityDrawer();
+
return function openCreateCommentDrawerForSelectedRowIds(
+ type: ActivityType,
entityType: CommentableType,
) {
const commentableEntityArray: CommentableEntity[] = selectedEntityIds.map(
@@ -44,45 +22,6 @@ export function useOpenCreateActivityDrawerForSelectedRowIds() {
id,
}),
);
- const now = new Date().toISOString();
-
- createActivityMutation({
- variables: {
- data: {
- id: v4(),
- createdAt: now,
- updatedAt: now,
- author: { connect: { id: currentUser?.id ?? '' } },
- type: ActivityType.Note,
- activityTargets: {
- createMany: {
- data: commentableEntityArray.map((entity) => ({
- commentableId: entity.id,
- commentableType: entity.type,
- id: v4(),
- createdAt: new Date().toISOString(),
- companyId:
- entity.type === CommentableType.Company ? entity.id : null,
- personId:
- entity.type === CommentableType.Person ? entity.id : null,
- })),
- skipDuplicates: true,
- },
- },
- },
- },
- refetchQueries: [
- getOperationName(GET_COMPANIES) ?? '',
- getOperationName(GET_PEOPLE) ?? '',
- getOperationName(GET_ACTIVITY) ?? '',
- getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
- ],
- onCompleted(data) {
- setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
- setViewableActivityId(data.createOneActivity.id);
- setCommentableEntityArray(commentableEntityArray);
- openRightDrawer(RightDrawerPages.CreateActivity);
- },
- });
+ openCreateActivityDrawer(type, commentableEntityArray);
};
}
diff --git a/front/src/modules/activities/timeline/components/Timeline.tsx b/front/src/modules/activities/timeline/components/Timeline.tsx
index c395642b53..a5dd649da1 100644
--- a/front/src/modules/activities/timeline/components/Timeline.tsx
+++ b/front/src/modules/activities/timeline/components/Timeline.tsx
@@ -121,8 +121,8 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
No activity yet
Create one:
openCreateActivity(ActivityType.Note, entity)}
- onTaskClick={() => openCreateActivity(ActivityType.Task, entity)}
+ onNoteClick={() => openCreateActivity(ActivityType.Note, [entity])}
+ onTaskClick={() => openCreateActivity(ActivityType.Task, [entity])}
/>
);
@@ -132,8 +132,8 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
openCreateActivity(ActivityType.Note, entity)}
- onTaskClick={() => openCreateActivity(ActivityType.Task, entity)}
+ onNoteClick={() => openCreateActivity(ActivityType.Note, [entity])}
+ onTaskClick={() => openCreateActivity(ActivityType.Task, [entity])}
/>
diff --git a/front/src/modules/companies/queries/__tests__/select.test.ts b/front/src/modules/companies/queries/__tests__/select.test.ts
index 80e292f7b5..bb0ddb0ba6 100644
--- a/front/src/modules/companies/queries/__tests__/select.test.ts
+++ b/front/src/modules/companies/queries/__tests__/select.test.ts
@@ -5,12 +5,11 @@ import { CompaniesSelectedSortType } from '../select';
describe('reduceSortsToOrderBy', () => {
it('should return an array of objects with the id as key and the order as value', () => {
const sorts = [
- { key: 'name', label: 'name', order: 'asc', _type: 'default_sort' },
+ { key: 'name', label: 'name', order: 'asc' },
{
key: 'domainName',
label: 'domainName',
order: 'desc',
- _type: 'default_sort',
},
] satisfies CompaniesSelectedSortType[];
const result = reduceSortsToOrderBy(sorts);
diff --git a/front/src/modules/companies/queries/update.ts b/front/src/modules/companies/queries/update.ts
index 483baf33c1..fe9c2c7e70 100644
--- a/front/src/modules/companies/queries/update.ts
+++ b/front/src/modules/companies/queries/update.ts
@@ -24,16 +24,20 @@ export const UPDATE_ONE_COMPANY = gql`
}
`;
+export const INSERT_COMPANY_FRAGMENT = gql`
+ fragment InsertCompanyFragment on Company {
+ domainName
+ address
+ id
+ name
+ createdAt
+ }
+`;
+
export const INSERT_ONE_COMPANY = gql`
mutation InsertOneCompany($data: CompanyCreateInput!) {
createOneCompany(data: $data) {
- address
- createdAt
- domainName
- linkedinUrl
- employees
- id
- name
+ ...InsertCompanyFragment
}
}
`;
diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx
index df38b6e2f0..cd5ba3d7d3 100644
--- a/front/src/modules/companies/table/components/CompanyTable.tsx
+++ b/front/src/modules/companies/table/components/CompanyTable.tsx
@@ -1,30 +1,33 @@
-import { useCallback, useMemo, useState } from 'react';
+import { useMemo } from 'react';
+import { useRecoilValue } from 'recoil';
import { companyViewFields } from '@/companies/constants/companyViewFields';
-import { CompaniesSelectedSortType, defaultOrderBy } from '@/companies/queries';
-import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
+import { sortsOrderByScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { IconList } from '@/ui/icon';
import { EntityTable } from '@/ui/table/components/EntityTable';
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
import { TableContext } from '@/ui/table/states/TableContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
+import { useViewSorts } from '@/views/hooks/useViewSorts';
+import { currentViewIdState } from '@/views/states/currentViewIdState';
import {
- CompanyOrderByWithRelationInput,
useGetCompaniesQuery,
useUpdateOneCompanyMutation,
} from '~/generated/graphql';
import { companiesFilters } from '~/pages/companies/companies-filters';
import { availableSorts } from '~/pages/companies/companies-sorts';
-export function CompanyTable() {
- const [orderBy, setOrderBy] =
- useState(defaultOrderBy);
+import { defaultOrderBy } from '../../queries';
- const updateSorts = useCallback((sorts: Array) => {
- setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
- }, []);
+export function CompanyTable() {
+ const currentViewId = useRecoilValue(currentViewIdState);
+ const orderBy = useRecoilScopedValue(sortsOrderByScopedState, TableContext);
+ const { updateSorts } = useViewSorts({
+ availableSorts,
+ Context: TableContext,
+ });
const filters = useRecoilScopedValue(filtersScopedState, TableContext);
@@ -38,7 +41,7 @@ export function CompanyTable() {
objectName="company"
getRequestResultKey="companies"
useGetRequest={useGetCompaniesQuery}
- orderBy={orderBy}
+ orderBy={orderBy.length ? orderBy : defaultOrderBy}
whereFilters={whereFilters}
viewFieldDefinitions={companyViewFields}
filterDefinitionArray={companiesFilters}
@@ -47,7 +50,7 @@ export function CompanyTable() {
viewName="All Companies"
viewIcon={}
availableSorts={availableSorts}
- onSortsUpdate={updateSorts}
+ onSortsUpdate={currentViewId ? updateSorts : undefined}
useUpdateEntityMutation={useUpdateOneCompanyMutation}
/>
>
diff --git a/front/src/modules/companies/table/components/TableActionBarButtonCreateActivityCompany.tsx b/front/src/modules/companies/table/components/TableActionBarButtonCreateActivityCompany.tsx
index b06dafaef6..cfb5eefdad 100644
--- a/front/src/modules/companies/table/components/TableActionBarButtonCreateActivityCompany.tsx
+++ b/front/src/modules/companies/table/components/TableActionBarButtonCreateActivityCompany.tsx
@@ -1,14 +1,24 @@
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
-import { CommentableType } from '~/generated/graphql';
+import { TableActionBarButtonToggleTasks } from '@/ui/table/action-bar/components/TableActionBarButtonOpenTasks';
+import { ActivityType, CommentableType } from '~/generated/graphql';
export function TableActionBarButtonCreateActivityCompany() {
const openCreateActivityRightDrawer =
useOpenCreateActivityDrawerForSelectedRowIds();
- async function handleButtonClick() {
- openCreateActivityRightDrawer(CommentableType.Company);
+ async function handleButtonClick(type: ActivityType) {
+ openCreateActivityRightDrawer(type, CommentableType.Company);
}
- return ;
+ return (
+ <>
+ handleButtonClick(ActivityType.Note)}
+ />
+ handleButtonClick(ActivityType.Task)}
+ />
+ >
+ );
}
diff --git a/front/src/modules/companies/table/components/TableActionBarButtonDeleteCompanies.tsx b/front/src/modules/companies/table/components/TableActionBarButtonDeleteCompanies.tsx
index fc172760d9..63d51d09fc 100644
--- a/front/src/modules/companies/table/components/TableActionBarButtonDeleteCompanies.tsx
+++ b/front/src/modules/companies/table/components/TableActionBarButtonDeleteCompanies.tsx
@@ -1,12 +1,12 @@
import { getOperationName } from '@apollo/client/utilities';
-import { useRecoilValue } from 'recoil';
+import { useRecoilState, useRecoilValue } from 'recoil';
-import { GET_COMPANIES } from '@/companies/queries';
import { GET_PIPELINES } from '@/pipeline/queries';
import { IconTrash } from '@/ui/icon/index';
import { EntityTableActionBarButton } from '@/ui/table/action-bar/components/EntityTableActionBarButton';
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector';
+import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { useDeleteManyCompaniesMutation } from '~/generated/graphql';
export function TableActionBarButtonDeleteCompanies() {
@@ -15,12 +15,11 @@ export function TableActionBarButtonDeleteCompanies() {
const resetRowSelection = useResetTableRowSelection();
const [deleteCompanies] = useDeleteManyCompaniesMutation({
- refetchQueries: [
- getOperationName(GET_COMPANIES) ?? '',
- getOperationName(GET_PIPELINES) ?? '',
- ],
+ refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
});
+ const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState);
+
async function handleDeleteClick() {
const rowIdsToDelete = selectedRowIds;
@@ -30,6 +29,17 @@ export function TableActionBarButtonDeleteCompanies() {
variables: {
ids: rowIdsToDelete,
},
+ optimisticResponse: {
+ __typename: 'Mutation',
+ deleteManyCompany: {
+ count: rowIdsToDelete.length,
+ },
+ },
+ update: () => {
+ setTableRowIds(
+ tableRowIds.filter((id) => !rowIdsToDelete.includes(id)),
+ );
+ },
});
}
diff --git a/front/src/modules/people/queries/update.ts b/front/src/modules/people/queries/update.ts
index 91c544f90c..7c3b00ebd5 100644
--- a/front/src/modules/people/queries/update.ts
+++ b/front/src/modules/people/queries/update.ts
@@ -26,25 +26,20 @@ export const UPDATE_ONE_PERSON = gql`
}
`;
+export const INSERT_PERSON_FRAGMENT = gql`
+ fragment InsertPersonFragment on Person {
+ id
+ firstName
+ lastName
+ displayName
+ createdAt
+ }
+`;
+
export const INSERT_ONE_PERSON = gql`
mutation InsertOnePerson($data: PersonCreateInput!) {
createOnePerson(data: $data) {
- id
- city
- company {
- domainName
- name
- id
- }
- email
- firstName
- lastName
- jobTitle
- linkedinUrl
- xUrl
- displayName
- phone
- createdAt
+ ...InsertPersonFragment
}
}
`;
diff --git a/front/src/modules/people/table/components/PeopleTable.tsx b/front/src/modules/people/table/components/PeopleTable.tsx
index ab80fabd43..0e4bd2ba7c 100644
--- a/front/src/modules/people/table/components/PeopleTable.tsx
+++ b/front/src/modules/people/table/components/PeopleTable.tsx
@@ -1,31 +1,33 @@
-import { useCallback, useMemo, useState } from 'react';
+import { useMemo } from 'react';
+import { useRecoilValue } from 'recoil';
-import { defaultOrderBy } from '@/companies/queries';
import { peopleViewFields } from '@/people/constants/peopleViewFields';
-import { PeopleSelectedSortType } from '@/people/queries';
-import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
+import { sortsOrderByScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { IconList } from '@/ui/icon';
import { EntityTable } from '@/ui/table/components/EntityTable';
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
import { TableContext } from '@/ui/table/states/TableContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
+import { useViewSorts } from '@/views/hooks/useViewSorts';
+import { currentViewIdState } from '@/views/states/currentViewIdState';
import {
- PersonOrderByWithRelationInput,
useGetPeopleQuery,
useUpdateOnePersonMutation,
} from '~/generated/graphql';
import { peopleFilters } from '~/pages/people/people-filters';
import { availableSorts } from '~/pages/people/people-sorts';
-export function PeopleTable() {
- const [orderBy, setOrderBy] =
- useState(defaultOrderBy);
+import { defaultOrderBy } from '../../queries';
- const updateSorts = useCallback((sorts: Array) => {
- setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
- }, []);
+export function PeopleTable() {
+ const currentViewId = useRecoilValue(currentViewIdState);
+ const orderBy = useRecoilScopedValue(sortsOrderByScopedState, TableContext);
+ const { updateSorts } = useViewSorts({
+ availableSorts,
+ Context: TableContext,
+ });
const filters = useRecoilScopedValue(filtersScopedState, TableContext);
@@ -39,7 +41,7 @@ export function PeopleTable() {
objectName="person"
getRequestResultKey="people"
useGetRequest={useGetPeopleQuery}
- orderBy={orderBy}
+ orderBy={orderBy.length ? orderBy : defaultOrderBy}
whereFilters={whereFilters}
viewFieldDefinitions={peopleViewFields}
filterDefinitionArray={peopleFilters}
@@ -48,7 +50,7 @@ export function PeopleTable() {
viewName="All People"
viewIcon={}
availableSorts={availableSorts}
- onSortsUpdate={updateSorts}
+ onSortsUpdate={currentViewId ? updateSorts : undefined}
useUpdateEntityMutation={useUpdateOnePersonMutation}
/>
>
diff --git a/front/src/modules/people/table/components/TableActionBarButtonCreateActivityPeople.tsx b/front/src/modules/people/table/components/TableActionBarButtonCreateActivityPeople.tsx
index 31197a5b91..9d8e271ca8 100644
--- a/front/src/modules/people/table/components/TableActionBarButtonCreateActivityPeople.tsx
+++ b/front/src/modules/people/table/components/TableActionBarButtonCreateActivityPeople.tsx
@@ -1,14 +1,24 @@
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
-import { CommentableType } from '~/generated/graphql';
+import { TableActionBarButtonToggleTasks } from '@/ui/table/action-bar/components/TableActionBarButtonOpenTasks';
+import { ActivityType, CommentableType } from '~/generated/graphql';
export function TableActionBarButtonCreateActivityPeople() {
const openCreateActivityRightDrawer =
useOpenCreateActivityDrawerForSelectedRowIds();
- async function handleButtonClick() {
- openCreateActivityRightDrawer(CommentableType.Person);
+ async function handleButtonClick(type: ActivityType) {
+ openCreateActivityRightDrawer(type, CommentableType.Person);
}
- return ;
+ return (
+ <>
+ handleButtonClick(ActivityType.Note)}
+ />
+ handleButtonClick(ActivityType.Task)}
+ />
+ >
+ );
}
diff --git a/front/src/modules/people/table/components/TableActionBarButtonDeletePeople.tsx b/front/src/modules/people/table/components/TableActionBarButtonDeletePeople.tsx
index f431e96adc..f3bb51f6a7 100644
--- a/front/src/modules/people/table/components/TableActionBarButtonDeletePeople.tsx
+++ b/front/src/modules/people/table/components/TableActionBarButtonDeletePeople.tsx
@@ -1,15 +1,17 @@
import { getOperationName } from '@apollo/client/utilities';
-import { useRecoilValue } from 'recoil';
+import { useRecoilState, useRecoilValue } from 'recoil';
import { GET_PEOPLE } from '@/people/queries';
import { IconTrash } from '@/ui/icon/index';
import { EntityTableActionBarButton } from '@/ui/table/action-bar/components/EntityTableActionBarButton';
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector';
+import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { useDeleteManyPersonMutation } from '~/generated/graphql';
export function TableActionBarButtonDeletePeople() {
const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
+ const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState);
const resetRowSelection = useResetTableRowSelection();
@@ -26,6 +28,17 @@ export function TableActionBarButtonDeletePeople() {
variables: {
ids: rowIdsToDelete,
},
+ optimisticResponse: {
+ __typename: 'Mutation',
+ deleteManyPerson: {
+ count: rowIdsToDelete.length,
+ },
+ },
+ update: () => {
+ setTableRowIds(
+ tableRowIds.filter((id) => !rowIdsToDelete.includes(id)),
+ );
+ },
});
}
diff --git a/front/src/modules/ui/filter-n-sort/helpers.ts b/front/src/modules/ui/filter-n-sort/helpers.ts
index 818a231348..596e4d2f4e 100644
--- a/front/src/modules/ui/filter-n-sort/helpers.ts
+++ b/front/src/modules/ui/filter-n-sort/helpers.ts
@@ -2,27 +2,16 @@ import { SortOrder as Order_By } from '~/generated/graphql';
import { SelectedSortType } from './types/interface';
-const mapOrderToOrder_By = (order: string) => {
- if (order === 'asc') return Order_By.Asc;
- return Order_By.Desc;
-};
-
-export const defaultOrderByTemplateFactory =
- (key: string) => (order: string) => ({
- [key]: order,
- });
-
export const reduceSortsToOrderBy = (
- sorts: Array>,
-): OrderByTemplate[] => {
- const mappedSorts = sorts.map((sort) => {
- if (sort.orderByTemplates) {
- return sort.orderByTemplates?.map((orderByTemplate) =>
- orderByTemplate(mapOrderToOrder_By(sort.order)),
+ sorts: SelectedSortType[],
+): OrderByTemplate[] =>
+ sorts
+ .map((sort) => {
+ const order = sort.order === 'asc' ? Order_By.Asc : Order_By.Desc;
+ return (
+ sort.orderByTemplate?.(order) || [
+ { [sort.key]: order } as OrderByTemplate,
+ ]
);
- }
-
- return defaultOrderByTemplateFactory(sort.key as string)(sort.order);
- });
- return mappedSorts.flat() as OrderByTemplate[];
-};
+ })
+ .flat();
diff --git a/front/src/modules/ui/filter-n-sort/states/sortScopedState.ts b/front/src/modules/ui/filter-n-sort/states/sortScopedState.ts
index 27b11d816e..7d9021a247 100644
--- a/front/src/modules/ui/filter-n-sort/states/sortScopedState.ts
+++ b/front/src/modules/ui/filter-n-sort/states/sortScopedState.ts
@@ -1,8 +1,28 @@
-import { atomFamily } from 'recoil';
+import { atomFamily, selectorFamily } from 'recoil';
-import { Filter } from '../types/Filter';
+import { reduceSortsToOrderBy } from '../helpers';
+import { SelectedSortType } from '../types/interface';
-export const sortScopedState = atomFamily({
+export const sortScopedState = atomFamily[], string>({
key: 'sortScopedState',
default: [],
});
+
+export const sortsByKeyScopedState = selectorFamily({
+ key: 'sortsByKeyScopedState',
+ get:
+ (param: string) =>
+ ({ get }) =>
+ get(sortScopedState(param)).reduce>>(
+ (result, sort) => ({ ...result, [sort.key]: sort }),
+ {},
+ ),
+});
+
+export const sortsOrderByScopedState = selectorFamily({
+ key: 'sortsOrderByScopedState',
+ get:
+ (param: string) =>
+ ({ get }) =>
+ reduceSortsToOrderBy(get(sortScopedState(param))),
+});
diff --git a/front/src/modules/ui/filter-n-sort/types/interface.ts b/front/src/modules/ui/filter-n-sort/types/interface.ts
index 1610e1b443..0cba99a243 100644
--- a/front/src/modules/ui/filter-n-sort/types/interface.ts
+++ b/front/src/modules/ui/filter-n-sort/types/interface.ts
@@ -6,7 +6,7 @@ export type SortType = {
label: string;
key: string;
icon?: ReactNode;
- orderByTemplates?: Array<(order: Order_By) => OrderByTemplate>;
+ orderByTemplate?: (order: Order_By) => OrderByTemplate[];
};
export type SelectedSortType = SortType & {
diff --git a/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenTasks.tsx b/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenTasks.tsx
new file mode 100644
index 0000000000..b407ec239b
--- /dev/null
+++ b/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenTasks.tsx
@@ -0,0 +1,17 @@
+import { IconCheckbox } from '@/ui/icon/index';
+
+import { EntityTableActionBarButton } from './EntityTableActionBarButton';
+
+type OwnProps = {
+ onClick: () => void;
+};
+
+export function TableActionBarButtonToggleTasks({ onClick }: OwnProps) {
+ return (
+ }
+ onClick={onClick}
+ />
+ );
+}
diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx
index c4bfd99c6b..e999449cc5 100644
--- a/front/src/modules/ui/table/components/EntityTableHeader.tsx
+++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx
@@ -5,19 +5,20 @@ import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { IconButton } from '@/ui/button/components/IconButton';
+import type {
+ ViewFieldDefinition,
+ ViewFieldMetadata,
+} from '@/ui/editable-field/types/ViewField';
import { IconPlus } from '@/ui/icon';
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
import { GET_VIEW_FIELDS } from '@/views/queries/select';
+import { currentViewIdState } from '@/views/states/currentViewIdState';
import {
useCreateViewFieldMutation,
useUpdateViewFieldMutation,
} from '~/generated/graphql';
-import type {
- ViewFieldDefinition,
- ViewFieldMetadata,
-} from '../../editable-field/types/ViewField';
-import { toViewFieldInput } from '../hooks/useLoadView';
+import { toViewFieldInput } from '../hooks/useLoadViewFields';
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
import {
addableViewFieldDefinitionsState,
@@ -89,6 +90,7 @@ export function EntityTableHeader() {
const theme = useTheme();
const [{ objectName }, setViewFieldsState] = useRecoilState(viewFieldsState);
+ const currentViewId = useRecoilValue(currentViewIdState);
const viewFields = useRecoilValue(visibleViewFieldsState);
const columnWidths = useRecoilValue(columnWidthByViewFieldIdState);
const addableViewFieldDefinitions = useRecoilValue(
@@ -176,15 +178,18 @@ export function EntityTableHeader() {
createViewFieldMutation({
variables: {
- data: toViewFieldInput(objectName, {
- ...viewFieldDefinition,
- columnOrder: viewFields.length + 1,
- }),
+ data: {
+ ...toViewFieldInput(objectName, {
+ ...viewFieldDefinition,
+ columnOrder: viewFields.length + 1,
+ }),
+ view: { connect: { id: currentViewId } },
+ },
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
});
},
- [createViewFieldMutation, objectName, viewFields.length],
+ [createViewFieldMutation, currentViewId, objectName, viewFields.length],
);
return (
diff --git a/front/src/modules/ui/table/components/GenericEntityTableData.tsx b/front/src/modules/ui/table/components/GenericEntityTableData.tsx
index bed37bc818..f284571479 100644
--- a/front/src/modules/ui/table/components/GenericEntityTableData.tsx
+++ b/front/src/modules/ui/table/components/GenericEntityTableData.tsx
@@ -6,7 +6,7 @@ import {
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
-import { useLoadView } from '../hooks/useLoadView';
+import { useLoadViewFields } from '../hooks/useLoadViewFields';
export function GenericEntityTableData({
objectName,
@@ -27,7 +27,7 @@ export function GenericEntityTableData({
}) {
const setEntityTableData = useSetEntityTableData();
- useLoadView({ objectName, viewFieldDefinitions });
+ useLoadViewFields({ objectName, viewFieldDefinitions });
useGetRequest({
variables: { orderBy, where: whereFilters },
diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx
index d4c61d051f..348e6b73ab 100644
--- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx
+++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx
@@ -40,7 +40,8 @@ export function GenericEditableDoubleTextChipCellDisplayMode({
}),
);
- const displayName = `${firstValue} ${secondValue}`;
+ const displayName =
+ firstValue || secondValue ? `${firstValue} ${secondValue}` : ' ';
switch (viewField.metadata.entityType) {
case Entity.Company: {
diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx
index 23da20a191..605c9d87b7 100644
--- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx
+++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx
@@ -7,6 +7,7 @@ import {
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
+import { isURL } from '~/utils/is-url';
import { TextCellEdit } from './TextCellEdit';
@@ -30,6 +31,8 @@ export function GenericEditableURLCellEditMode({ viewField }: OwnProps) {
function handleSubmit(newText: string) {
if (newText === fieldValue) return;
+ if (!isURL(newText)) return;
+
setFieldValue(newText);
if (currentRowEntityId && updateField) {
diff --git a/front/src/modules/ui/table/hooks/useLoadView.ts b/front/src/modules/ui/table/hooks/useLoadViewFields.ts
similarity index 82%
rename from front/src/modules/ui/table/hooks/useLoadView.ts
rename to front/src/modules/ui/table/hooks/useLoadViewFields.ts
index 801531aef4..f7cbb56405 100644
--- a/front/src/modules/ui/table/hooks/useLoadView.ts
+++ b/front/src/modules/ui/table/hooks/useLoadViewFields.ts
@@ -1,18 +1,19 @@
import { getOperationName } from '@apollo/client/utilities';
-import { useSetRecoilState } from 'recoil';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
+import type {
+ ViewFieldDefinition,
+ ViewFieldMetadata,
+ ViewFieldTextMetadata,
+} from '@/ui/editable-field/types/ViewField';
import { GET_VIEW_FIELDS } from '@/views/queries/select';
+import { currentViewIdState } from '@/views/states/currentViewIdState';
import {
SortOrder,
useCreateViewFieldsMutation,
useGetViewFieldsQuery,
} from '~/generated/graphql';
-import type {
- ViewFieldDefinition,
- ViewFieldMetadata,
- ViewFieldTextMetadata,
-} from '../../editable-field/types/ViewField';
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
import { viewFieldsState } from '../states/viewFieldsState';
@@ -33,13 +34,14 @@ export const toViewFieldInput = (
sizeInPx: viewFieldDefinition.columnSize,
});
-export const useLoadView = ({
+export const useLoadViewFields = ({
objectName,
viewFieldDefinitions,
}: {
objectName: 'company' | 'person';
viewFieldDefinitions: ViewFieldDefinition[];
}) => {
+ const currentViewId = useRecoilValue(currentViewIdState);
const setEntityTableDimensions = useSetRecoilState(
entityTableDimensionsState,
);
@@ -50,7 +52,10 @@ export const useLoadView = ({
useGetViewFieldsQuery({
variables: {
orderBy: { index: SortOrder.Asc },
- where: { objectName: { equals: objectName } },
+ where: {
+ objectName: { equals: objectName },
+ viewId: { equals: currentViewId ?? null },
+ },
},
onCompleted: (data) => {
if (data.viewFields.length) {
@@ -79,9 +84,10 @@ export const useLoadView = ({
// Populate if empty
createViewFieldsMutation({
variables: {
- data: viewFieldDefinitions.map((viewFieldDefinition) =>
- toViewFieldInput(objectName, viewFieldDefinition),
- ),
+ data: viewFieldDefinitions.map((viewFieldDefinition) => ({
+ ...toViewFieldInput(objectName, viewFieldDefinition),
+ viewId: currentViewId,
+ })),
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
});
diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx
index 1934794110..c406d8b806 100644
--- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx
+++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx
@@ -1,12 +1,14 @@
-import { ReactNode, useCallback, useState } from 'react';
+import { ReactNode, useCallback } from 'react';
import styled from '@emotion/styled';
import { FilterDropdownButton } from '@/ui/filter-n-sort/components/FilterDropdownButton';
import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar';
import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton';
+import { sortScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { TopBar } from '@/ui/top-bar/TopBar';
+import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { OptionsDropdownButton } from '@/views/components/OptionsDropdownButton';
import { TableContext } from '../../states/TableContext';
@@ -34,26 +36,26 @@ export function TableHeader({
availableSorts,
onSortsUpdate,
}: OwnProps) {
- const [sorts, innerSetSorts] = useState>>(
- [],
+ const [sorts, setSorts] = useRecoilScopedState[]>(
+ sortScopedState,
+ TableContext,
);
+ const handleSortsUpdate = onSortsUpdate ?? setSorts;
const sortSelect = useCallback(
(newSort: SelectedSortType) => {
const newSorts = updateSortOrFilterByKey(sorts, newSort);
- innerSetSorts(newSorts);
- onSortsUpdate && onSortsUpdate(newSorts);
+ handleSortsUpdate(newSorts);
},
- [onSortsUpdate, sorts],
+ [handleSortsUpdate, sorts],
);
const sortUnselect = useCallback(
(sortKey: string) => {
const newSorts = sorts.filter((sort) => sort.key !== sortKey);
- innerSetSorts(newSorts);
- onSortsUpdate && onSortsUpdate(newSorts);
+ handleSortsUpdate(newSorts);
},
- [onSortsUpdate, sorts],
+ [handleSortsUpdate, sorts],
);
return (
@@ -88,8 +90,7 @@ export function TableHeader({
sorts={sorts}
onRemoveSort={sortUnselect}
onCancelClick={() => {
- innerSetSorts([]);
- onSortsUpdate && onSortsUpdate([]);
+ handleSortsUpdate([]);
}}
/>
}
diff --git a/front/src/modules/ui/utilities/recoil-scope/hooks/useRecoilScopedValue.ts b/front/src/modules/ui/utilities/recoil-scope/hooks/useRecoilScopedValue.ts
index 1ef6e8ff6b..a3f65ef8f1 100644
--- a/front/src/modules/ui/utilities/recoil-scope/hooks/useRecoilScopedValue.ts
+++ b/front/src/modules/ui/utilities/recoil-scope/hooks/useRecoilScopedValue.ts
@@ -1,10 +1,10 @@
import { Context, useContext } from 'react';
-import { RecoilState, useRecoilValue } from 'recoil';
+import { RecoilState, RecoilValueReadOnly, useRecoilValue } from 'recoil';
import { RecoilScopeContext } from '../states/RecoilScopeContext';
export function useRecoilScopedValue(
- recoilState: (param: string) => RecoilState,
+ recoilState: (param: string) => RecoilState | RecoilValueReadOnly,
SpecificContext?: Context,
) {
const recoilScopeId = useContext(SpecificContext ?? RecoilScopeContext);
diff --git a/front/src/modules/views/hooks/useViewSorts.ts b/front/src/modules/views/hooks/useViewSorts.ts
new file mode 100644
index 0000000000..81ec89b003
--- /dev/null
+++ b/front/src/modules/views/hooks/useViewSorts.ts
@@ -0,0 +1,152 @@
+import { Context, useCallback } from 'react';
+import { getOperationName } from '@apollo/client/utilities';
+import { useRecoilValue } from 'recoil';
+
+import {
+ sortsByKeyScopedState,
+ sortScopedState,
+} from '@/ui/filter-n-sort/states/sortScopedState';
+import type {
+ SelectedSortType,
+ SortType,
+} from '@/ui/filter-n-sort/types/interface';
+import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
+import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
+import { currentViewIdState } from '@/views/states/currentViewIdState';
+import {
+ useCreateViewSortsMutation,
+ useDeleteViewSortsMutation,
+ useGetViewSortsQuery,
+ useUpdateViewSortMutation,
+ ViewSortDirection,
+} from '~/generated/graphql';
+
+import { GET_VIEW_SORTS } from '../queries/select';
+
+export const useViewSorts = ({
+ availableSorts,
+ Context,
+}: {
+ availableSorts: SortType[];
+ Context?: Context;
+}) => {
+ const currentViewId = useRecoilValue(currentViewIdState);
+ const [, setSorts] = useRecoilScopedState(sortScopedState, Context);
+ const sortsByKey = useRecoilScopedValue(sortsByKeyScopedState, Context);
+
+ useGetViewSortsQuery({
+ skip: !currentViewId,
+ variables: {
+ where: {
+ viewId: { equals: currentViewId },
+ },
+ },
+ onCompleted: (data) => {
+ setSorts(
+ data.viewSorts
+ .map((viewSort) => ({
+ ...availableSorts.find((sort) => sort.key === viewSort.key),
+ label: viewSort.name,
+ order: viewSort.direction.toLowerCase(),
+ }))
+ .filter((sort): sort is SelectedSortType => !!sort),
+ );
+ },
+ });
+
+ const [createViewSortsMutation] = useCreateViewSortsMutation();
+ const [updateViewSortMutation] = useUpdateViewSortMutation();
+ const [deleteViewSortsMutation] = useDeleteViewSortsMutation();
+
+ const createViewSorts = useCallback(
+ (sorts: SelectedSortType[]) => {
+ if (!currentViewId || !sorts.length) return;
+
+ return createViewSortsMutation({
+ variables: {
+ data: sorts.map((sort) => ({
+ key: sort.key,
+ direction: sort.order as ViewSortDirection,
+ name: sort.label,
+ viewId: currentViewId,
+ })),
+ },
+ refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
+ });
+ },
+ [createViewSortsMutation, currentViewId],
+ );
+
+ const updateViewSorts = useCallback(
+ (sorts: SelectedSortType[]) => {
+ if (!currentViewId || !sorts.length) return;
+
+ return Promise.all(
+ sorts.map((sort) =>
+ updateViewSortMutation({
+ variables: {
+ data: {
+ direction: sort.order as ViewSortDirection,
+ },
+ where: {
+ viewId_key: { key: sort.key, viewId: currentViewId },
+ },
+ },
+ refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
+ }),
+ ),
+ );
+ },
+ [currentViewId, updateViewSortMutation],
+ );
+
+ const deleteViewSorts = useCallback(
+ (sortKeys: string[]) => {
+ if (!currentViewId || !sortKeys.length) return;
+
+ return deleteViewSortsMutation({
+ variables: {
+ where: {
+ key: { in: sortKeys },
+ viewId: { equals: currentViewId },
+ },
+ },
+ refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
+ });
+ },
+ [currentViewId, deleteViewSortsMutation],
+ );
+
+ const updateSorts = useCallback(
+ async (nextSorts: SelectedSortType[]) => {
+ if (!currentViewId) return;
+
+ const sortsToCreate = nextSorts.filter(
+ (nextSort) => !sortsByKey[nextSort.key],
+ );
+ await createViewSorts(sortsToCreate);
+
+ const sortsToUpdate = nextSorts.filter(
+ (nextSort) =>
+ sortsByKey[nextSort.key] &&
+ sortsByKey[nextSort.key].order !== nextSort.order,
+ );
+ await updateViewSorts(sortsToUpdate);
+
+ const nextSortKeys = nextSorts.map((nextSort) => nextSort.key);
+ const sortKeysToDelete = Object.keys(sortsByKey).filter(
+ (previousSortKey) => !nextSortKeys.includes(previousSortKey),
+ );
+ return deleteViewSorts(sortKeysToDelete);
+ },
+ [
+ createViewSorts,
+ currentViewId,
+ deleteViewSorts,
+ sortsByKey,
+ updateViewSorts,
+ ],
+ );
+
+ return { updateSorts };
+};
diff --git a/front/src/modules/views/queries/create.ts b/front/src/modules/views/queries/create.ts
index a99ada2fd8..ace46507c6 100644
--- a/front/src/modules/views/queries/create.ts
+++ b/front/src/modules/views/queries/create.ts
@@ -19,3 +19,11 @@ export const CREATE_VIEW_FIELDS = gql`
}
}
`;
+
+export const CREATE_VIEW_SORTS = gql`
+ mutation CreateViewSorts($data: [ViewSortCreateManyInput!]!) {
+ createManyViewSort(data: $data) {
+ count
+ }
+ }
+`;
diff --git a/front/src/modules/views/queries/delete.ts b/front/src/modules/views/queries/delete.ts
new file mode 100644
index 0000000000..15cec37ee2
--- /dev/null
+++ b/front/src/modules/views/queries/delete.ts
@@ -0,0 +1,9 @@
+import { gql } from '@apollo/client';
+
+export const DELETE_VIEW_SORTS = gql`
+ mutation DeleteViewSorts($where: ViewSortWhereInput!) {
+ deleteManyViewSort(where: $where) {
+ count
+ }
+ }
+`;
diff --git a/front/src/modules/views/queries/select.ts b/front/src/modules/views/queries/select.ts
index 735405c482..6cadcde8c1 100644
--- a/front/src/modules/views/queries/select.ts
+++ b/front/src/modules/views/queries/select.ts
@@ -14,3 +14,13 @@ export const GET_VIEW_FIELDS = gql`
}
}
`;
+
+export const GET_VIEW_SORTS = gql`
+ query GetViewSorts($where: ViewSortWhereInput) {
+ viewSorts: findManyViewSort(where: $where) {
+ direction
+ key
+ name
+ }
+ }
+`;
diff --git a/front/src/modules/views/queries/update.ts b/front/src/modules/views/queries/update.ts
index c270d7a6b7..3430eac347 100644
--- a/front/src/modules/views/queries/update.ts
+++ b/front/src/modules/views/queries/update.ts
@@ -14,3 +14,16 @@ export const UPDATE_VIEW_FIELD = gql`
}
}
`;
+
+export const UPDATE_VIEW_SORT = gql`
+ mutation UpdateViewSort(
+ $data: ViewSortUpdateInput!
+ $where: ViewSortWhereUniqueInput!
+ ) {
+ viewSort: updateOneViewSort(data: $data, where: $where) {
+ direction
+ key
+ name
+ }
+ }
+`;
diff --git a/front/src/modules/views/states/currentViewIdState.ts b/front/src/modules/views/states/currentViewIdState.ts
new file mode 100644
index 0000000000..08ceca1c12
--- /dev/null
+++ b/front/src/modules/views/states/currentViewIdState.ts
@@ -0,0 +1,6 @@
+import { atom } from 'recoil';
+
+export const currentViewIdState = atom({
+ key: 'currentViewIdState',
+ default: undefined,
+});
diff --git a/front/src/pages/companies/Companies.tsx b/front/src/pages/companies/Companies.tsx
index 77dd9e2f8d..79d04fd694 100644
--- a/front/src/pages/companies/Companies.tsx
+++ b/front/src/pages/companies/Companies.tsx
@@ -1,20 +1,21 @@
import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
+import { useRecoilState } from 'recoil';
+import { v4 } from 'uuid';
-import { GET_COMPANIES } from '@/companies/queries';
import { CompanyTable } from '@/companies/table/components/CompanyTable';
import { TableActionBarButtonCreateActivityCompany } from '@/companies/table/components/TableActionBarButtonCreateActivityCompany';
import { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies';
+import { SEARCH_COMPANY_QUERY } from '@/search/queries/search';
import { IconBuildingSkyscraper } from '@/ui/icon';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar';
import { TableContext } from '@/ui/table/states/TableContext';
+import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useInsertOneCompanyMutation } from '~/generated/graphql';
-import { SEARCH_COMPANY_QUERY } from '../../modules/search/queries/search';
-
const StyledTableContainer = styled.div`
display: flex;
width: 100%;
@@ -22,20 +23,35 @@ const StyledTableContainer = styled.div`
export function Companies() {
const [insertCompany] = useInsertOneCompanyMutation();
+ const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState);
async function handleAddButtonClick() {
+ const newCompanyId: string = v4();
await insertCompany({
variables: {
data: {
+ id: newCompanyId,
name: '',
domainName: '',
address: '',
},
},
- refetchQueries: [
- getOperationName(GET_COMPANIES) ?? '',
- getOperationName(SEARCH_COMPANY_QUERY) ?? '',
- ],
+ optimisticResponse: {
+ __typename: 'Mutation',
+ createOneCompany: {
+ __typename: 'Company',
+ id: newCompanyId,
+ name: '',
+ domainName: '',
+ address: '',
+ createdAt: '',
+ },
+ },
+ update: (cache, { data }) => {
+ data?.createOneCompany.id &&
+ setTableRowIds([data?.createOneCompany.id, ...tableRowIds]);
+ },
+ refetchQueries: [getOperationName(SEARCH_COMPANY_QUERY) ?? ''],
});
}
diff --git a/front/src/pages/companies/companies-sorts.tsx b/front/src/pages/companies/companies-sorts.tsx
index 71ecc66203..df26454837 100644
--- a/front/src/pages/companies/companies-sorts.tsx
+++ b/front/src/pages/companies/companies-sorts.tsx
@@ -8,7 +8,7 @@ import {
} from '@/ui/icon/index';
import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql';
-export const availableSorts = [
+export const availableSorts: SortType[] = [
{
key: 'name',
label: 'Name',
@@ -34,4 +34,4 @@ export const availableSorts = [
label: 'Creation',
icon: ,
},
-] satisfies Array>;
+];
diff --git a/front/src/pages/people/People.tsx b/front/src/pages/people/People.tsx
index edbf396dc0..4ed08ad2ca 100644
--- a/front/src/pages/people/People.tsx
+++ b/front/src/pages/people/People.tsx
@@ -1,8 +1,8 @@
-import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
+import { useRecoilState } from 'recoil';
+import { v4 } from 'uuid';
-import { GET_PEOPLE } from '@/people/queries';
import { PeopleTable } from '@/people/table/components/PeopleTable';
import { TableActionBarButtonCreateActivityPeople } from '@/people/table/components/TableActionBarButtonCreateActivityPeople';
import { TableActionBarButtonDeletePeople } from '@/people/table/components/TableActionBarButtonDeletePeople';
@@ -10,6 +10,7 @@ import { IconUser } from '@/ui/icon';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar';
import { TableContext } from '@/ui/table/states/TableContext';
+import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useInsertOnePersonMutation } from '~/generated/graphql';
@@ -20,16 +21,33 @@ const StyledTableContainer = styled.div`
export function People() {
const [insertOnePerson] = useInsertOnePersonMutation();
+ const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState);
async function handleAddButtonClick() {
+ const newPersonId: string = v4();
await insertOnePerson({
variables: {
data: {
+ id: newPersonId,
firstName: '',
lastName: '',
},
},
- refetchQueries: [getOperationName(GET_PEOPLE) ?? ''],
+ optimisticResponse: {
+ __typename: 'Mutation',
+ createOnePerson: {
+ __typename: 'Person',
+ id: newPersonId,
+ firstName: '',
+ lastName: '',
+ displayName: '',
+ createdAt: '',
+ },
+ },
+ update: (cache, { data }) => {
+ data?.createOnePerson?.id &&
+ setTableRowIds([data?.createOnePerson.id, ...tableRowIds]);
+ },
});
}
diff --git a/front/src/pages/people/people-sorts.tsx b/front/src/pages/people/people-sorts.tsx
index 9f866f5652..9ece4f20cb 100644
--- a/front/src/pages/people/people-sorts.tsx
+++ b/front/src/pages/people/people-sorts.tsx
@@ -12,19 +12,15 @@ import {
SortOrder as Order_By,
} from '~/generated/graphql';
-export const availableSorts = [
+export const availableSorts: SortType[] = [
{
key: 'fullname',
label: 'People',
icon: ,
- orderByTemplates: [
- (order: Order_By) => ({
- firstName: order,
- }),
- (order: Order_By) => ({
- lastName: order,
- }),
+ orderByTemplate: (order: Order_By) => [
+ { firstName: order },
+ { lastName: order },
],
},
{
@@ -32,7 +28,7 @@ export const availableSorts = [
label: 'Company',
icon: ,
- orderByTemplates: [(order: Order_By) => ({ company: { name: order } })],
+ orderByTemplate: (order: Order_By) => [{ company: { name: order } }],
},
{
key: 'email',
@@ -54,4 +50,4 @@ export const availableSorts = [
label: 'City',
icon: ,
},
-] satisfies Array>;
+];
diff --git a/front/src/sync-hooks/AuthAutoRouter.tsx b/front/src/sync-hooks/AuthAutoRouter.tsx
index 2ff66c3f3d..a29f94be94 100644
--- a/front/src/sync-hooks/AuthAutoRouter.tsx
+++ b/front/src/sync-hooks/AuthAutoRouter.tsx
@@ -186,14 +186,22 @@ export function AuthAutoRouter() {
label: 'Create Task',
type: CommandType.Create,
icon: ,
- onCommandClick: () => openCreateActivity(ActivityType.Task, entity),
+ onCommandClick: () =>
+ openCreateActivity(
+ ActivityType.Task,
+ entity ? [entity] : undefined,
+ ),
},
{
to: '',
label: 'Create Note',
type: CommandType.Create,
icon: ,
- onCommandClick: () => openCreateActivity(ActivityType.Note, entity),
+ onCommandClick: () =>
+ openCreateActivity(
+ ActivityType.Note,
+ entity ? [entity] : undefined,
+ ),
},
]);
break;
@@ -212,14 +220,22 @@ export function AuthAutoRouter() {
label: 'Create Task',
type: CommandType.Create,
icon: ,
- onCommandClick: () => openCreateActivity(ActivityType.Task, entity),
+ onCommandClick: () =>
+ openCreateActivity(
+ ActivityType.Task,
+ entity ? [entity] : undefined,
+ ),
},
{
to: '',
label: 'Create Note',
type: CommandType.Create,
icon: ,
- onCommandClick: () => openCreateActivity(ActivityType.Note, entity),
+ onCommandClick: () =>
+ openCreateActivity(
+ ActivityType.Note,
+ entity ? [entity] : undefined,
+ ),
},
]);
break;
diff --git a/front/src/utils/__tests__/is-url.test.ts b/front/src/utils/__tests__/is-url.test.ts
new file mode 100644
index 0000000000..2ee1e54297
--- /dev/null
+++ b/front/src/utils/__tests__/is-url.test.ts
@@ -0,0 +1,35 @@
+import { isURL } from '~/utils/is-url';
+
+describe('isURL', () => {
+ it(`should return false if null`, () => {
+ expect(isURL(null)).toBeFalsy();
+ });
+
+ it(`should return false if undefined`, () => {
+ expect(isURL(undefined)).toBeFalsy();
+ });
+
+ it(`should return true if string google`, () => {
+ expect(isURL('google')).toBeFalsy();
+ });
+
+ it(`should return true if string google.com`, () => {
+ expect(isURL('google.com')).toBeTruthy();
+ });
+
+ it(`should return true if string bbc.co.uk`, () => {
+ expect(isURL('bbc.co.uk')).toBeTruthy();
+ });
+
+ it(`should return true if string web.io`, () => {
+ expect(isURL('web.io')).toBeTruthy();
+ });
+
+ it(`should return true if string x.com`, () => {
+ expect(isURL('x.com')).toBeTruthy();
+ });
+
+ it(`should return true if string 2.com`, () => {
+ expect(isURL('2.com')).toBeTruthy();
+ });
+});
diff --git a/front/src/utils/is-url.ts b/front/src/utils/is-url.ts
new file mode 100644
index 0000000000..b6c1f01d51
--- /dev/null
+++ b/front/src/utils/is-url.ts
@@ -0,0 +1,10 @@
+import { isDefined } from './isDefined';
+
+export function isURL(url: string | undefined | null) {
+ return (
+ isDefined(url) &&
+ /^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/.test(
+ url,
+ )
+ );
+}
diff --git a/server/src/ability/ability.factory.ts b/server/src/ability/ability.factory.ts
index 3d275d804e..0eb1ffe930 100644
--- a/server/src/ability/ability.factory.ts
+++ b/server/src/ability/ability.factory.ts
@@ -17,7 +17,9 @@ import {
PipelineStage,
PipelineProgress,
UserSettings,
+ View,
ViewField,
+ ViewSort,
} from '@prisma/client';
import { AbilityAction } from './ability.action';
@@ -37,7 +39,9 @@ type SubjectsAbility = Subjects<{
PipelineProgress: PipelineProgress;
Attachment: Attachment;
UserSettings: UserSettings;
+ View: View;
ViewField: ViewField;
+ ViewSort: ViewSort;
}>;
export type AppAbility = PureAbility<
@@ -130,11 +134,22 @@ export class AbilityFactory {
workspaceId: workspace.id,
});
+ // View
+ can(AbilityAction.Read, 'View', { workspaceId: workspace.id });
+ can(AbilityAction.Create, 'View', { workspaceId: workspace.id });
+ can(AbilityAction.Update, 'View', { workspaceId: workspace.id });
+
// ViewField
can(AbilityAction.Read, 'ViewField', { workspaceId: workspace.id });
can(AbilityAction.Create, 'ViewField', { workspaceId: workspace.id });
can(AbilityAction.Update, 'ViewField', { workspaceId: workspace.id });
+ // ViewSort
+ can(AbilityAction.Read, 'ViewSort', { workspaceId: workspace.id });
+ can(AbilityAction.Create, 'ViewSort', { workspaceId: workspace.id });
+ can(AbilityAction.Update, 'ViewSort', { workspaceId: workspace.id });
+ can(AbilityAction.Delete, 'ViewSort', { workspaceId: workspace.id });
+
return build();
}
}
diff --git a/server/src/ability/ability.module.ts b/server/src/ability/ability.module.ts
index 3c5e3a82ce..af72699920 100644
--- a/server/src/ability/ability.module.ts
+++ b/server/src/ability/ability.module.ts
@@ -99,6 +99,12 @@ import {
ReadViewFieldAbilityHandler,
UpdateViewFieldAbilityHandler,
} from './handlers/view-field.ability-handler';
+import {
+ CreateViewSortAbilityHandler,
+ ReadViewSortAbilityHandler,
+ UpdateViewSortAbilityHandler,
+ DeleteViewSortAbilityHandler,
+} from './handlers/view-sort.ability-handler';
@Global()
@Module({
@@ -187,6 +193,11 @@ import {
ReadViewFieldAbilityHandler,
CreateViewFieldAbilityHandler,
UpdateViewFieldAbilityHandler,
+ // ViewSort
+ ReadViewSortAbilityHandler,
+ CreateViewSortAbilityHandler,
+ UpdateViewSortAbilityHandler,
+ DeleteViewSortAbilityHandler,
],
exports: [
AbilityFactory,
@@ -272,6 +283,11 @@ import {
ReadViewFieldAbilityHandler,
CreateViewFieldAbilityHandler,
UpdateViewFieldAbilityHandler,
+ // ViewSort
+ ReadViewSortAbilityHandler,
+ CreateViewSortAbilityHandler,
+ UpdateViewSortAbilityHandler,
+ DeleteViewSortAbilityHandler,
],
})
export class AbilityModule {}
diff --git a/server/src/ability/handlers/view-sort.ability-handler.ts b/server/src/ability/handlers/view-sort.ability-handler.ts
new file mode 100644
index 0000000000..262c4deea3
--- /dev/null
+++ b/server/src/ability/handlers/view-sort.ability-handler.ts
@@ -0,0 +1,122 @@
+import {
+ ExecutionContext,
+ Injectable,
+ NotFoundException,
+} from '@nestjs/common';
+import { GqlExecutionContext } from '@nestjs/graphql';
+
+import { subject } from '@casl/ability';
+
+import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
+
+import { AbilityAction } from 'src/ability/ability.action';
+import { AppAbility } from 'src/ability/ability.factory';
+import {
+ convertToWhereInput,
+ relationAbilityChecker,
+} from 'src/ability/ability.util';
+import { PrismaService } from 'src/database/prisma.service';
+import { assert } from 'src/utils/assert';
+import { ViewSortWhereUniqueInput } from 'src/core/@generated/view-sort/view-sort-where-unique.input';
+import { ViewSortWhereInput } from 'src/core/@generated/view-sort/view-sort-where.input';
+
+class ViewSortArgs {
+ where?: ViewSortWhereInput | ViewSortWhereUniqueInput;
+ [key: string]: any;
+}
+
+const isViewSortWhereUniqueInput = (
+ input: ViewSortWhereInput | ViewSortWhereUniqueInput,
+): input is ViewSortWhereUniqueInput => 'viewId_key' in input;
+
+@Injectable()
+export class ReadViewSortAbilityHandler implements IAbilityHandler {
+ handle(ability: AppAbility) {
+ return ability.can(AbilityAction.Read, 'ViewSort');
+ }
+}
+
+@Injectable()
+export class CreateViewSortAbilityHandler 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(
+ 'ViewSort',
+ ability,
+ this.prismaService.client,
+ args,
+ );
+
+ if (!allowed) {
+ return false;
+ }
+
+ return ability.can(AbilityAction.Create, 'ViewSort');
+ }
+}
+
+@Injectable()
+export class UpdateViewSortAbilityHandler implements IAbilityHandler {
+ constructor(private readonly prismaService: PrismaService) {}
+
+ async handle(ability: AppAbility, context: ExecutionContext) {
+ const gqlContext = GqlExecutionContext.create(context);
+ const args = gqlContext.getArgs();
+ const viewSort = await this.prismaService.client.viewSort.findFirst({
+ where:
+ args.where && isViewSortWhereUniqueInput(args.where)
+ ? args.where.viewId_key
+ : args.where,
+ });
+ assert(viewSort, '', NotFoundException);
+
+ const allowed = await relationAbilityChecker(
+ 'ViewSort',
+ ability,
+ this.prismaService.client,
+ args,
+ );
+
+ if (!allowed) {
+ return false;
+ }
+
+ return ability.can(AbilityAction.Update, subject('ViewSort', viewSort));
+ }
+}
+
+@Injectable()
+export class DeleteViewSortAbilityHandler implements IAbilityHandler {
+ constructor(private readonly prismaService: PrismaService) {}
+
+ async handle(ability: AppAbility, context: ExecutionContext) {
+ const gqlContext = GqlExecutionContext.create(context);
+ const args = gqlContext.getArgs();
+ const where = convertToWhereInput(
+ args.where && isViewSortWhereUniqueInput(args.where)
+ ? args.where.viewId_key
+ : args.where,
+ );
+ const viewSorts = await this.prismaService.client.viewSort.findMany({
+ where,
+ });
+ assert(viewSorts.length, '', NotFoundException);
+
+ for (const viewSort of viewSorts) {
+ const allowed = ability.can(
+ AbilityAction.Delete,
+ subject('ViewSort', viewSort),
+ );
+
+ if (!allowed) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/server/src/ability/handlers/view.ability-handler.ts b/server/src/ability/handlers/view.ability-handler.ts
new file mode 100644
index 0000000000..eec45c15c4
--- /dev/null
+++ b/server/src/ability/handlers/view.ability-handler.ts
@@ -0,0 +1,79 @@
+import {
+ ExecutionContext,
+ Injectable,
+ NotFoundException,
+} from '@nestjs/common';
+import { GqlExecutionContext } from '@nestjs/graphql';
+
+import { subject } from '@casl/ability';
+
+import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
+
+import { AbilityAction } from 'src/ability/ability.action';
+import { AppAbility } from 'src/ability/ability.factory';
+import { relationAbilityChecker } from 'src/ability/ability.util';
+import { ViewWhereInput } from 'src/core/@generated/view/view-where.input';
+import { PrismaService } from 'src/database/prisma.service';
+import { assert } from 'src/utils/assert';
+
+class ViewArgs {
+ where?: ViewWhereInput;
+ [key: string]: any;
+}
+
+@Injectable()
+export class ReadViewAbilityHandler implements IAbilityHandler {
+ handle(ability: AppAbility) {
+ return ability.can(AbilityAction.Read, 'View');
+ }
+}
+
+@Injectable()
+export class CreateViewAbilityHandler 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(
+ 'View',
+ ability,
+ this.prismaService.client,
+ args,
+ );
+
+ if (!allowed) {
+ return false;
+ }
+
+ return ability.can(AbilityAction.Create, 'View');
+ }
+}
+
+@Injectable()
+export class UpdateViewAbilityHandler implements IAbilityHandler {
+ constructor(private readonly prismaService: PrismaService) {}
+
+ async handle(ability: AppAbility, context: ExecutionContext) {
+ const gqlContext = GqlExecutionContext.create(context);
+ const args = gqlContext.getArgs();
+ const view = await this.prismaService.client.view.findFirst({
+ where: args.where,
+ });
+ assert(view, '', NotFoundException);
+
+ const allowed = await relationAbilityChecker(
+ 'View',
+ ability,
+ this.prismaService.client,
+ args,
+ );
+
+ if (!allowed) {
+ return false;
+ }
+
+ return ability.can(AbilityAction.Update, subject('View', view));
+ }
+}
diff --git a/server/src/core/view/resolvers/view-field.resolver.ts b/server/src/core/view/resolvers/view-field.resolver.ts
index ea9a94cdc6..09d9efd183 100644
--- a/server/src/core/view/resolvers/view-field.resolver.ts
+++ b/server/src/core/view/resolvers/view-field.resolver.ts
@@ -45,7 +45,7 @@ export class ViewFieldResolver {
): Promise> {
return this.viewFieldService.create({
data: {
- ...args.data,
+ ...(args.data as Prisma.ViewFieldCreateInput),
workspace: { connect: { id: workspace.id } },
},
select: prismaSelect.value,
diff --git a/server/src/core/view/resolvers/view-sort.resolver.spec.ts b/server/src/core/view/resolvers/view-sort.resolver.spec.ts
new file mode 100644
index 0000000000..57ae3f7289
--- /dev/null
+++ b/server/src/core/view/resolvers/view-sort.resolver.spec.ts
@@ -0,0 +1,32 @@
+import { Test, TestingModule } from '@nestjs/testing';
+
+import { ViewSortService } from 'src/core/view/services/view-sort.service';
+import { AbilityFactory } from 'src/ability/ability.factory';
+
+import { ViewSortResolver } from './view-sort.resolver';
+
+describe('ViewSortResolver', () => {
+ let resolver: ViewSortResolver;
+
+ beforeEach(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [
+ ViewSortResolver,
+ {
+ provide: ViewSortService,
+ useValue: {},
+ },
+ {
+ provide: AbilityFactory,
+ useValue: {},
+ },
+ ],
+ }).compile();
+
+ resolver = module.get(ViewSortResolver);
+ });
+
+ it('should be defined', () => {
+ expect(resolver).toBeDefined();
+ });
+});
diff --git a/server/src/core/view/resolvers/view-sort.resolver.ts b/server/src/core/view/resolvers/view-sort.resolver.ts
new file mode 100644
index 0000000000..6be437b4ab
--- /dev/null
+++ b/server/src/core/view/resolvers/view-sort.resolver.ts
@@ -0,0 +1,102 @@
+import { UseGuards } from '@nestjs/common';
+import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
+
+import { accessibleBy } from '@casl/prisma';
+import { Prisma, Workspace } from '@prisma/client';
+
+import { AppAbility } from 'src/ability/ability.factory';
+import {
+ CreateViewSortAbilityHandler,
+ DeleteViewSortAbilityHandler,
+ ReadViewSortAbilityHandler,
+ UpdateViewSortAbilityHandler,
+} from 'src/ability/handlers/view-sort.ability-handler';
+import { FindManyViewSortArgs } from 'src/core/@generated/view-sort/find-many-view-sort.args';
+import { ViewSort } from 'src/core/@generated/view-sort/view-sort.model';
+import { ViewSortService } from 'src/core/view/services/view-sort.service';
+import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
+import {
+ PrismaSelect,
+ PrismaSelector,
+} from 'src/decorators/prisma-select.decorator';
+import { UserAbility } from 'src/decorators/user-ability.decorator';
+import { AbilityGuard } from 'src/guards/ability.guard';
+import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
+import { UpdateOneViewSortArgs } from 'src/core/@generated/view-sort/update-one-view-sort.args';
+import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
+import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
+import { DeleteManyViewSortArgs } from 'src/core/@generated/view-sort/delete-many-view-sort.args';
+import { CreateManyViewSortArgs } from 'src/core/@generated/view-sort/create-many-view-sort.args';
+
+@UseGuards(JwtAuthGuard)
+@Resolver(() => ViewSort)
+export class ViewSortResolver {
+ constructor(private readonly viewSortService: ViewSortService) {}
+
+ @Mutation(() => AffectedRows)
+ @UseGuards(AbilityGuard)
+ @CheckAbilities(CreateViewSortAbilityHandler)
+ async createManyViewSort(
+ @Args() args: CreateManyViewSortArgs,
+ @AuthWorkspace() workspace: Workspace,
+ ): Promise {
+ return this.viewSortService.createMany({
+ data: args.data.map((data) => ({
+ ...data,
+ workspaceId: workspace.id,
+ })),
+ });
+ }
+
+ @Query(() => [ViewSort])
+ @UseGuards(AbilityGuard)
+ @CheckAbilities(ReadViewSortAbilityHandler)
+ async findManyViewSort(
+ @Args() args: FindManyViewSortArgs,
+ @UserAbility() ability: AppAbility,
+ @PrismaSelector({ modelName: 'ViewSort' })
+ prismaSelect: PrismaSelect<'ViewSort'>,
+ ): Promise[]> {
+ return this.viewSortService.findMany({
+ where: args.where
+ ? {
+ AND: [args.where, accessibleBy(ability).ViewSort],
+ }
+ : accessibleBy(ability).ViewSort,
+ orderBy: args.orderBy,
+ cursor: args.cursor,
+ take: args.take,
+ skip: args.skip,
+ distinct: args.distinct,
+ select: prismaSelect.value,
+ });
+ }
+
+ @Mutation(() => ViewSort)
+ @UseGuards(AbilityGuard)
+ @CheckAbilities(UpdateViewSortAbilityHandler)
+ async updateOneViewSort(
+ @Args() args: UpdateOneViewSortArgs,
+ @PrismaSelector({ modelName: 'ViewSort' })
+ prismaSelect: PrismaSelect<'ViewSort'>,
+ ): Promise> {
+ return this.viewSortService.update({
+ data: args.data,
+ where: args.where,
+ select: prismaSelect.value,
+ } as Prisma.ViewSortUpdateArgs);
+ }
+
+ @Mutation(() => AffectedRows, {
+ nullable: false,
+ })
+ @UseGuards(AbilityGuard)
+ @CheckAbilities(DeleteViewSortAbilityHandler)
+ async deleteManyViewSort(
+ @Args() args: DeleteManyViewSortArgs,
+ ): Promise {
+ return this.viewSortService.deleteMany({
+ where: args.where,
+ });
+ }
+}
diff --git a/server/src/core/view/services/view-sort.service.spec.ts b/server/src/core/view/services/view-sort.service.spec.ts
new file mode 100644
index 0000000000..aa45348767
--- /dev/null
+++ b/server/src/core/view/services/view-sort.service.spec.ts
@@ -0,0 +1,28 @@
+import { Test, TestingModule } from '@nestjs/testing';
+
+import { PrismaService } from 'src/database/prisma.service';
+import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
+
+import { ViewSortService } from './view-sort.service';
+
+describe('ViewSortService', () => {
+ let service: ViewSortService;
+
+ beforeEach(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [
+ ViewSortService,
+ {
+ provide: PrismaService,
+ useValue: prismaMock,
+ },
+ ],
+ }).compile();
+
+ service = module.get(ViewSortService);
+ });
+
+ it('should be defined', () => {
+ expect(service).toBeDefined();
+ });
+});
diff --git a/server/src/core/view/services/view-sort.service.ts b/server/src/core/view/services/view-sort.service.ts
new file mode 100644
index 0000000000..f6d22b0fbe
--- /dev/null
+++ b/server/src/core/view/services/view-sort.service.ts
@@ -0,0 +1,39 @@
+import { Injectable } from '@nestjs/common';
+
+import { PrismaService } from 'src/database/prisma.service';
+
+@Injectable()
+export class ViewSortService {
+ constructor(private readonly prismaService: PrismaService) {}
+
+ // Find
+ findFirst = this.prismaService.client.viewSort.findFirst;
+ findFirstOrThrow = this.prismaService.client.viewSort.findFirstOrThrow;
+
+ findUnique = this.prismaService.client.viewSort.findUnique;
+ findUniqueOrThrow = this.prismaService.client.viewSort.findUniqueOrThrow;
+
+ findMany = this.prismaService.client.viewSort.findMany;
+
+ // Create
+ create = this.prismaService.client.viewSort.create;
+ createMany = this.prismaService.client.viewSort.createMany;
+
+ // Update
+ update = this.prismaService.client.viewSort.update;
+ upsert = this.prismaService.client.viewSort.upsert;
+ updateMany = this.prismaService.client.viewSort.updateMany;
+
+ // Delete
+ delete = this.prismaService.client.viewSort.delete;
+ deleteMany = this.prismaService.client.viewSort.deleteMany;
+
+ // Aggregate
+ aggregate = this.prismaService.client.viewSort.aggregate;
+
+ // Count
+ count = this.prismaService.client.viewSort.count;
+
+ // GroupBy
+ groupBy = this.prismaService.client.viewSort.groupBy;
+}
diff --git a/server/src/core/view/view.module.ts b/server/src/core/view/view.module.ts
index f22071866c..a82b050e0e 100644
--- a/server/src/core/view/view.module.ts
+++ b/server/src/core/view/view.module.ts
@@ -2,8 +2,15 @@ import { Module } from '@nestjs/common';
import { ViewFieldService } from './services/view-field.service';
import { ViewFieldResolver } from './resolvers/view-field.resolver';
+import { ViewSortService } from './services/view-sort.service';
+import { ViewSortResolver } from './resolvers/view-sort.resolver';
@Module({
- providers: [ViewFieldService, ViewFieldResolver],
+ providers: [
+ ViewFieldService,
+ ViewSortService,
+ ViewFieldResolver,
+ ViewSortResolver,
+ ],
})
export class ViewModule {}
diff --git a/server/src/core/workspace/services/workspace.service.ts b/server/src/core/workspace/services/workspace.service.ts
index 19ce7105b7..a56a744f87 100644
--- a/server/src/core/workspace/services/workspace.service.ts
+++ b/server/src/core/workspace/services/workspace.service.ts
@@ -111,7 +111,9 @@ export class WorkspaceService {
comment,
activityTarget,
activity,
+ view,
viewField,
+ viewSort,
} = this.prismaService.client;
const activitys = await activity.findMany({
@@ -151,9 +153,15 @@ export class WorkspaceService {
activity.deleteMany({
where,
}),
+ view.deleteMany({
+ where,
+ }),
viewField.deleteMany({
where,
}),
+ viewSort.deleteMany({
+ where,
+ }),
refreshToken.deleteMany({
where: { userId },
}),
diff --git a/server/src/database/migrations/20230809143432_add_views_table/migration.sql b/server/src/database/migrations/20230809143432_add_views_table/migration.sql
new file mode 100644
index 0000000000..17ff87f2ec
--- /dev/null
+++ b/server/src/database/migrations/20230809143432_add_views_table/migration.sql
@@ -0,0 +1,40 @@
+/*
+ Warnings:
+
+ - A unique constraint covering the columns `[workspaceId,viewId,objectName,fieldName]` on the table `viewFields` will be added. If there are existing duplicate values, this will fail.
+
+*/
+-- CreateEnum
+CREATE TYPE "ViewType" AS ENUM ('Table', 'Pipeline');
+
+-- AlterTable
+ALTER TABLE "viewFields" ADD COLUMN "viewId" TEXT;
+
+-- CreateTable
+CREATE TABLE "views" (
+ "id" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "objectId" TEXT NOT NULL,
+ "type" "ViewType" NOT NULL,
+ "workspaceId" TEXT NOT NULL,
+
+ CONSTRAINT "views_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "views_workspaceId_type_objectId_name_key" ON "views"("workspaceId", "type", "objectId", "name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "viewFields_workspaceId_viewId_objectName_fieldName_key" ON "viewFields"("workspaceId", "viewId", "objectName", "fieldName");
+
+-- AddForeignKey
+ALTER TABLE "pipeline_progresses" ADD CONSTRAINT "pipeline_progresses_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "pipeline_progresses" ADD CONSTRAINT "pipeline_progresses_personId_fkey" FOREIGN KEY ("personId") REFERENCES "people"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "views" ADD CONSTRAINT "views_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "viewFields" ADD CONSTRAINT "viewFields_viewId_fkey" FOREIGN KEY ("viewId") REFERENCES "views"("id") ON DELETE SET NULL ON UPDATE CASCADE;
diff --git a/server/src/database/migrations/20230809143836_add_view_sorts_table/migration.sql b/server/src/database/migrations/20230809143836_add_view_sorts_table/migration.sql
new file mode 100644
index 0000000000..870add1fa7
--- /dev/null
+++ b/server/src/database/migrations/20230809143836_add_view_sorts_table/migration.sql
@@ -0,0 +1,19 @@
+-- CreateEnum
+CREATE TYPE "ViewSortDirection" AS ENUM ('asc', 'desc');
+
+-- CreateTable
+CREATE TABLE "viewSorts" (
+ "direction" "ViewSortDirection" NOT NULL,
+ "key" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "viewId" TEXT NOT NULL,
+ "workspaceId" TEXT NOT NULL,
+
+ CONSTRAINT "viewSorts_pkey" PRIMARY KEY ("viewId","key")
+);
+
+-- AddForeignKey
+ALTER TABLE "viewSorts" ADD CONSTRAINT "viewSorts_viewId_fkey" FOREIGN KEY ("viewId") REFERENCES "views"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "viewSorts" ADD CONSTRAINT "viewSorts_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/server/src/database/schema.prisma b/server/src/database/schema.prisma
index 9e23288956..b02c78a93d 100644
--- a/server/src/database/schema.prisma
+++ b/server/src/database/schema.prisma
@@ -174,6 +174,8 @@ model Workspace {
pipelineProgresses PipelineProgress[]
activityTargets ActivityTarget[]
viewFields ViewField[]
+ views View[]
+ viewSorts ViewSort[]
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
@@ -266,7 +268,7 @@ model Person {
linkedinUrl String?
/// @Validator.IsString()
/// @Validator.IsOptional()
- xUrl String?
+ xUrl String?
/// @Validator.IsString()
/// @Validator.IsOptional()
jobTitle String?
@@ -557,6 +559,53 @@ model Attachment {
@@map("attachments")
}
+enum ViewType {
+ Table
+ Pipeline
+}
+
+model View {
+ /// @Validator.IsString()
+ /// @Validator.IsOptional()
+ id String @id @default(uuid())
+
+ fields ViewField[]
+ name String
+ objectId String
+ sorts ViewSort[]
+ type ViewType
+
+ /// @TypeGraphQL.omit(input: true, output: true)
+ workspace Workspace @relation(fields: [workspaceId], references: [id])
+ /// @TypeGraphQL.omit(input: true, output: true)
+ workspaceId String
+
+ @@unique([workspaceId, type, objectId, name])
+ @@map("views")
+}
+
+enum ViewSortDirection {
+ asc
+ desc
+}
+
+model ViewSort {
+ direction ViewSortDirection
+ key String
+ name String
+
+ view View @relation(fields: [viewId], references: [id])
+ viewId String
+
+ /// @TypeGraphQL.omit(input: true, output: true)
+ workspace Workspace @relation(fields: [workspaceId], references: [id])
+ /// @TypeGraphQL.omit(input: true, output: true)
+ workspaceId String
+
+ @@id([viewId, key])
+ @@map("viewSorts")
+}
+
model ViewField {
/// @Validator.IsString()
/// @Validator.IsOptional()
@@ -568,10 +617,14 @@ model ViewField {
objectName String
sizeInPx Int
+ view View? @relation(fields: [viewId], references: [id])
+ viewId String?
+
/// @TypeGraphQL.omit(input: true, output: true)
workspace Workspace @relation(fields: [workspaceId], references: [id])
/// @TypeGraphQL.omit(input: true, output: true)
workspaceId String
+ @@unique([workspaceId, viewId, objectName, fieldName])
@@map("viewFields")
}
diff --git a/server/src/utils/prisma-select/model-select-map.ts b/server/src/utils/prisma-select/model-select-map.ts
index 653508af35..052371e71d 100644
--- a/server/src/utils/prisma-select/model-select-map.ts
+++ b/server/src/utils/prisma-select/model-select-map.ts
@@ -16,5 +16,7 @@ export type ModelSelectMap = {
PipelineStage: Prisma.PipelineStageSelect;
PipelineProgress: Prisma.PipelineProgressSelect;
Attachment: Prisma.AttachmentSelect;
+ View: Prisma.ViewSelect;
+ ViewSort: Prisma.ViewSortSelect;
ViewField: Prisma.ViewFieldSelect;
};