feat: persist view filters and sorts on Update View button click (#1290)
* feat: add viewFilters table Closes #1121 * feat: add Update View button + Create View dropdown Closes #1124, #1289 * feat: add View Filter resolvers * feat: persist view filters and sorts on Update View button click Closes #1123 * refactor: code review - Rename recoil selectors - Rename filters `field` property to `key`
This commit is contained in:
@ -829,6 +829,13 @@ export type EnumPipelineProgressableTypeFilter = {
|
||||
notIn?: InputMaybe<Array<PipelineProgressableType>>;
|
||||
};
|
||||
|
||||
export type EnumViewFilterOperandFilter = {
|
||||
equals?: InputMaybe<ViewFilterOperand>;
|
||||
in?: InputMaybe<Array<ViewFilterOperand>>;
|
||||
not?: InputMaybe<NestedEnumViewFilterOperandFilter>;
|
||||
notIn?: InputMaybe<Array<ViewFilterOperand>>;
|
||||
};
|
||||
|
||||
export type EnumViewSortDirectionFilter = {
|
||||
equals?: InputMaybe<ViewSortDirection>;
|
||||
in?: InputMaybe<Array<ViewSortDirection>>;
|
||||
@ -969,6 +976,7 @@ export type Mutation = {
|
||||
createManyPerson: AffectedRows;
|
||||
createManyView: AffectedRows;
|
||||
createManyViewField: AffectedRows;
|
||||
createManyViewFilter: AffectedRows;
|
||||
createManyViewSort: AffectedRows;
|
||||
createOneActivity: Activity;
|
||||
createOneComment: Comment;
|
||||
@ -983,6 +991,7 @@ export type Mutation = {
|
||||
deleteManyPerson: AffectedRows;
|
||||
deleteManyPipelineProgress: AffectedRows;
|
||||
deleteManyView: AffectedRows;
|
||||
deleteManyViewFilter: AffectedRows;
|
||||
deleteManyViewSort: AffectedRows;
|
||||
deleteUserAccount: User;
|
||||
deleteWorkspaceMember: WorkspaceMember;
|
||||
@ -996,6 +1005,7 @@ export type Mutation = {
|
||||
updateOnePipelineStage?: Maybe<PipelineStage>;
|
||||
updateOneView: View;
|
||||
updateOneViewField: ViewField;
|
||||
updateOneViewFilter: ViewFilter;
|
||||
updateOneViewSort: ViewSort;
|
||||
updateUser: User;
|
||||
updateWorkspace: Workspace;
|
||||
@ -1060,6 +1070,12 @@ export type MutationCreateManyViewFieldArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateManyViewFilterArgs = {
|
||||
data: Array<ViewFilterCreateManyInput>;
|
||||
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateManyViewSortArgs = {
|
||||
data: Array<ViewSortCreateManyInput>;
|
||||
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
|
||||
@ -1126,6 +1142,11 @@ export type MutationDeleteManyViewArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteManyViewFilterArgs = {
|
||||
where?: InputMaybe<ViewFilterWhereInput>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteManyViewSortArgs = {
|
||||
where?: InputMaybe<ViewSortWhereInput>;
|
||||
};
|
||||
@ -1195,6 +1216,12 @@ export type MutationUpdateOneViewFieldArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateOneViewFilterArgs = {
|
||||
data: ViewFilterUpdateInput;
|
||||
where: ViewFilterWhereUniqueInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateOneViewSortArgs = {
|
||||
data: ViewSortUpdateInput;
|
||||
where: ViewSortWhereUniqueInput;
|
||||
@ -1305,6 +1332,13 @@ export type NestedEnumPipelineProgressableTypeFilter = {
|
||||
notIn?: InputMaybe<Array<PipelineProgressableType>>;
|
||||
};
|
||||
|
||||
export type NestedEnumViewFilterOperandFilter = {
|
||||
equals?: InputMaybe<ViewFilterOperand>;
|
||||
in?: InputMaybe<Array<ViewFilterOperand>>;
|
||||
not?: InputMaybe<NestedEnumViewFilterOperandFilter>;
|
||||
notIn?: InputMaybe<Array<ViewFilterOperand>>;
|
||||
};
|
||||
|
||||
export type NestedEnumViewSortDirectionFilter = {
|
||||
equals?: InputMaybe<ViewSortDirection>;
|
||||
in?: InputMaybe<Array<ViewSortDirection>>;
|
||||
@ -1930,6 +1964,7 @@ export type Query = {
|
||||
findManyUser: Array<User>;
|
||||
findManyView: Array<View>;
|
||||
findManyViewField: Array<ViewField>;
|
||||
findManyViewFilter: Array<ViewFilter>;
|
||||
findManyViewSort: Array<ViewSort>;
|
||||
findManyWorkspaceMember: Array<WorkspaceMember>;
|
||||
findUniqueCompany: Company;
|
||||
@ -2038,6 +2073,16 @@ export type QueryFindManyViewFieldArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindManyViewFilterArgs = {
|
||||
cursor?: InputMaybe<ViewFilterWhereUniqueInput>;
|
||||
distinct?: InputMaybe<Array<ViewFilterScalarFieldEnum>>;
|
||||
orderBy?: InputMaybe<Array<ViewFilterOrderByWithRelationInput>>;
|
||||
skip?: InputMaybe<Scalars['Int']>;
|
||||
take?: InputMaybe<Scalars['Int']>;
|
||||
where?: InputMaybe<ViewFilterWhereInput>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindManyViewSortArgs = {
|
||||
cursor?: InputMaybe<ViewSortWhereUniqueInput>;
|
||||
distinct?: InputMaybe<Array<ViewSortScalarFieldEnum>>;
|
||||
@ -2349,6 +2394,7 @@ export type Verify = {
|
||||
export type View = {
|
||||
__typename?: 'View';
|
||||
fields?: Maybe<Array<ViewField>>;
|
||||
filters?: Maybe<Array<ViewFilter>>;
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
objectId: Scalars['String'];
|
||||
@ -2478,8 +2524,111 @@ export type ViewFieldWorkspaceIdViewIdObjectNameFieldNameCompoundUniqueInput = {
|
||||
viewId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ViewFilter = {
|
||||
__typename?: 'ViewFilter';
|
||||
displayValue: Scalars['String'];
|
||||
key: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
operand: ViewFilterOperand;
|
||||
value: Scalars['String'];
|
||||
view: View;
|
||||
viewId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ViewFilterCreateManyInput = {
|
||||
displayValue: Scalars['String'];
|
||||
key: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
operand: ViewFilterOperand;
|
||||
value: Scalars['String'];
|
||||
viewId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ViewFilterListRelationFilter = {
|
||||
every?: InputMaybe<ViewFilterWhereInput>;
|
||||
none?: InputMaybe<ViewFilterWhereInput>;
|
||||
some?: InputMaybe<ViewFilterWhereInput>;
|
||||
};
|
||||
|
||||
export enum ViewFilterOperand {
|
||||
Contains = 'Contains',
|
||||
DoesNotContain = 'DoesNotContain',
|
||||
GreaterThan = 'GreaterThan',
|
||||
Is = 'Is',
|
||||
IsNot = 'IsNot',
|
||||
LessThan = 'LessThan'
|
||||
}
|
||||
|
||||
export type ViewFilterOrderByRelationAggregateInput = {
|
||||
_count?: InputMaybe<SortOrder>;
|
||||
};
|
||||
|
||||
export type ViewFilterOrderByWithRelationInput = {
|
||||
displayValue?: InputMaybe<SortOrder>;
|
||||
key?: InputMaybe<SortOrder>;
|
||||
name?: InputMaybe<SortOrder>;
|
||||
operand?: InputMaybe<SortOrder>;
|
||||
value?: InputMaybe<SortOrder>;
|
||||
view?: InputMaybe<ViewOrderByWithRelationInput>;
|
||||
viewId?: InputMaybe<SortOrder>;
|
||||
};
|
||||
|
||||
export enum ViewFilterScalarFieldEnum {
|
||||
DisplayValue = 'displayValue',
|
||||
Key = 'key',
|
||||
Name = 'name',
|
||||
Operand = 'operand',
|
||||
Value = 'value',
|
||||
ViewId = 'viewId',
|
||||
WorkspaceId = 'workspaceId'
|
||||
}
|
||||
|
||||
export type ViewFilterUpdateInput = {
|
||||
displayValue?: InputMaybe<Scalars['String']>;
|
||||
key?: InputMaybe<Scalars['String']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
operand?: InputMaybe<ViewFilterOperand>;
|
||||
value?: InputMaybe<Scalars['String']>;
|
||||
view?: InputMaybe<ViewUpdateOneRequiredWithoutFiltersNestedInput>;
|
||||
};
|
||||
|
||||
export type ViewFilterUpdateManyWithoutViewNestedInput = {
|
||||
connect?: InputMaybe<Array<ViewFilterWhereUniqueInput>>;
|
||||
disconnect?: InputMaybe<Array<ViewFilterWhereUniqueInput>>;
|
||||
set?: InputMaybe<Array<ViewFilterWhereUniqueInput>>;
|
||||
};
|
||||
|
||||
export type ViewFilterUpdateManyWithoutWorkspaceNestedInput = {
|
||||
connect?: InputMaybe<Array<ViewFilterWhereUniqueInput>>;
|
||||
disconnect?: InputMaybe<Array<ViewFilterWhereUniqueInput>>;
|
||||
set?: InputMaybe<Array<ViewFilterWhereUniqueInput>>;
|
||||
};
|
||||
|
||||
export type ViewFilterViewIdKeyCompoundUniqueInput = {
|
||||
key: Scalars['String'];
|
||||
viewId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ViewFilterWhereInput = {
|
||||
AND?: InputMaybe<Array<ViewFilterWhereInput>>;
|
||||
NOT?: InputMaybe<Array<ViewFilterWhereInput>>;
|
||||
OR?: InputMaybe<Array<ViewFilterWhereInput>>;
|
||||
displayValue?: InputMaybe<StringFilter>;
|
||||
key?: InputMaybe<StringFilter>;
|
||||
name?: InputMaybe<StringFilter>;
|
||||
operand?: InputMaybe<EnumViewFilterOperandFilter>;
|
||||
value?: InputMaybe<StringFilter>;
|
||||
view?: InputMaybe<ViewRelationFilter>;
|
||||
viewId?: InputMaybe<StringFilter>;
|
||||
};
|
||||
|
||||
export type ViewFilterWhereUniqueInput = {
|
||||
viewId_key?: InputMaybe<ViewFilterViewIdKeyCompoundUniqueInput>;
|
||||
};
|
||||
|
||||
export type ViewOrderByWithRelationInput = {
|
||||
fields?: InputMaybe<ViewFieldOrderByRelationAggregateInput>;
|
||||
filters?: InputMaybe<ViewFilterOrderByRelationAggregateInput>;
|
||||
id?: InputMaybe<SortOrder>;
|
||||
name?: InputMaybe<SortOrder>;
|
||||
objectId?: InputMaybe<SortOrder>;
|
||||
@ -2593,6 +2742,7 @@ export enum ViewType {
|
||||
|
||||
export type ViewUpdateInput = {
|
||||
fields?: InputMaybe<ViewFieldUpdateManyWithoutViewNestedInput>;
|
||||
filters?: InputMaybe<ViewFilterUpdateManyWithoutViewNestedInput>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
objectId?: InputMaybe<Scalars['String']>;
|
||||
@ -2606,6 +2756,10 @@ export type ViewUpdateManyWithoutWorkspaceNestedInput = {
|
||||
set?: InputMaybe<Array<ViewWhereUniqueInput>>;
|
||||
};
|
||||
|
||||
export type ViewUpdateOneRequiredWithoutFiltersNestedInput = {
|
||||
connect?: InputMaybe<ViewWhereUniqueInput>;
|
||||
};
|
||||
|
||||
export type ViewUpdateOneRequiredWithoutSortsNestedInput = {
|
||||
connect?: InputMaybe<ViewWhereUniqueInput>;
|
||||
};
|
||||
@ -2620,6 +2774,7 @@ export type ViewWhereInput = {
|
||||
NOT?: InputMaybe<Array<ViewWhereInput>>;
|
||||
OR?: InputMaybe<Array<ViewWhereInput>>;
|
||||
fields?: InputMaybe<ViewFieldListRelationFilter>;
|
||||
filters?: InputMaybe<ViewFilterListRelationFilter>;
|
||||
id?: InputMaybe<StringFilter>;
|
||||
name?: InputMaybe<StringFilter>;
|
||||
objectId?: InputMaybe<StringFilter>;
|
||||
@ -2657,6 +2812,7 @@ export type Workspace = {
|
||||
pipelines?: Maybe<Array<Pipeline>>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
viewFields?: Maybe<Array<ViewField>>;
|
||||
viewFilters?: Maybe<Array<ViewFilter>>;
|
||||
viewSorts?: Maybe<Array<ViewSort>>;
|
||||
views?: Maybe<Array<View>>;
|
||||
workspaceMember?: Maybe<Array<WorkspaceMember>>;
|
||||
@ -2741,6 +2897,7 @@ export type WorkspaceUpdateInput = {
|
||||
pipelines?: InputMaybe<PipelineUpdateManyWithoutWorkspaceNestedInput>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
viewFields?: InputMaybe<ViewFieldUpdateManyWithoutWorkspaceNestedInput>;
|
||||
viewFilters?: InputMaybe<ViewFilterUpdateManyWithoutWorkspaceNestedInput>;
|
||||
viewSorts?: InputMaybe<ViewSortUpdateManyWithoutWorkspaceNestedInput>;
|
||||
views?: InputMaybe<ViewUpdateManyWithoutWorkspaceNestedInput>;
|
||||
workspaceMember?: InputMaybe<WorkspaceMemberUpdateManyWithoutWorkspaceNestedInput>;
|
||||
@ -2838,7 +2995,7 @@ export type CreateEventMutationVariables = Exact<{
|
||||
|
||||
export type CreateEventMutation = { __typename?: 'Mutation', createEvent: { __typename?: 'Analytics', success: boolean } };
|
||||
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } };
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } };
|
||||
|
||||
export type ChallengeMutationVariables = Exact<{
|
||||
email: Scalars['String'];
|
||||
@ -2853,7 +3010,7 @@ export type ImpersonateMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type RenewTokenMutationVariables = Exact<{
|
||||
refreshToken: Scalars['String'];
|
||||
@ -2876,7 +3033,7 @@ export type VerifyMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type CheckUserExistsQueryVariables = Exact<{
|
||||
email: Scalars['String'];
|
||||
@ -3216,6 +3373,13 @@ export type CreateViewFieldsMutationVariables = Exact<{
|
||||
|
||||
export type CreateViewFieldsMutation = { __typename?: 'Mutation', createManyViewField: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
export type CreateViewFiltersMutationVariables = Exact<{
|
||||
data: Array<ViewFilterCreateManyInput> | ViewFilterCreateManyInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateViewFiltersMutation = { __typename?: 'Mutation', createManyViewFilter: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
export type CreateViewSortsMutationVariables = Exact<{
|
||||
data: Array<ViewSortCreateManyInput> | ViewSortCreateManyInput;
|
||||
}>;
|
||||
@ -3230,7 +3394,6 @@ export type CreateViewsMutationVariables = Exact<{
|
||||
|
||||
export type CreateViewsMutation = { __typename?: 'Mutation', createManyView: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
|
||||
export type DeleteViewsMutationVariables = Exact<{
|
||||
where: ViewWhereInput;
|
||||
}>;
|
||||
@ -3238,6 +3401,12 @@ export type DeleteViewsMutationVariables = Exact<{
|
||||
|
||||
export type DeleteViewsMutation = { __typename?: 'Mutation', deleteManyView: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
export type DeleteViewFiltersMutationVariables = Exact<{
|
||||
where: ViewFilterWhereInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteViewFiltersMutation = { __typename?: 'Mutation', deleteManyViewFilter: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
export type DeleteViewSortsMutationVariables = Exact<{
|
||||
where: ViewSortWhereInput;
|
||||
@ -3262,6 +3431,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 UpdateViewFilterMutationVariables = Exact<{
|
||||
data: ViewFilterUpdateInput;
|
||||
where: ViewFilterWhereUniqueInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateViewFilterMutation = { __typename?: 'Mutation', viewFilter: { __typename?: 'ViewFilter', displayValue: string, key: string, name: string, operand: ViewFilterOperand, value: string } };
|
||||
|
||||
export type UpdateViewSortMutationVariables = Exact<{
|
||||
data: ViewSortUpdateInput;
|
||||
where: ViewSortWhereUniqueInput;
|
||||
@ -3278,6 +3455,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 GetViewFiltersQueryVariables = Exact<{
|
||||
where?: InputMaybe<ViewFilterWhereInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetViewFiltersQuery = { __typename?: 'Query', viewFilters: Array<{ __typename?: 'ViewFilter', displayValue: string, key: string, name: string, operand: ViewFilterOperand, value: string }> };
|
||||
|
||||
export type GetViewSortsQueryVariables = Exact<{
|
||||
where?: InputMaybe<ViewSortWhereInput>;
|
||||
}>;
|
||||
@ -5934,6 +6118,39 @@ export function useCreateViewFieldsMutation(baseOptions?: Apollo.MutationHookOpt
|
||||
export type CreateViewFieldsMutationHookResult = ReturnType<typeof useCreateViewFieldsMutation>;
|
||||
export type CreateViewFieldsMutationResult = Apollo.MutationResult<CreateViewFieldsMutation>;
|
||||
export type CreateViewFieldsMutationOptions = Apollo.BaseMutationOptions<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>;
|
||||
export const CreateViewFiltersDocument = gql`
|
||||
mutation CreateViewFilters($data: [ViewFilterCreateManyInput!]!) {
|
||||
createManyViewFilter(data: $data) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type CreateViewFiltersMutationFn = Apollo.MutationFunction<CreateViewFiltersMutation, CreateViewFiltersMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateViewFiltersMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateViewFiltersMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateViewFiltersMutation` 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 [createViewFiltersMutation, { data, loading, error }] = useCreateViewFiltersMutation({
|
||||
* variables: {
|
||||
* data: // value for 'data'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateViewFiltersMutation(baseOptions?: Apollo.MutationHookOptions<CreateViewFiltersMutation, CreateViewFiltersMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateViewFiltersMutation, CreateViewFiltersMutationVariables>(CreateViewFiltersDocument, options);
|
||||
}
|
||||
export type CreateViewFiltersMutationHookResult = ReturnType<typeof useCreateViewFiltersMutation>;
|
||||
export type CreateViewFiltersMutationResult = Apollo.MutationResult<CreateViewFiltersMutation>;
|
||||
export type CreateViewFiltersMutationOptions = Apollo.BaseMutationOptions<CreateViewFiltersMutation, CreateViewFiltersMutationVariables>;
|
||||
export const CreateViewSortsDocument = gql`
|
||||
mutation CreateViewSorts($data: [ViewSortCreateManyInput!]!) {
|
||||
createManyViewSort(data: $data) {
|
||||
@ -6000,7 +6217,6 @@ export function useCreateViewsMutation(baseOptions?: Apollo.MutationHookOptions<
|
||||
export type CreateViewsMutationHookResult = ReturnType<typeof useCreateViewsMutation>;
|
||||
export type CreateViewsMutationResult = Apollo.MutationResult<CreateViewsMutation>;
|
||||
export type CreateViewsMutationOptions = Apollo.BaseMutationOptions<CreateViewsMutation, CreateViewsMutationVariables>;
|
||||
|
||||
export const DeleteViewsDocument = gql`
|
||||
mutation DeleteViews($where: ViewWhereInput!) {
|
||||
deleteManyView(where: $where) {
|
||||
@ -6034,7 +6250,39 @@ export function useDeleteViewsMutation(baseOptions?: Apollo.MutationHookOptions<
|
||||
export type DeleteViewsMutationHookResult = ReturnType<typeof useDeleteViewsMutation>;
|
||||
export type DeleteViewsMutationResult = Apollo.MutationResult<DeleteViewsMutation>;
|
||||
export type DeleteViewsMutationOptions = Apollo.BaseMutationOptions<DeleteViewsMutation, DeleteViewsMutationVariables>;
|
||||
export const DeleteViewFiltersDocument = gql`
|
||||
mutation DeleteViewFilters($where: ViewFilterWhereInput!) {
|
||||
deleteManyViewFilter(where: $where) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type DeleteViewFiltersMutationFn = Apollo.MutationFunction<DeleteViewFiltersMutation, DeleteViewFiltersMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useDeleteViewFiltersMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useDeleteViewFiltersMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useDeleteViewFiltersMutation` 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 [deleteViewFiltersMutation, { data, loading, error }] = useDeleteViewFiltersMutation({
|
||||
* variables: {
|
||||
* where: // value for 'where'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useDeleteViewFiltersMutation(baseOptions?: Apollo.MutationHookOptions<DeleteViewFiltersMutation, DeleteViewFiltersMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<DeleteViewFiltersMutation, DeleteViewFiltersMutationVariables>(DeleteViewFiltersDocument, options);
|
||||
}
|
||||
export type DeleteViewFiltersMutationHookResult = ReturnType<typeof useDeleteViewFiltersMutation>;
|
||||
export type DeleteViewFiltersMutationResult = Apollo.MutationResult<DeleteViewFiltersMutation>;
|
||||
export type DeleteViewFiltersMutationOptions = Apollo.BaseMutationOptions<DeleteViewFiltersMutation, DeleteViewFiltersMutationVariables>;
|
||||
export const DeleteViewSortsDocument = gql`
|
||||
mutation DeleteViewSorts($where: ViewSortWhereInput!) {
|
||||
deleteManyViewSort(where: $where) {
|
||||
@ -6141,6 +6389,44 @@ export function useUpdateViewFieldMutation(baseOptions?: Apollo.MutationHookOpti
|
||||
export type UpdateViewFieldMutationHookResult = ReturnType<typeof useUpdateViewFieldMutation>;
|
||||
export type UpdateViewFieldMutationResult = Apollo.MutationResult<UpdateViewFieldMutation>;
|
||||
export type UpdateViewFieldMutationOptions = Apollo.BaseMutationOptions<UpdateViewFieldMutation, UpdateViewFieldMutationVariables>;
|
||||
export const UpdateViewFilterDocument = gql`
|
||||
mutation UpdateViewFilter($data: ViewFilterUpdateInput!, $where: ViewFilterWhereUniqueInput!) {
|
||||
viewFilter: updateOneViewFilter(data: $data, where: $where) {
|
||||
displayValue
|
||||
key
|
||||
name
|
||||
operand
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UpdateViewFilterMutationFn = Apollo.MutationFunction<UpdateViewFilterMutation, UpdateViewFilterMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateViewFilterMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateViewFilterMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateViewFilterMutation` 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 [updateViewFilterMutation, { data, loading, error }] = useUpdateViewFilterMutation({
|
||||
* variables: {
|
||||
* data: // value for 'data'
|
||||
* where: // value for 'where'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateViewFilterMutation(baseOptions?: Apollo.MutationHookOptions<UpdateViewFilterMutation, UpdateViewFilterMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateViewFilterMutation, UpdateViewFilterMutationVariables>(UpdateViewFilterDocument, options);
|
||||
}
|
||||
export type UpdateViewFilterMutationHookResult = ReturnType<typeof useUpdateViewFilterMutation>;
|
||||
export type UpdateViewFilterMutationResult = Apollo.MutationResult<UpdateViewFilterMutation>;
|
||||
export type UpdateViewFilterMutationOptions = Apollo.BaseMutationOptions<UpdateViewFilterMutation, UpdateViewFilterMutationVariables>;
|
||||
export const UpdateViewSortDocument = gql`
|
||||
mutation UpdateViewSort($data: ViewSortUpdateInput!, $where: ViewSortWhereUniqueInput!) {
|
||||
viewSort: updateOneViewSort(data: $data, where: $where) {
|
||||
@ -6217,6 +6503,45 @@ export function useGetViewFieldsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpti
|
||||
export type GetViewFieldsQueryHookResult = ReturnType<typeof useGetViewFieldsQuery>;
|
||||
export type GetViewFieldsLazyQueryHookResult = ReturnType<typeof useGetViewFieldsLazyQuery>;
|
||||
export type GetViewFieldsQueryResult = Apollo.QueryResult<GetViewFieldsQuery, GetViewFieldsQueryVariables>;
|
||||
export const GetViewFiltersDocument = gql`
|
||||
query GetViewFilters($where: ViewFilterWhereInput) {
|
||||
viewFilters: findManyViewFilter(where: $where) {
|
||||
displayValue
|
||||
key
|
||||
name
|
||||
operand
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetViewFiltersQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetViewFiltersQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetViewFiltersQuery` 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 } = useGetViewFiltersQuery({
|
||||
* variables: {
|
||||
* where: // value for 'where'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetViewFiltersQuery(baseOptions?: Apollo.QueryHookOptions<GetViewFiltersQuery, GetViewFiltersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetViewFiltersQuery, GetViewFiltersQueryVariables>(GetViewFiltersDocument, options);
|
||||
}
|
||||
export function useGetViewFiltersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetViewFiltersQuery, GetViewFiltersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetViewFiltersQuery, GetViewFiltersQueryVariables>(GetViewFiltersDocument, options);
|
||||
}
|
||||
export type GetViewFiltersQueryHookResult = ReturnType<typeof useGetViewFiltersQuery>;
|
||||
export type GetViewFiltersLazyQueryHookResult = ReturnType<typeof useGetViewFiltersLazyQuery>;
|
||||
export type GetViewFiltersQueryResult = Apollo.QueryResult<GetViewFiltersQuery, GetViewFiltersQueryVariables>;
|
||||
export const GetViewSortsDocument = gql`
|
||||
query GetViewSorts($where: ViewSortWhereInput) {
|
||||
viewSorts: findManyViewSort(where: $where) {
|
||||
|
||||
@ -4,6 +4,7 @@ import { useRecoilState } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { FilterOperand } from '@/ui/filter-n-sort/types/FilterOperand';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
|
||||
import { activeTabIdScopedState } from '@/ui/tab/states/activeTabIdScopedState';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
@ -37,10 +38,10 @@ export function useTasks() {
|
||||
if (currentUser && !filters.length) {
|
||||
setFilters([
|
||||
{
|
||||
field: 'assigneeId',
|
||||
key: 'assigneeId',
|
||||
type: 'entity',
|
||||
value: currentUser.id,
|
||||
operand: 'is',
|
||||
operand: FilterOperand.Is,
|
||||
displayValue: currentUser.displayName,
|
||||
displayAvatarUrl: currentUser.avatarUrl ?? undefined,
|
||||
},
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { companyViewFields } from '@/companies/constants/companyViewFields';
|
||||
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
|
||||
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
|
||||
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { sortsOrderByScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
|
||||
import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/sortsOrderByScopedSelector';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
|
||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
|
||||
import { useTableViews } from '@/views/hooks/useTableViews';
|
||||
import { useViewFilters } from '@/views/hooks/useViewFilters';
|
||||
import { useViewSorts } from '@/views/hooks/useViewSorts';
|
||||
import {
|
||||
SortOrder,
|
||||
@ -26,12 +26,8 @@ import { companiesFilters } from '~/pages/companies/companies-filters';
|
||||
import { availableSorts } from '~/pages/companies/companies-sorts';
|
||||
|
||||
export function CompanyTable() {
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const orderBy = useRecoilScopedValue(
|
||||
sortsOrderByScopedState,
|
||||
sortsOrderByScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [updateEntityMutation] = useUpdateOneCompanyMutation();
|
||||
@ -44,7 +40,10 @@ export function CompanyTable() {
|
||||
viewFieldDefinitions: companyViewFields,
|
||||
});
|
||||
|
||||
const { handleSortsChange } = useViewSorts({ availableSorts });
|
||||
const { persistFilters } = useViewFilters({
|
||||
availableFilters: companiesFilters,
|
||||
});
|
||||
const { persistSorts } = useViewSorts({ availableSorts });
|
||||
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
|
||||
|
||||
const filters = useRecoilScopedValue(
|
||||
@ -59,6 +58,11 @@ export function CompanyTable() {
|
||||
const { setContextMenuEntries } = useCompanyTableContextMenuEntries();
|
||||
const { setActionBarEntries } = useCompanyTableActionBarEntries();
|
||||
|
||||
const handleViewSubmit = useCallback(async () => {
|
||||
await persistFilters();
|
||||
await persistSorts();
|
||||
}, [persistFilters, persistSorts]);
|
||||
|
||||
function handleImport() {
|
||||
openCompanySpreadsheetImport();
|
||||
}
|
||||
@ -68,15 +72,7 @@ export function CompanyTable() {
|
||||
<GenericEntityTableData
|
||||
getRequestResultKey="companies"
|
||||
useGetRequest={useGetCompaniesQuery}
|
||||
orderBy={
|
||||
orderBy.length
|
||||
? orderBy
|
||||
: [
|
||||
{
|
||||
createdAt: SortOrder.Desc,
|
||||
},
|
||||
]
|
||||
}
|
||||
orderBy={orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]}
|
||||
whereFilters={whereFilters}
|
||||
filterDefinitionArray={companiesFilters}
|
||||
setContextMenuEntries={setContextMenuEntries}
|
||||
@ -86,8 +82,8 @@ export function CompanyTable() {
|
||||
viewName="All Companies"
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
onSortsUpdate={currentViewId ? handleSortsChange : undefined}
|
||||
onViewsChange={handleViewsChange}
|
||||
onViewSubmit={handleViewSubmit}
|
||||
onImport={handleImport}
|
||||
updateEntityMutation={({
|
||||
variables,
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { peopleViewFields } from '@/people/constants/peopleViewFields';
|
||||
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
|
||||
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
|
||||
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { sortsOrderByScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
|
||||
import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/sortsOrderByScopedSelector';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
|
||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
|
||||
import { useTableViews } from '@/views/hooks/useTableViews';
|
||||
import { useViewFilters } from '@/views/hooks/useViewFilters';
|
||||
import { useViewSorts } from '@/views/hooks/useViewSorts';
|
||||
import {
|
||||
SortOrder,
|
||||
@ -26,12 +26,8 @@ import { peopleFilters } from '~/pages/people/people-filters';
|
||||
import { availableSorts } from '~/pages/people/people-sorts';
|
||||
|
||||
export function PeopleTable() {
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const orderBy = useRecoilScopedValue(
|
||||
sortsOrderByScopedState,
|
||||
sortsOrderByScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [updateEntityMutation] = useUpdateOnePersonMutation();
|
||||
@ -44,7 +40,10 @@ export function PeopleTable() {
|
||||
objectName: objectId,
|
||||
viewFieldDefinitions: peopleViewFields,
|
||||
});
|
||||
const { handleSortsChange } = useViewSorts({ availableSorts });
|
||||
const { persistFilters } = useViewFilters({
|
||||
availableFilters: peopleFilters,
|
||||
});
|
||||
const { persistSorts } = useViewSorts({ availableSorts });
|
||||
|
||||
const filters = useRecoilScopedValue(
|
||||
filtersScopedState,
|
||||
@ -58,6 +57,11 @@ export function PeopleTable() {
|
||||
const { setContextMenuEntries } = usePersonTableContextMenuEntries();
|
||||
const { setActionBarEntries } = usePersonTableActionBarEntries();
|
||||
|
||||
const handleViewSubmit = useCallback(async () => {
|
||||
await persistFilters();
|
||||
await persistSorts();
|
||||
}, [persistFilters, persistSorts]);
|
||||
|
||||
function handleImport() {
|
||||
openPersonSpreadsheetImport();
|
||||
}
|
||||
@ -67,15 +71,7 @@ export function PeopleTable() {
|
||||
<GenericEntityTableData
|
||||
getRequestResultKey="people"
|
||||
useGetRequest={useGetPeopleQuery}
|
||||
orderBy={
|
||||
orderBy.length
|
||||
? orderBy
|
||||
: [
|
||||
{
|
||||
createdAt: SortOrder.Desc,
|
||||
},
|
||||
]
|
||||
}
|
||||
orderBy={orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]}
|
||||
whereFilters={whereFilters}
|
||||
filterDefinitionArray={peopleFilters}
|
||||
setContextMenuEntries={setContextMenuEntries}
|
||||
@ -85,8 +81,8 @@ export function PeopleTable() {
|
||||
viewName="All People"
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
onSortsUpdate={currentViewId ? handleSortsChange : undefined}
|
||||
onViewsChange={handleViewsChange}
|
||||
onViewSubmit={handleViewSubmit}
|
||||
onImport={handleImport}
|
||||
updateEntityMutation={({
|
||||
variables,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ButtonPosition, ButtonProps } from './Button';
|
||||
@ -9,13 +9,15 @@ const StyledButtonGroupContainer = styled.div`
|
||||
`;
|
||||
|
||||
type ButtonGroupProps = Pick<ButtonProps, 'variant' | 'size'> & {
|
||||
children: React.ReactElement[];
|
||||
children: ReactNode[];
|
||||
};
|
||||
|
||||
export function ButtonGroup({ children, variant, size }: ButtonGroupProps) {
|
||||
return (
|
||||
<StyledButtonGroupContainer>
|
||||
{React.Children.map(children, (child, index) => {
|
||||
if (!React.isValidElement(child)) return null;
|
||||
|
||||
let position: ButtonPosition;
|
||||
|
||||
if (index === 0) {
|
||||
|
||||
@ -28,7 +28,7 @@ export function FilterDropdownDateSearchInput({
|
||||
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;
|
||||
|
||||
upsertFilter({
|
||||
field: filterDefinitionUsedInDropdown.field,
|
||||
key: filterDefinitionUsedInDropdown.key,
|
||||
type: filterDefinitionUsedInDropdown.type,
|
||||
value: date.toISOString(),
|
||||
operand: selectedOperandInDropdown,
|
||||
|
||||
@ -51,14 +51,14 @@ export function FilterDropdownEntitySearchSelect({
|
||||
selectedEntity.id === filterDropdownSelectedEntityId;
|
||||
|
||||
if (clickedOnAlreadySelectedEntity) {
|
||||
removeFilter(filterDefinitionUsedInDropdown.field);
|
||||
removeFilter(filterDefinitionUsedInDropdown.key);
|
||||
setFilterDropdownSelectedEntityId(null);
|
||||
} else {
|
||||
setFilterDropdownSelectedEntityId(selectedEntity.id);
|
||||
|
||||
upsertFilter({
|
||||
displayValue: selectedEntity.name,
|
||||
field: filterDefinitionUsedInDropdown.field,
|
||||
key: filterDefinitionUsedInDropdown.key,
|
||||
operand: selectedOperandInDropdown,
|
||||
type: filterDefinitionUsedInDropdown.type,
|
||||
value: selectedEntity.id,
|
||||
|
||||
@ -34,10 +34,10 @@ export function FilterDropdownNumberSearchInput({
|
||||
placeholder={filterDefinitionUsedInDropdown.label}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.value === '') {
|
||||
removeFilter(filterDefinitionUsedInDropdown.field);
|
||||
removeFilter(filterDefinitionUsedInDropdown.key);
|
||||
} else {
|
||||
upsertFilter({
|
||||
field: filterDefinitionUsedInDropdown.field,
|
||||
key: filterDefinitionUsedInDropdown.key,
|
||||
type: filterDefinitionUsedInDropdown.type,
|
||||
value: event.target.value,
|
||||
operand: selectedOperandInDropdown,
|
||||
|
||||
@ -48,7 +48,7 @@ export function FilterDropdownOperandSelect({
|
||||
|
||||
if (filterDefinitionUsedInDropdown && filterCurrentlyEdited) {
|
||||
upsertFilter({
|
||||
field: filterCurrentlyEdited.field,
|
||||
key: filterCurrentlyEdited.key,
|
||||
displayValue: filterCurrentlyEdited.displayValue,
|
||||
operand: newOperand,
|
||||
type: filterCurrentlyEdited.type,
|
||||
|
||||
@ -44,10 +44,10 @@ export function FilterDropdownTextSearchInput({
|
||||
setFilterDropdownSearchInput(event.target.value);
|
||||
|
||||
if (event.target.value === '') {
|
||||
removeFilter(filterDefinitionUsedInDropdown.field);
|
||||
removeFilter(filterDefinitionUsedInDropdown.key);
|
||||
} else {
|
||||
upsertFilter({
|
||||
field: filterDefinitionUsedInDropdown.field,
|
||||
key: filterDefinitionUsedInDropdown.key,
|
||||
type: filterDefinitionUsedInDropdown.type,
|
||||
value: event.target.value,
|
||||
operand: selectedOperandInDropdown,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Context } from 'react';
|
||||
import type { Context, ReactNode } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
@ -26,6 +26,7 @@ type OwnProps<SortField> = {
|
||||
onRemoveSort: (sortId: SelectedSortType<SortField>['key']) => void;
|
||||
onCancelClick: () => void;
|
||||
hasFilterButton?: boolean;
|
||||
rightComponent?: ReactNode;
|
||||
};
|
||||
|
||||
const StyledBar = styled.div`
|
||||
@ -97,6 +98,7 @@ function SortAndFilterBar<SortField>({
|
||||
onRemoveSort,
|
||||
onCancelClick,
|
||||
hasFilterButton = false,
|
||||
rightComponent,
|
||||
}: OwnProps<SortField>) {
|
||||
const theme = useTheme();
|
||||
|
||||
@ -117,7 +119,7 @@ function SortAndFilterBar<SortField>({
|
||||
|
||||
const filtersWithDefinition = filters.map((filter) => {
|
||||
const filterDefinition = availableFilters.find((availableFilter) => {
|
||||
return availableFilter.field === filter.field;
|
||||
return availableFilter.key === filter.key;
|
||||
});
|
||||
|
||||
return {
|
||||
@ -170,15 +172,15 @@ function SortAndFilterBar<SortField>({
|
||||
{filtersWithDefinition.map((filter) => {
|
||||
return (
|
||||
<SortOrFilterChip
|
||||
key={filter.field}
|
||||
key={filter.key}
|
||||
labelKey={filter.label}
|
||||
labelValue={`${getOperandLabelShort(filter.operand)} ${
|
||||
filter.displayValue
|
||||
}`}
|
||||
id={filter.field}
|
||||
id={filter.key}
|
||||
icon={filter.icon}
|
||||
onRemove={() => {
|
||||
removeFilter(filter.field);
|
||||
removeFilter(filter.key);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -190,18 +192,19 @@ function SortAndFilterBar<SortField>({
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
color={theme.font.color.tertiary}
|
||||
icon={<IconPlus size={theme.icon.size.md} />}
|
||||
label={`Add filter`}
|
||||
label="Add filter"
|
||||
/>
|
||||
)}
|
||||
</StyledFilterContainer>
|
||||
{filters.length + sorts.length > 0 && (
|
||||
<StyledCancelButton
|
||||
data-testid={'cancel-button'}
|
||||
data-testid="cancel-button"
|
||||
onClick={handleCancelClick}
|
||||
>
|
||||
Cancel
|
||||
</StyledCancelButton>
|
||||
)}
|
||||
{rightComponent}
|
||||
</StyledBar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export function useFilterCurrentlyEdited(context: Context<string | null>) {
|
||||
|
||||
return useMemo(() => {
|
||||
return filters.find(
|
||||
(filter) => filter.field === filterDefinitionUsedInDropdown?.field,
|
||||
(filter) => filter.key === filterDefinitionUsedInDropdown?.key,
|
||||
);
|
||||
}, [filterDefinitionUsedInDropdown, filters]);
|
||||
}
|
||||
|
||||
@ -7,10 +7,10 @@ import { filtersScopedState } from '../states/filtersScopedState';
|
||||
export function useRemoveFilter(context: Context<string | null>) {
|
||||
const [, setFilters] = useRecoilScopedState(filtersScopedState, context);
|
||||
|
||||
return function removeFilter(filterField: string) {
|
||||
return function removeFilter(filterKey: string) {
|
||||
setFilters((filters) => {
|
||||
return filters.filter((filter) => {
|
||||
return filter.field !== filterField;
|
||||
return filter.key !== filterKey;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ export function useUpsertFilter(context: Context<string | null>) {
|
||||
setFilters((filters) => {
|
||||
return produce(filters, (filtersDraft) => {
|
||||
const index = filtersDraft.findIndex(
|
||||
(filter) => filter.field === filterToUpsert.field,
|
||||
(filter) => filter.key === filterToUpsert.key,
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import { Filter } from '../types/Filter';
|
||||
import type { Filter } from '../types/Filter';
|
||||
|
||||
export const filtersScopedState = atomFamily<Filter[], string>({
|
||||
key: 'filtersScopedState',
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import type { Filter } from '../types/Filter';
|
||||
|
||||
import { savedFiltersScopedState } from './savedFiltersScopedState';
|
||||
|
||||
export const savedFiltersByKeyScopedSelector = selectorFamily({
|
||||
key: 'savedFiltersByKeyScopedSelector',
|
||||
get:
|
||||
(param: string | undefined) =>
|
||||
({ get }) =>
|
||||
get(savedFiltersScopedState(param)).reduce<Record<string, Filter>>(
|
||||
(result, filter) => ({ ...result, [filter.key]: filter }),
|
||||
{},
|
||||
),
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import type { Filter } from '../types/Filter';
|
||||
|
||||
export const savedFiltersScopedState = atomFamily<Filter[], string | undefined>(
|
||||
{
|
||||
key: 'savedFiltersScopedState',
|
||||
default: [],
|
||||
},
|
||||
);
|
||||
@ -0,0 +1,15 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import type { SelectedSortType } from '../types/interface';
|
||||
|
||||
import { savedSortsScopedState } from './savedSortsScopedState';
|
||||
|
||||
export const savedSortsByKeyScopedSelector = selectorFamily({
|
||||
key: 'savedSortsByKeyScopedSelector',
|
||||
get:
|
||||
(viewId: string | undefined) =>
|
||||
({ get }) =>
|
||||
get(savedSortsScopedState(viewId)).reduce<
|
||||
Record<string, SelectedSortType<any>>
|
||||
>((result, sort) => ({ ...result, [sort.key]: sort }), {}),
|
||||
});
|
||||
@ -0,0 +1,11 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import type { SelectedSortType } from '../types/interface';
|
||||
|
||||
export const savedSortsScopedState = atomFamily<
|
||||
SelectedSortType<any>[],
|
||||
string | undefined
|
||||
>({
|
||||
key: 'savedSortsScopedState',
|
||||
default: [],
|
||||
});
|
||||
@ -1,28 +0,0 @@
|
||||
import { atomFamily, selectorFamily } from 'recoil';
|
||||
|
||||
import { reduceSortsToOrderBy } from '../helpers';
|
||||
import { SelectedSortType } from '../types/interface';
|
||||
|
||||
export const sortScopedState = atomFamily<SelectedSortType<any>[], string>({
|
||||
key: 'sortScopedState',
|
||||
default: [],
|
||||
});
|
||||
|
||||
export const sortsByKeyScopedState = selectorFamily({
|
||||
key: 'sortsByKeyScopedState',
|
||||
get:
|
||||
(param: string) =>
|
||||
({ get }) =>
|
||||
get(sortScopedState(param)).reduce<Record<string, SelectedSortType<any>>>(
|
||||
(result, sort) => ({ ...result, [sort.key]: sort }),
|
||||
{},
|
||||
),
|
||||
});
|
||||
|
||||
export const sortsOrderByScopedState = selectorFamily({
|
||||
key: 'sortsOrderByScopedState',
|
||||
get:
|
||||
(param: string) =>
|
||||
({ get }) =>
|
||||
reduceSortsToOrderBy(get(sortScopedState(param))),
|
||||
});
|
||||
@ -0,0 +1,13 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { reduceSortsToOrderBy } from '../helpers';
|
||||
|
||||
import { sortsScopedState } from './sortsScopedState';
|
||||
|
||||
export const sortsOrderByScopedSelector = selectorFamily({
|
||||
key: 'sortsOrderByScopedSelector',
|
||||
get:
|
||||
(param: string) =>
|
||||
({ get }) =>
|
||||
reduceSortsToOrderBy(get(sortsScopedState(param))),
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import type { SelectedSortType } from '../types/interface';
|
||||
|
||||
export const sortsScopedState = atomFamily<SelectedSortType<any>[], string>({
|
||||
key: 'sortsScopedState',
|
||||
default: [],
|
||||
});
|
||||
@ -2,7 +2,7 @@ import { FilterOperand } from './FilterOperand';
|
||||
import { FilterType } from './FilterType';
|
||||
|
||||
export type Filter = {
|
||||
field: string;
|
||||
key: string;
|
||||
type: FilterType;
|
||||
value: string;
|
||||
displayValue: string;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { FilterType } from './FilterType';
|
||||
|
||||
export type FilterDefinition = {
|
||||
field: string;
|
||||
key: string;
|
||||
label: string;
|
||||
icon: JSX.Element;
|
||||
type: FilterType;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { FilterDefinition } from './FilterDefinition';
|
||||
|
||||
export type FilterDefinitionByEntity<T> = FilterDefinition & {
|
||||
field: keyof T;
|
||||
key: keyof T;
|
||||
};
|
||||
|
||||
@ -1,7 +1 @@
|
||||
export type FilterOperand =
|
||||
| 'contains'
|
||||
| 'does-not-contain'
|
||||
| 'greater-than'
|
||||
| 'less-than'
|
||||
| 'is'
|
||||
| 'is-not';
|
||||
export { ViewFilterOperand as FilterOperand } from '~/generated/graphql';
|
||||
|
||||
@ -2,17 +2,17 @@ import { FilterOperand } from '../types/FilterOperand';
|
||||
|
||||
export function getOperandLabel(operand: FilterOperand | null | undefined) {
|
||||
switch (operand) {
|
||||
case 'contains':
|
||||
case FilterOperand.Contains:
|
||||
return 'Contains';
|
||||
case 'does-not-contain':
|
||||
case FilterOperand.DoesNotContain:
|
||||
return "Doesn't contain";
|
||||
case 'greater-than':
|
||||
case FilterOperand.GreaterThan:
|
||||
return 'Greater than';
|
||||
case 'less-than':
|
||||
case FilterOperand.LessThan:
|
||||
return 'Less than';
|
||||
case 'is':
|
||||
case FilterOperand.Is:
|
||||
return 'Is';
|
||||
case 'is-not':
|
||||
case FilterOperand.IsNot:
|
||||
return 'Is not';
|
||||
default:
|
||||
return '';
|
||||
@ -22,15 +22,15 @@ export function getOperandLabelShort(
|
||||
operand: FilterOperand | null | undefined,
|
||||
) {
|
||||
switch (operand) {
|
||||
case 'is':
|
||||
case 'contains':
|
||||
case FilterOperand.Is:
|
||||
case FilterOperand.Contains:
|
||||
return ': ';
|
||||
case 'is-not':
|
||||
case 'does-not-contain':
|
||||
case FilterOperand.IsNot:
|
||||
case FilterOperand.DoesNotContain:
|
||||
return ': Not';
|
||||
case 'greater-than':
|
||||
case FilterOperand.GreaterThan:
|
||||
return '\u00A0> ';
|
||||
case 'less-than':
|
||||
case FilterOperand.LessThan:
|
||||
return '\u00A0< ';
|
||||
default:
|
||||
return ': ';
|
||||
|
||||
@ -6,12 +6,12 @@ export function getOperandsForFilterType(
|
||||
): FilterOperand[] {
|
||||
switch (filterType) {
|
||||
case 'text':
|
||||
return ['contains', 'does-not-contain'];
|
||||
return [FilterOperand.Contains, FilterOperand.DoesNotContain];
|
||||
case 'number':
|
||||
case 'date':
|
||||
return ['greater-than', 'less-than'];
|
||||
return [FilterOperand.GreaterThan, FilterOperand.LessThan];
|
||||
case 'entity':
|
||||
return ['is', 'is-not'];
|
||||
return [FilterOperand.Is, FilterOperand.IsNot];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -1,21 +1,22 @@
|
||||
import { QueryMode } from '~/generated/graphql';
|
||||
|
||||
import { Filter } from '../types/Filter';
|
||||
import { FilterOperand } from '../types/FilterOperand';
|
||||
|
||||
export function turnFilterIntoWhereClause(filter: Filter) {
|
||||
switch (filter.type) {
|
||||
case 'text':
|
||||
switch (filter.operand) {
|
||||
case 'contains':
|
||||
case FilterOperand.Contains:
|
||||
return {
|
||||
[filter.field]: {
|
||||
[filter.key]: {
|
||||
contains: filter.value,
|
||||
mode: QueryMode.Insensitive,
|
||||
},
|
||||
};
|
||||
case 'does-not-contain':
|
||||
case FilterOperand.DoesNotContain:
|
||||
return {
|
||||
[filter.field]: {
|
||||
[filter.key]: {
|
||||
not: {
|
||||
contains: filter.value,
|
||||
mode: QueryMode.Insensitive,
|
||||
@ -29,15 +30,15 @@ export function turnFilterIntoWhereClause(filter: Filter) {
|
||||
}
|
||||
case 'number':
|
||||
switch (filter.operand) {
|
||||
case 'greater-than':
|
||||
case FilterOperand.GreaterThan:
|
||||
return {
|
||||
[filter.field]: {
|
||||
[filter.key]: {
|
||||
gte: parseFloat(filter.value),
|
||||
},
|
||||
};
|
||||
case 'less-than':
|
||||
case FilterOperand.LessThan:
|
||||
return {
|
||||
[filter.field]: {
|
||||
[filter.key]: {
|
||||
lte: parseFloat(filter.value),
|
||||
},
|
||||
};
|
||||
@ -48,15 +49,15 @@ export function turnFilterIntoWhereClause(filter: Filter) {
|
||||
}
|
||||
case 'date':
|
||||
switch (filter.operand) {
|
||||
case 'greater-than':
|
||||
case FilterOperand.GreaterThan:
|
||||
return {
|
||||
[filter.field]: {
|
||||
[filter.key]: {
|
||||
gte: filter.value,
|
||||
},
|
||||
};
|
||||
case 'less-than':
|
||||
case FilterOperand.LessThan:
|
||||
return {
|
||||
[filter.field]: {
|
||||
[filter.key]: {
|
||||
lte: filter.value,
|
||||
},
|
||||
};
|
||||
@ -67,15 +68,15 @@ export function turnFilterIntoWhereClause(filter: Filter) {
|
||||
}
|
||||
case 'entity':
|
||||
switch (filter.operand) {
|
||||
case 'is':
|
||||
case FilterOperand.Is:
|
||||
return {
|
||||
[filter.field]: {
|
||||
[filter.key]: {
|
||||
equals: filter.value,
|
||||
},
|
||||
};
|
||||
case 'is-not':
|
||||
case FilterOperand.IsNot:
|
||||
return {
|
||||
[filter.field]: {
|
||||
[filter.key]: {
|
||||
not: { equals: filter.value },
|
||||
},
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@ import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||
import { SortType } from '@/ui/filter-n-sort/types/interface';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
@ -97,8 +97,8 @@ type OwnProps<SortField> = {
|
||||
viewIcon?: React.ReactNode;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
onViewsChange?: (views: TableView[]) => void;
|
||||
onViewSubmit?: () => void;
|
||||
onImport?: () => void;
|
||||
updateEntityMutation: any;
|
||||
};
|
||||
@ -107,8 +107,8 @@ export function EntityTable<SortField>({
|
||||
viewName,
|
||||
availableSorts,
|
||||
onColumnsChange,
|
||||
onSortsUpdate,
|
||||
onViewsChange,
|
||||
onViewSubmit,
|
||||
onImport,
|
||||
updateEntityMutation,
|
||||
}: OwnProps<SortField>) {
|
||||
@ -136,8 +136,8 @@ export function EntityTable<SortField>({
|
||||
viewName={viewName}
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={onColumnsChange}
|
||||
onSortsUpdate={onSortsUpdate}
|
||||
onViewsChange={onViewsChange}
|
||||
onViewSubmit={onViewSubmit}
|
||||
onImport={onImport}
|
||||
/>
|
||||
<StyledTableWrapper>
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { Button, ButtonSize } from '@/ui/button/components/Button';
|
||||
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuContainer } from '@/ui/filter-n-sort/components/DropdownMenuContainer';
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
||||
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
|
||||
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
||||
import { IconChevronDown, IconPlus } from '@/ui/icon';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import {
|
||||
currentTableViewIdState,
|
||||
tableViewEditModeState,
|
||||
} from '../../states/tableViewsState';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: inline-flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledDropdownMenuContainer = styled(DropdownMenuContainer)`
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type TableUpdateViewButtonGroupProps = {
|
||||
onViewSubmit?: () => void;
|
||||
HotkeyScope: string;
|
||||
};
|
||||
|
||||
export const TableUpdateViewButtonGroup = ({
|
||||
onViewSubmit,
|
||||
HotkeyScope,
|
||||
}: TableUpdateViewButtonGroupProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
|
||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setViewEditMode = useSetRecoilState(tableViewEditModeState);
|
||||
|
||||
const handleArrowDownButtonClick = useCallback(() => {
|
||||
setIsDropdownOpen((previousIsOpen) => !previousIsOpen);
|
||||
}, []);
|
||||
|
||||
const handleCreateViewButtonClick = useCallback(() => {
|
||||
setViewEditMode({ mode: 'create', viewId: undefined });
|
||||
setIsDropdownOpen(false);
|
||||
}, [setViewEditMode]);
|
||||
|
||||
const handleDropdownClose = useCallback(() => {
|
||||
setIsDropdownOpen(false);
|
||||
}, []);
|
||||
|
||||
const handleViewSubmit = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async () => {
|
||||
await Promise.resolve(onViewSubmit?.());
|
||||
|
||||
const selectedFilters = await snapshot.getPromise(
|
||||
filtersScopedState(tableScopeId),
|
||||
);
|
||||
set(savedFiltersScopedState(currentViewId), selectedFilters);
|
||||
|
||||
const selectedSorts = await snapshot.getPromise(
|
||||
sortsScopedState(tableScopeId),
|
||||
);
|
||||
set(savedSortsScopedState(currentViewId), selectedSorts);
|
||||
},
|
||||
[currentViewId, onViewSubmit],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Enter, Key.Escape],
|
||||
handleDropdownClose,
|
||||
HotkeyScope,
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<ButtonGroup size={ButtonSize.Small}>
|
||||
<Button
|
||||
title="Update view"
|
||||
disabled={!currentViewId}
|
||||
onClick={handleViewSubmit}
|
||||
/>
|
||||
<Button
|
||||
size={ButtonSize.Small}
|
||||
icon={<IconChevronDown />}
|
||||
onClick={handleArrowDownButtonClick}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
{isDropdownOpen && (
|
||||
<StyledDropdownMenuContainer onClose={handleDropdownClose}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItem onClick={handleCreateViewButtonClick}>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
Create view
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledDropdownMenuContainer>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -1,13 +1,17 @@
|
||||
import { type MouseEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { IconButton } from '@/ui/button/components/IconButton';
|
||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
||||
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
|
||||
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconList,
|
||||
@ -23,6 +27,7 @@ import {
|
||||
tableViewsState,
|
||||
} from '@/ui/table/states/tableViewsState';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
@ -59,6 +64,8 @@ export const TableViewsDropdownButton = ({
|
||||
const theme = useTheme();
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
|
||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||
|
||||
const currentView = useRecoilScopedValue(
|
||||
currentTableViewState,
|
||||
TableRecoilScopeContext,
|
||||
@ -78,11 +85,21 @@ export const TableViewsDropdownButton = ({
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleViewSelect = useCallback(
|
||||
(viewId?: string) => {
|
||||
setCurrentViewId(viewId);
|
||||
setIsUnfolded(false);
|
||||
},
|
||||
const handleViewSelect = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async (viewId?: string) => {
|
||||
const savedFilters = await snapshot.getPromise(
|
||||
savedFiltersScopedState(viewId),
|
||||
);
|
||||
const savedSorts = await snapshot.getPromise(
|
||||
savedSortsScopedState(viewId),
|
||||
);
|
||||
|
||||
set(filtersScopedState(tableScopeId), savedFilters);
|
||||
set(sortsScopedState(tableScopeId), savedSorts);
|
||||
setCurrentViewId(viewId);
|
||||
setIsUnfolded(false);
|
||||
},
|
||||
[setCurrentViewId],
|
||||
);
|
||||
|
||||
|
||||
@ -7,13 +7,14 @@ import type {
|
||||
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 { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
||||
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
|
||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||
import { TableOptionsDropdownButton } from '@/ui/table/options/components/TableOptionsDropdownButton';
|
||||
import { TopBar } from '@/ui/top-bar/TopBar';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { TableUpdateViewButtonGroup } from '../../options/components/TableUpdateViewButtonGroup';
|
||||
import { TableViewsDropdownButton } from '../../options/components/TableViewsDropdownButton';
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import type { TableView } from '../../states/tableViewsState';
|
||||
@ -24,8 +25,8 @@ type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
onViewsChange?: (views: TableView[]) => void;
|
||||
onViewSubmit?: () => void;
|
||||
onImport?: () => void;
|
||||
};
|
||||
|
||||
@ -33,30 +34,29 @@ export function TableHeader<SortField>({
|
||||
viewName,
|
||||
availableSorts,
|
||||
onColumnsChange,
|
||||
onSortsUpdate,
|
||||
onViewsChange,
|
||||
onViewSubmit,
|
||||
onImport,
|
||||
}: OwnProps<SortField>) {
|
||||
const [sorts, setSorts] = useRecoilScopedState<SelectedSortType<SortField>[]>(
|
||||
sortScopedState,
|
||||
sortsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const handleSortsUpdate = onSortsUpdate ?? setSorts;
|
||||
|
||||
const sortSelect = useCallback(
|
||||
(newSort: SelectedSortType<SortField>) => {
|
||||
const newSorts = updateSortOrFilterByKey(sorts, newSort);
|
||||
handleSortsUpdate(newSorts);
|
||||
setSorts(newSorts);
|
||||
},
|
||||
[handleSortsUpdate, sorts],
|
||||
[setSorts, sorts],
|
||||
);
|
||||
|
||||
const sortUnselect = useCallback(
|
||||
(sortKey: string) => {
|
||||
const newSorts = sorts.filter((sort) => sort.key !== sortKey);
|
||||
handleSortsUpdate(newSorts);
|
||||
setSorts(newSorts);
|
||||
},
|
||||
[handleSortsUpdate, sorts],
|
||||
[setSorts, sorts],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -65,7 +65,7 @@ export function TableHeader<SortField>({
|
||||
<TableViewsDropdownButton
|
||||
defaultViewName={viewName}
|
||||
onViewsChange={onViewsChange}
|
||||
HotkeyScope={TableViewsHotkeyScope.Dropdown}
|
||||
HotkeyScope={TableViewsHotkeyScope.ListDropdown}
|
||||
/>
|
||||
}
|
||||
displayBottomBorder={false}
|
||||
@ -97,10 +97,14 @@ export function TableHeader<SortField>({
|
||||
context={TableRecoilScopeContext}
|
||||
sorts={sorts}
|
||||
onRemoveSort={sortUnselect}
|
||||
onCancelClick={() => {
|
||||
handleSortsUpdate([]);
|
||||
}}
|
||||
onCancelClick={() => setSorts([])}
|
||||
hasFilterButton
|
||||
rightComponent={
|
||||
<TableUpdateViewButtonGroup
|
||||
onViewSubmit={onViewSubmit}
|
||||
HotkeyScope={TableViewsHotkeyScope.CreateDropdown}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export enum TableViewsHotkeyScope {
|
||||
Dropdown = 'table-views-dropdown',
|
||||
ListDropdown = 'table-views-list-dropdown',
|
||||
CreateDropdown = 'table-views-create-dropdown',
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_VIEW_FILTERS = gql`
|
||||
mutation CreateViewFilters($data: [ViewFilterCreateManyInput!]!) {
|
||||
createManyViewFilter(data: $data) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const DELETE_VIEW_FILTERS = gql`
|
||||
mutation DeleteViewFilters($where: ViewFilterWhereInput!) {
|
||||
deleteManyViewFilter(where: $where) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,16 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_VIEW_FILTER = gql`
|
||||
mutation UpdateViewFilter(
|
||||
$data: ViewFilterUpdateInput!
|
||||
$where: ViewFilterWhereUniqueInput!
|
||||
) {
|
||||
viewFilter: updateOneViewFilter(data: $data, where: $where) {
|
||||
displayValue
|
||||
key
|
||||
name
|
||||
operand
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
13
front/src/modules/views/graphql/queries/getViewFilters.ts
Normal file
13
front/src/modules/views/graphql/queries/getViewFilters.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_VIEW_FILTERS = gql`
|
||||
query GetViewFilters($where: ViewFilterWhereInput) {
|
||||
viewFilters: findManyViewFilter(where: $where) {
|
||||
displayValue
|
||||
key
|
||||
name
|
||||
operand
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
172
front/src/modules/views/hooks/useViewFilters.ts
Normal file
172
front/src/modules/views/hooks/useViewFilters.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { savedFiltersByKeyScopedSelector } from '@/ui/filter-n-sort/states/savedFiltersByKeyScopedSelector';
|
||||
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
||||
import type { Filter } from '@/ui/filter-n-sort/types/Filter';
|
||||
import type { FilterDefinitionByEntity } from '@/ui/filter-n-sort/types/FilterDefinitionByEntity';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import {
|
||||
useCreateViewFiltersMutation,
|
||||
useDeleteViewFiltersMutation,
|
||||
useGetViewFiltersQuery,
|
||||
useUpdateViewFilterMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useViewFilters = <Entity>({
|
||||
availableFilters,
|
||||
}: {
|
||||
availableFilters: FilterDefinitionByEntity<Entity>[];
|
||||
}) => {
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [filters, setFilters] = useRecoilScopedState(
|
||||
filtersScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [, setSavedFilters] = useRecoilState(
|
||||
savedFiltersScopedState(currentViewId),
|
||||
);
|
||||
const savedFiltersByKey = useRecoilValue(
|
||||
savedFiltersByKeyScopedSelector(currentViewId),
|
||||
);
|
||||
|
||||
const { refetch } = useGetViewFiltersQuery({
|
||||
skip: !currentViewId,
|
||||
variables: {
|
||||
where: {
|
||||
viewId: { equals: currentViewId },
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
const nextFilters = data.viewFilters
|
||||
.map(({ __typename, name: _name, ...viewFilter }) => {
|
||||
const availableFilter = availableFilters.find(
|
||||
(filter) => filter.key === viewFilter.key,
|
||||
);
|
||||
|
||||
return availableFilter
|
||||
? {
|
||||
...viewFilter,
|
||||
displayValue: viewFilter.displayValue ?? viewFilter.value,
|
||||
type: availableFilter.type,
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((filter): filter is Filter => !!filter);
|
||||
|
||||
if (!isDeeplyEqual(filters, nextFilters)) {
|
||||
setSavedFilters(nextFilters);
|
||||
setFilters(nextFilters);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [createViewFiltersMutation] = useCreateViewFiltersMutation();
|
||||
const [updateViewFilterMutation] = useUpdateViewFilterMutation();
|
||||
const [deleteViewFiltersMutation] = useDeleteViewFiltersMutation();
|
||||
|
||||
const createViewFilters = useCallback(
|
||||
(filters: Filter[]) => {
|
||||
if (!currentViewId || !filters.length) return;
|
||||
|
||||
return createViewFiltersMutation({
|
||||
variables: {
|
||||
data: filters.map((filter) => ({
|
||||
displayValue: filter.displayValue ?? filter.value,
|
||||
key: filter.key,
|
||||
name:
|
||||
availableFilters.find(({ key }) => key === filter.key)?.label ??
|
||||
'',
|
||||
operand: filter.operand,
|
||||
value: filter.value,
|
||||
viewId: currentViewId,
|
||||
})),
|
||||
},
|
||||
});
|
||||
},
|
||||
[availableFilters, createViewFiltersMutation, currentViewId],
|
||||
);
|
||||
|
||||
const updateViewFilters = useCallback(
|
||||
(filters: Filter[]) => {
|
||||
if (!currentViewId || !filters.length) return;
|
||||
|
||||
return Promise.all(
|
||||
filters.map((filter) =>
|
||||
updateViewFilterMutation({
|
||||
variables: {
|
||||
data: {
|
||||
displayValue: filter.displayValue ?? filter.value,
|
||||
operand: filter.operand,
|
||||
value: filter.value,
|
||||
},
|
||||
where: {
|
||||
viewId_key: { key: filter.key, viewId: currentViewId },
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[currentViewId, updateViewFilterMutation],
|
||||
);
|
||||
|
||||
const deleteViewFilters = useCallback(
|
||||
(filterKeys: string[]) => {
|
||||
if (!currentViewId || !filterKeys.length) return;
|
||||
|
||||
return deleteViewFiltersMutation({
|
||||
variables: {
|
||||
where: {
|
||||
key: { in: filterKeys },
|
||||
viewId: { equals: currentViewId },
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
[currentViewId, deleteViewFiltersMutation],
|
||||
);
|
||||
|
||||
const persistFilters = useCallback(async () => {
|
||||
if (!currentViewId) return;
|
||||
|
||||
const filtersToCreate = filters.filter(
|
||||
(filter) => !savedFiltersByKey[filter.key],
|
||||
);
|
||||
await createViewFilters(filtersToCreate);
|
||||
|
||||
const filtersToUpdate = filters.filter(
|
||||
(filter) =>
|
||||
savedFiltersByKey[filter.key] &&
|
||||
(savedFiltersByKey[filter.key].operand !== filter.operand ||
|
||||
savedFiltersByKey[filter.key].value !== filter.value),
|
||||
);
|
||||
await updateViewFilters(filtersToUpdate);
|
||||
|
||||
const filterKeys = filters.map((filter) => filter.key);
|
||||
const filterKeysToDelete = Object.keys(savedFiltersByKey).filter(
|
||||
(previousFilterKey) => !filterKeys.includes(previousFilterKey),
|
||||
);
|
||||
await deleteViewFilters(filterKeysToDelete);
|
||||
|
||||
return refetch();
|
||||
}, [
|
||||
currentViewId,
|
||||
filters,
|
||||
createViewFilters,
|
||||
updateViewFilters,
|
||||
savedFiltersByKey,
|
||||
deleteViewFilters,
|
||||
refetch,
|
||||
]);
|
||||
|
||||
return { persistFilters };
|
||||
};
|
||||
@ -1,10 +1,9 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
sortsByKeyScopedState,
|
||||
sortScopedState,
|
||||
} from '@/ui/filter-n-sort/states/sortScopedState';
|
||||
import { savedSortsByKeyScopedSelector } from '@/ui/filter-n-sort/states/savedSortsByKeyScopedSelector';
|
||||
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
|
||||
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
||||
import type {
|
||||
SelectedSortType,
|
||||
SortType,
|
||||
@ -20,8 +19,7 @@ import {
|
||||
useUpdateViewSortMutation,
|
||||
ViewSortDirection,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_VIEW_SORTS } from '../graphql/queries/getViewSorts';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useViewSorts = <SortField>({
|
||||
availableSorts,
|
||||
@ -32,20 +30,18 @@ export const useViewSorts = <SortField>({
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [, setSorts] = useRecoilScopedState(
|
||||
sortScopedState,
|
||||
const [sorts, setSorts] = useRecoilScopedState(
|
||||
sortsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const sortsByKey = useRecoilScopedValue(
|
||||
sortsByKeyScopedState,
|
||||
TableRecoilScopeContext,
|
||||
const [, setSavedSorts] = useRecoilState(
|
||||
savedSortsScopedState(currentViewId),
|
||||
);
|
||||
const savedSortsByKey = useRecoilValue(
|
||||
savedSortsByKeyScopedSelector(currentViewId),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentViewId) setSorts([]);
|
||||
}, [currentViewId, setSorts]);
|
||||
|
||||
useGetViewSortsQuery({
|
||||
const { refetch } = useGetViewSortsQuery({
|
||||
skip: !currentViewId,
|
||||
variables: {
|
||||
where: {
|
||||
@ -53,23 +49,26 @@ export const useViewSorts = <SortField>({
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
setSorts(
|
||||
data.viewSorts
|
||||
.map((viewSort) => {
|
||||
const availableSort = availableSorts.find(
|
||||
(sort) => sort.key === viewSort.key,
|
||||
);
|
||||
const nextSorts = data.viewSorts
|
||||
.map((viewSort) => {
|
||||
const availableSort = availableSorts.find(
|
||||
(sort) => sort.key === viewSort.key,
|
||||
);
|
||||
|
||||
return availableSort
|
||||
? {
|
||||
...availableSort,
|
||||
label: viewSort.name,
|
||||
order: viewSort.direction.toLowerCase(),
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((sort): sort is SelectedSortType<SortField> => !!sort),
|
||||
);
|
||||
return availableSort
|
||||
? {
|
||||
...availableSort,
|
||||
label: viewSort.name,
|
||||
order: viewSort.direction.toLowerCase(),
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((sort): sort is SelectedSortType<SortField> => !!sort);
|
||||
|
||||
if (!isDeeplyEqual(sorts, nextSorts)) {
|
||||
setSavedSorts(nextSorts);
|
||||
setSorts(nextSorts);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -90,7 +89,6 @@ export const useViewSorts = <SortField>({
|
||||
viewId: currentViewId,
|
||||
})),
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
|
||||
});
|
||||
},
|
||||
[createViewSortsMutation, currentViewId],
|
||||
@ -111,7 +109,6 @@ export const useViewSorts = <SortField>({
|
||||
viewId_key: { key: sort.key, viewId: currentViewId },
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -130,45 +127,40 @@ export const useViewSorts = <SortField>({
|
||||
viewId: { equals: currentViewId },
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
|
||||
});
|
||||
},
|
||||
[currentViewId, deleteViewSortsMutation],
|
||||
);
|
||||
|
||||
const handleSortsChange = useCallback(
|
||||
async (nextSorts: SelectedSortType<SortField>[]) => {
|
||||
if (!currentViewId) return;
|
||||
const persistSorts = useCallback(async () => {
|
||||
if (!currentViewId) return;
|
||||
|
||||
setSorts(nextSorts);
|
||||
const sortsToCreate = sorts.filter((sort) => !savedSortsByKey[sort.key]);
|
||||
await createViewSorts(sortsToCreate);
|
||||
|
||||
const sortsToCreate = nextSorts.filter(
|
||||
(nextSort) => !sortsByKey[nextSort.key],
|
||||
);
|
||||
await createViewSorts(sortsToCreate);
|
||||
const sortsToUpdate = sorts.filter(
|
||||
(sort) =>
|
||||
savedSortsByKey[sort.key] &&
|
||||
savedSortsByKey[sort.key].order !== sort.order,
|
||||
);
|
||||
await updateViewSorts(sortsToUpdate);
|
||||
|
||||
const sortsToUpdate = nextSorts.filter(
|
||||
(nextSort) =>
|
||||
sortsByKey[nextSort.key] &&
|
||||
sortsByKey[nextSort.key].order !== nextSort.order,
|
||||
);
|
||||
await updateViewSorts(sortsToUpdate);
|
||||
const sortKeys = sorts.map((sort) => sort.key);
|
||||
const sortKeysToDelete = Object.keys(savedSortsByKey).filter(
|
||||
(previousSortKey) => !sortKeys.includes(previousSortKey),
|
||||
);
|
||||
await deleteViewSorts(sortKeysToDelete);
|
||||
|
||||
const nextSortKeys = nextSorts.map((nextSort) => nextSort.key);
|
||||
const sortKeysToDelete = Object.keys(sortsByKey).filter(
|
||||
(previousSortKey) => !nextSortKeys.includes(previousSortKey),
|
||||
);
|
||||
return deleteViewSorts(sortKeysToDelete);
|
||||
},
|
||||
[
|
||||
createViewSorts,
|
||||
currentViewId,
|
||||
deleteViewSorts,
|
||||
setSorts,
|
||||
sortsByKey,
|
||||
updateViewSorts,
|
||||
],
|
||||
);
|
||||
return refetch();
|
||||
}, [
|
||||
currentViewId,
|
||||
sorts,
|
||||
createViewSorts,
|
||||
updateViewSorts,
|
||||
savedSortsByKey,
|
||||
deleteViewSorts,
|
||||
refetch,
|
||||
]);
|
||||
|
||||
return { handleSortsChange };
|
||||
return { persistSorts };
|
||||
};
|
||||
|
||||
@ -14,7 +14,7 @@ import { Company } from '~/generated/graphql';
|
||||
|
||||
export const companiesFilters: FilterDefinitionByEntity<Company>[] = [
|
||||
{
|
||||
field: 'name',
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
icon: (
|
||||
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
|
||||
@ -22,31 +22,31 @@ export const companiesFilters: FilterDefinitionByEntity<Company>[] = [
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
field: 'employees',
|
||||
key: 'employees',
|
||||
label: 'Employees',
|
||||
icon: <IconUsers size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
field: 'domainName',
|
||||
key: 'domainName',
|
||||
label: 'URL',
|
||||
icon: <IconLink size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
field: 'address',
|
||||
key: 'address',
|
||||
label: 'Address',
|
||||
icon: <IconMap size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
key: 'createdAt',
|
||||
label: 'Created at',
|
||||
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
field: 'accountOwnerId',
|
||||
key: 'accountOwnerId',
|
||||
label: 'Account owner',
|
||||
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'entity',
|
||||
|
||||
@ -15,19 +15,19 @@ import { FilterDropdownPeopleSearchSelect } from '../../modules/people/component
|
||||
export const opportunitiesFilters: FilterDefinitionByEntity<PipelineProgress>[] =
|
||||
[
|
||||
{
|
||||
field: 'amount',
|
||||
key: 'amount',
|
||||
label: 'Amount',
|
||||
icon: <IconCurrencyDollar size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
field: 'closeDate',
|
||||
key: 'closeDate',
|
||||
label: 'Close date',
|
||||
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
field: 'companyId',
|
||||
key: 'companyId',
|
||||
label: 'Company',
|
||||
icon: (
|
||||
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
|
||||
@ -40,7 +40,7 @@ export const opportunitiesFilters: FilterDefinitionByEntity<PipelineProgress>[]
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'pointOfContactId',
|
||||
key: 'pointOfContactId',
|
||||
label: 'Point of contact',
|
||||
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'entity',
|
||||
|
||||
@ -14,25 +14,25 @@ import { Person } from '~/generated/graphql';
|
||||
|
||||
export const peopleFilters: FilterDefinitionByEntity<Person>[] = [
|
||||
{
|
||||
field: 'firstName',
|
||||
key: 'firstName',
|
||||
label: 'First name',
|
||||
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
field: 'lastName',
|
||||
key: 'lastName',
|
||||
label: 'Last name',
|
||||
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
icon: <IconMail size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
field: 'companyId',
|
||||
key: 'companyId',
|
||||
label: 'Company',
|
||||
icon: (
|
||||
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
|
||||
@ -43,19 +43,19 @@ export const peopleFilters: FilterDefinitionByEntity<Person>[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'phone',
|
||||
key: 'phone',
|
||||
label: 'Phone',
|
||||
icon: <IconPhone size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
key: 'createdAt',
|
||||
label: 'Created at',
|
||||
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
field: 'city',
|
||||
key: 'city',
|
||||
label: 'City',
|
||||
icon: <IconMap size={icon.size.md} stroke={icon.stroke.sm} />,
|
||||
type: 'text',
|
||||
|
||||
@ -7,7 +7,7 @@ import { Activity } from '~/generated/graphql';
|
||||
|
||||
export const tasksFilters: FilterDefinitionByEntity<Activity>[] = [
|
||||
{
|
||||
field: 'assigneeId',
|
||||
key: 'assigneeId',
|
||||
label: 'Assignee',
|
||||
icon: <IconUser />,
|
||||
type: 'entity',
|
||||
|
||||
@ -3,47 +3,49 @@ import { Injectable } from '@nestjs/common';
|
||||
import { PureAbility, AbilityBuilder } from '@casl/ability';
|
||||
import { createPrismaAbility, PrismaQuery, Subjects } from '@casl/prisma';
|
||||
import {
|
||||
Attachment,
|
||||
Activity,
|
||||
Company,
|
||||
ActivityTarget,
|
||||
Attachment,
|
||||
Comment,
|
||||
Company,
|
||||
Favorite,
|
||||
Person,
|
||||
Pipeline,
|
||||
PipelineProgress,
|
||||
PipelineStage,
|
||||
RefreshToken,
|
||||
User,
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
ActivityTarget,
|
||||
Pipeline,
|
||||
PipelineStage,
|
||||
PipelineProgress,
|
||||
UserSettings,
|
||||
View,
|
||||
ViewField,
|
||||
Favorite,
|
||||
ViewFilter,
|
||||
ViewSort,
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { AbilityAction } from './ability.action';
|
||||
|
||||
type SubjectsAbility = Subjects<{
|
||||
User: User;
|
||||
Workspace: Workspace;
|
||||
WorkspaceMember: WorkspaceMember;
|
||||
Company: Company;
|
||||
Person: Person;
|
||||
RefreshToken: RefreshToken;
|
||||
Activity: Activity;
|
||||
Comment: Comment;
|
||||
ActivityTarget: ActivityTarget;
|
||||
Pipeline: Pipeline;
|
||||
PipelineStage: PipelineStage;
|
||||
PipelineProgress: PipelineProgress;
|
||||
Attachment: Attachment;
|
||||
Comment: Comment;
|
||||
Company: Company;
|
||||
Favorite: Favorite;
|
||||
Person: Person;
|
||||
Pipeline: Pipeline;
|
||||
PipelineProgress: PipelineProgress;
|
||||
PipelineStage: PipelineStage;
|
||||
RefreshToken: RefreshToken;
|
||||
User: User;
|
||||
UserSettings: UserSettings;
|
||||
View: View;
|
||||
ViewField: ViewField;
|
||||
Favorite: Favorite;
|
||||
ViewFilter: ViewFilter;
|
||||
ViewSort: ViewSort;
|
||||
Workspace: Workspace;
|
||||
WorkspaceMember: WorkspaceMember;
|
||||
}>;
|
||||
|
||||
export type AppAbility = PureAbility<
|
||||
@ -147,10 +149,11 @@ export class AbilityFactory {
|
||||
can(AbilityAction.Create, 'ViewField', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Update, 'ViewField', { workspaceId: workspace.id });
|
||||
|
||||
//Favorite
|
||||
can(AbilityAction.Read, 'Favorite', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Create, 'Favorite');
|
||||
can(AbilityAction.Delete, 'Favorite', { workspaceId: workspace.id });
|
||||
// ViewFilter
|
||||
can(AbilityAction.Read, 'ViewFilter', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Create, 'ViewFilter', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Update, 'ViewFilter', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Delete, 'ViewFilter', { workspaceId: workspace.id });
|
||||
|
||||
// ViewSort
|
||||
can(AbilityAction.Read, 'ViewSort', { workspaceId: workspace.id });
|
||||
@ -158,6 +161,11 @@ export class AbilityFactory {
|
||||
can(AbilityAction.Update, 'ViewSort', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Delete, 'ViewSort', { workspaceId: workspace.id });
|
||||
|
||||
// Favorite
|
||||
can(AbilityAction.Read, 'Favorite', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Create, 'Favorite');
|
||||
can(AbilityAction.Delete, 'Favorite', { workspaceId: workspace.id });
|
||||
|
||||
return build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +116,12 @@ import {
|
||||
UpdateViewAbilityHandler,
|
||||
DeleteViewAbilityHandler,
|
||||
} from './handlers/view.ability-handler';
|
||||
import {
|
||||
CreateViewFilterAbilityHandler,
|
||||
DeleteViewFilterAbilityHandler,
|
||||
ReadViewFilterAbilityHandler,
|
||||
UpdateViewFilterAbilityHandler,
|
||||
} from './handlers/view-filter.ability-handler';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@ -213,6 +219,11 @@ import {
|
||||
ReadViewFieldAbilityHandler,
|
||||
CreateViewFieldAbilityHandler,
|
||||
UpdateViewFieldAbilityHandler,
|
||||
// ViewFilter
|
||||
ReadViewFilterAbilityHandler,
|
||||
CreateViewFilterAbilityHandler,
|
||||
UpdateViewFilterAbilityHandler,
|
||||
DeleteViewFilterAbilityHandler,
|
||||
// ViewSort
|
||||
ReadViewSortAbilityHandler,
|
||||
CreateViewSortAbilityHandler,
|
||||
@ -312,6 +323,11 @@ import {
|
||||
ReadViewFieldAbilityHandler,
|
||||
CreateViewFieldAbilityHandler,
|
||||
UpdateViewFieldAbilityHandler,
|
||||
// ViewFilter
|
||||
ReadViewFilterAbilityHandler,
|
||||
CreateViewFilterAbilityHandler,
|
||||
UpdateViewFilterAbilityHandler,
|
||||
DeleteViewFilterAbilityHandler,
|
||||
// ViewSort
|
||||
ReadViewSortAbilityHandler,
|
||||
CreateViewSortAbilityHandler,
|
||||
|
||||
122
server/src/ability/handlers/view-filter.ability-handler.ts
Normal file
122
server/src/ability/handlers/view-filter.ability-handler.ts
Normal file
@ -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 { ViewFilterWhereUniqueInput } from 'src/core/@generated/view-filter/view-filter-where-unique.input';
|
||||
import { ViewFilterWhereInput } from 'src/core/@generated/view-filter/view-filter-where.input';
|
||||
|
||||
class ViewFilterArgs {
|
||||
where?: ViewFilterWhereInput | ViewFilterWhereUniqueInput;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const isViewFilterWhereUniqueInput = (
|
||||
input: ViewFilterWhereInput | ViewFilterWhereUniqueInput,
|
||||
): input is ViewFilterWhereUniqueInput => 'viewId_key' in input;
|
||||
|
||||
@Injectable()
|
||||
export class ReadViewFilterAbilityHandler implements IAbilityHandler {
|
||||
handle(ability: AppAbility) {
|
||||
return ability.can(AbilityAction.Read, 'ViewFilter');
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CreateViewFilterAbilityHandler 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(
|
||||
'ViewFilter',
|
||||
ability,
|
||||
this.prismaService.client,
|
||||
args,
|
||||
);
|
||||
|
||||
if (!allowed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ability.can(AbilityAction.Create, 'ViewFilter');
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UpdateViewFilterAbilityHandler implements IAbilityHandler {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||
const gqlContext = GqlExecutionContext.create(context);
|
||||
const args = gqlContext.getArgs<ViewFilterArgs>();
|
||||
const viewFilter = await this.prismaService.client.viewFilter.findFirst({
|
||||
where:
|
||||
args.where && isViewFilterWhereUniqueInput(args.where)
|
||||
? args.where.viewId_key
|
||||
: args.where,
|
||||
});
|
||||
assert(viewFilter, '', NotFoundException);
|
||||
|
||||
const allowed = await relationAbilityChecker(
|
||||
'ViewFilter',
|
||||
ability,
|
||||
this.prismaService.client,
|
||||
args,
|
||||
);
|
||||
|
||||
if (!allowed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ability.can(AbilityAction.Update, subject('ViewFilter', viewFilter));
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DeleteViewFilterAbilityHandler implements IAbilityHandler {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||
const gqlContext = GqlExecutionContext.create(context);
|
||||
const args = gqlContext.getArgs<ViewFilterArgs>();
|
||||
const where = convertToWhereInput(
|
||||
args.where && isViewFilterWhereUniqueInput(args.where)
|
||||
? args.where.viewId_key
|
||||
: args.where,
|
||||
);
|
||||
const viewFilters = await this.prismaService.client.viewFilter.findMany({
|
||||
where,
|
||||
});
|
||||
assert(viewFilters.length, '', NotFoundException);
|
||||
|
||||
for (const viewFilter of viewFilters) {
|
||||
const allowed = ability.can(
|
||||
AbilityAction.Delete,
|
||||
subject('ViewFilter', viewFilter),
|
||||
);
|
||||
|
||||
if (!allowed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
102
server/src/core/view/resolvers/view-filter.resolver.ts
Normal file
102
server/src/core/view/resolvers/view-filter.resolver.ts
Normal file
@ -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 {
|
||||
CreateViewFilterAbilityHandler,
|
||||
DeleteViewFilterAbilityHandler,
|
||||
ReadViewFilterAbilityHandler,
|
||||
UpdateViewFilterAbilityHandler,
|
||||
} from 'src/ability/handlers/view-filter.ability-handler';
|
||||
import { FindManyViewFilterArgs } from 'src/core/@generated/view-filter/find-many-view-filter.args';
|
||||
import { ViewFilter } from 'src/core/@generated/view-filter/view-filter.model';
|
||||
import { ViewFilterService } from 'src/core/view/services/view-filter.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 { UpdateOneViewFilterArgs } from 'src/core/@generated/view-filter/update-one-view-filter.args';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
||||
import { DeleteManyViewFilterArgs } from 'src/core/@generated/view-filter/delete-many-view-filter.args';
|
||||
import { CreateManyViewFilterArgs } from 'src/core/@generated/view-filter/create-many-view-filter.args';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => ViewFilter)
|
||||
export class ViewFilterResolver {
|
||||
constructor(private readonly viewFilterService: ViewFilterService) {}
|
||||
|
||||
@Mutation(() => AffectedRows)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateViewFilterAbilityHandler)
|
||||
async createManyViewFilter(
|
||||
@Args() args: CreateManyViewFilterArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<AffectedRows> {
|
||||
return this.viewFilterService.createMany({
|
||||
data: args.data.map((data) => ({
|
||||
...data,
|
||||
workspaceId: workspace.id,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => [ViewFilter])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadViewFilterAbilityHandler)
|
||||
async findManyViewFilter(
|
||||
@Args() args: FindManyViewFilterArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'ViewFilter' })
|
||||
prismaSelect: PrismaSelect<'ViewFilter'>,
|
||||
): Promise<Partial<ViewFilter>[]> {
|
||||
return this.viewFilterService.findMany({
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).ViewFilter],
|
||||
}
|
||||
: accessibleBy(ability).ViewFilter,
|
||||
orderBy: args.orderBy,
|
||||
cursor: args.cursor,
|
||||
take: args.take,
|
||||
skip: args.skip,
|
||||
distinct: args.distinct,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => ViewFilter)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateViewFilterAbilityHandler)
|
||||
async updateOneViewFilter(
|
||||
@Args() args: UpdateOneViewFilterArgs,
|
||||
@PrismaSelector({ modelName: 'ViewFilter' })
|
||||
prismaSelect: PrismaSelect<'ViewFilter'>,
|
||||
): Promise<Partial<ViewFilter>> {
|
||||
return this.viewFilterService.update({
|
||||
data: args.data,
|
||||
where: args.where,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.ViewFilterUpdateArgs);
|
||||
}
|
||||
|
||||
@Mutation(() => AffectedRows, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteViewFilterAbilityHandler)
|
||||
async deleteManyViewFilter(
|
||||
@Args() args: DeleteManyViewFilterArgs,
|
||||
): Promise<AffectedRows> {
|
||||
return this.viewFilterService.deleteMany({
|
||||
where: args.where,
|
||||
});
|
||||
}
|
||||
}
|
||||
39
server/src/core/view/services/view-filter.service.ts
Normal file
39
server/src/core/view/services/view-filter.service.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewFilterService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.viewFilter.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.viewFilter.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.viewFilter.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.viewFilter.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.viewFilter.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.viewFilter.create;
|
||||
createMany = this.prismaService.client.viewFilter.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.viewFilter.update;
|
||||
upsert = this.prismaService.client.viewFilter.upsert;
|
||||
updateMany = this.prismaService.client.viewFilter.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.viewFilter.delete;
|
||||
deleteMany = this.prismaService.client.viewFilter.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.viewFilter.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.viewFilter.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.viewFilter.groupBy;
|
||||
}
|
||||
@ -6,14 +6,18 @@ import { ViewSortService } from './services/view-sort.service';
|
||||
import { ViewSortResolver } from './resolvers/view-sort.resolver';
|
||||
import { ViewService } from './services/view.service';
|
||||
import { ViewResolver } from './resolvers/view.resolver';
|
||||
import { ViewFilterService } from './services/view-filter.service';
|
||||
import { ViewFilterResolver } from './resolvers/view-filter.resolver';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
ViewService,
|
||||
ViewFieldService,
|
||||
ViewFilterService,
|
||||
ViewSortService,
|
||||
ViewResolver,
|
||||
ViewFieldResolver,
|
||||
ViewFilterResolver,
|
||||
ViewSortResolver,
|
||||
],
|
||||
})
|
||||
|
||||
@ -112,8 +112,6 @@ export class WorkspaceService {
|
||||
activityTarget,
|
||||
activity,
|
||||
view,
|
||||
viewField,
|
||||
viewSort,
|
||||
} = this.prismaService.client;
|
||||
|
||||
const activitys = await activity.findMany({
|
||||
@ -156,12 +154,6 @@ export class WorkspaceService {
|
||||
view.deleteMany({
|
||||
where,
|
||||
}),
|
||||
viewField.deleteMany({
|
||||
where,
|
||||
}),
|
||||
viewSort.deleteMany({
|
||||
where,
|
||||
}),
|
||||
refreshToken.deleteMany({
|
||||
where: { userId },
|
||||
}),
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ViewFilterOperand" AS ENUM ('Contains', 'DoesNotContain', 'GreaterThan', 'LessThan', 'Is', 'IsNot');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "viewFilters" (
|
||||
"displayValue" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"operand" "ViewFilterOperand" NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"viewId" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "viewFilters_pkey" PRIMARY KEY ("viewId","key")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "viewFilters" ADD CONSTRAINT "viewFilters_viewId_fkey" FOREIGN KEY ("viewId") REFERENCES "views"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "viewFilters" ADD CONSTRAINT "viewFilters_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -174,6 +174,7 @@ model Workspace {
|
||||
pipelineProgresses PipelineProgress[]
|
||||
activityTargets ActivityTarget[]
|
||||
viewFields ViewField[]
|
||||
viewFilters ViewFilter[]
|
||||
views View[]
|
||||
viewSorts ViewSort[]
|
||||
|
||||
@ -582,6 +583,7 @@ model View {
|
||||
id String @id @default(uuid())
|
||||
|
||||
fields ViewField[]
|
||||
filters ViewFilter[]
|
||||
name String
|
||||
objectId String
|
||||
sorts ViewSort[]
|
||||
@ -596,6 +598,34 @@ model View {
|
||||
@@map("views")
|
||||
}
|
||||
|
||||
enum ViewFilterOperand {
|
||||
Contains
|
||||
DoesNotContain
|
||||
GreaterThan
|
||||
LessThan
|
||||
Is
|
||||
IsNot
|
||||
}
|
||||
|
||||
model ViewFilter {
|
||||
displayValue String
|
||||
key String
|
||||
name String
|
||||
operand ViewFilterOperand
|
||||
value String
|
||||
|
||||
view View @relation(fields: [viewId], references: [id], onDelete: Cascade)
|
||||
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("viewFilters")
|
||||
}
|
||||
|
||||
enum ViewSortDirection {
|
||||
asc
|
||||
desc
|
||||
|
||||
@ -18,6 +18,7 @@ export type ModelSelectMap = {
|
||||
Attachment: Prisma.AttachmentSelect;
|
||||
Favorite: Prisma.FavoriteSelect;
|
||||
View: Prisma.ViewSelect;
|
||||
ViewFilter: Prisma.ViewFilterSelect;
|
||||
ViewSort: Prisma.ViewSortSelect;
|
||||
ViewField: Prisma.ViewFieldSelect;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user