Feature/filter and sort board (#725)

* Get pipeline progress from stage IDs

* Rename hooks file

* Addd first amount filter

* Add remaining filters

* Design fixes

* Add filtering on creation date or amount

* Fix card updates and creations with the new state management

* Keep ordering when dropping a card

* Add remainint sorts

* Make board header more generic

* Move available filters and sorts to board options

* Fix decorators for test

* Add pipeline stage ids to mock data

* Adapt mock data

* Linter
This commit is contained in:
Emilien Chauvet
2023-07-17 19:32:47 -07:00
committed by GitHub
parent 9895c1d5d6
commit 6301bc2fbf
19 changed files with 784 additions and 413 deletions

View File

@ -3372,57 +3372,6 @@ export type WorkspaceUpdateInput = {
workspaceMember?: InputMaybe<WorkspaceMemberUpdateManyWithoutWorkspaceNestedInput>; workspaceMember?: InputMaybe<WorkspaceMemberUpdateManyWithoutWorkspaceNestedInput>;
}; };
export type CreateEventMutationVariables = Exact<{
type: Scalars['String'];
data: Scalars['JSON'];
}>;
export type CreateEventMutation = { __typename?: 'Mutation', createEvent: { __typename?: 'Analytics', success: boolean } };
export type CheckUserExistsQueryVariables = Exact<{
email: Scalars['String'];
}>;
export type CheckUserExistsQuery = { __typename?: 'Query', checkUserExists: { __typename?: 'UserExists', exists: boolean } };
export type ChallengeMutationVariables = Exact<{
email: Scalars['String'];
password: Scalars['String'];
}>;
export type ChallengeMutation = { __typename?: 'Mutation', challenge: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', expiresAt: string, token: string } } };
export type SignUpMutationVariables = Exact<{
email: Scalars['String'];
password: Scalars['String'];
workspaceInviteHash?: InputMaybe<Scalars['String']>;
}>;
export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', expiresAt: string, token: string } } };
export type VerifyMutationVariables = Exact<{
loginToken: Scalars['String'];
}>;
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null } } | null }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type RenewTokenMutationVariables = Exact<{
refreshToken: Scalars['String'];
}>;
export type RenewTokenMutation = { __typename?: 'Mutation', renewToken: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', expiresAt: string, token: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', demoMode: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean } } };
export type CreateCommentMutationVariables = Exact<{ export type CreateCommentMutationVariables = Exact<{
commentId: Scalars['String']; commentId: Scalars['String'];
commentText: Scalars['String']; commentText: Scalars['String'];
@ -3498,6 +3447,57 @@ export type UpdateCommentThreadMutationVariables = Exact<{
export type UpdateCommentThreadMutation = { __typename?: 'Mutation', updateOneCommentThread: { __typename?: 'CommentThread', id: string, body?: string | null, title?: string | null, type: ActivityType } }; export type UpdateCommentThreadMutation = { __typename?: 'Mutation', updateOneCommentThread: { __typename?: 'CommentThread', id: string, body?: string | null, title?: string | null, type: ActivityType } };
export type CreateEventMutationVariables = Exact<{
type: Scalars['String'];
data: Scalars['JSON'];
}>;
export type CreateEventMutation = { __typename?: 'Mutation', createEvent: { __typename?: 'Analytics', success: boolean } };
export type CheckUserExistsQueryVariables = Exact<{
email: Scalars['String'];
}>;
export type CheckUserExistsQuery = { __typename?: 'Query', checkUserExists: { __typename?: 'UserExists', exists: boolean } };
export type ChallengeMutationVariables = Exact<{
email: Scalars['String'];
password: Scalars['String'];
}>;
export type ChallengeMutation = { __typename?: 'Mutation', challenge: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', expiresAt: string, token: string } } };
export type SignUpMutationVariables = Exact<{
email: Scalars['String'];
password: Scalars['String'];
workspaceInviteHash?: InputMaybe<Scalars['String']>;
}>;
export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', expiresAt: string, token: string } } };
export type VerifyMutationVariables = Exact<{
loginToken: Scalars['String'];
}>;
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null } } | null }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type RenewTokenMutationVariables = Exact<{
refreshToken: Scalars['String'];
}>;
export type RenewTokenMutation = { __typename?: 'Mutation', renewToken: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', expiresAt: string, token: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', demoMode: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean } } };
export type GetCompaniesQueryVariables = Exact<{ export type GetCompaniesQueryVariables = Exact<{
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>; orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
where?: InputMaybe<CompanyWhereInput>; where?: InputMaybe<CompanyWhereInput>;
@ -3649,14 +3649,15 @@ export type GetPipelinesQueryVariables = Exact<{
}>; }>;
export type GetPipelinesQuery = { __typename?: 'Query', findManyPipeline: Array<{ __typename?: 'Pipeline', id: string, name: string, pipelineProgressableType: PipelineProgressableType, pipelineStages?: Array<{ __typename?: 'PipelineStage', id: string, name: string, color: string, index?: number | null, pipelineProgresses?: Array<{ __typename?: 'PipelineProgress', id: string }> | null }> | null }> }; export type GetPipelinesQuery = { __typename?: 'Query', findManyPipeline: Array<{ __typename?: 'Pipeline', id: string, name: string, pipelineProgressableType: PipelineProgressableType, pipelineStages?: Array<{ __typename?: 'PipelineStage', id: string, name: string, color: string, index?: number | null }> | null }> };
export type GetPipelineProgressQueryVariables = Exact<{ export type GetPipelineProgressQueryVariables = Exact<{
where?: InputMaybe<PipelineProgressWhereInput>; where?: InputMaybe<PipelineProgressWhereInput>;
orderBy?: InputMaybe<Array<PipelineProgressOrderByWithRelationInput> | PipelineProgressOrderByWithRelationInput>;
}>; }>;
export type GetPipelineProgressQuery = { __typename?: 'Query', findManyPipelineProgress: Array<{ __typename?: 'PipelineProgress', id: string, progressableType: PipelineProgressableType, progressableId: string, amount?: number | null, closeDate?: string | null }> }; export type GetPipelineProgressQuery = { __typename?: 'Query', findManyPipelineProgress: Array<{ __typename?: 'PipelineProgress', id: string, pipelineStageId: string, progressableType: PipelineProgressableType, progressableId: string, amount?: number | null, closeDate?: string | null }> };
export type UpdateOnePipelineProgressMutationVariables = Exact<{ export type UpdateOnePipelineProgressMutationVariables = Exact<{
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -3797,297 +3798,6 @@ export type RemoveWorkspaceMemberMutationVariables = Exact<{
export type RemoveWorkspaceMemberMutation = { __typename?: 'Mutation', deleteWorkspaceMember: { __typename?: 'WorkspaceMember', id: string } }; export type RemoveWorkspaceMemberMutation = { __typename?: 'Mutation', deleteWorkspaceMember: { __typename?: 'WorkspaceMember', id: string } };
export const CreateEventDocument = gql`
mutation CreateEvent($type: String!, $data: JSON!) {
createEvent(type: $type, data: $data) {
success
}
}
`;
export type CreateEventMutationFn = Apollo.MutationFunction<CreateEventMutation, CreateEventMutationVariables>;
/**
* __useCreateEventMutation__
*
* To run a mutation, you first call `useCreateEventMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateEventMutation` 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 [createEventMutation, { data, loading, error }] = useCreateEventMutation({
* variables: {
* type: // value for 'type'
* data: // value for 'data'
* },
* });
*/
export function useCreateEventMutation(baseOptions?: Apollo.MutationHookOptions<CreateEventMutation, CreateEventMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateEventMutation, CreateEventMutationVariables>(CreateEventDocument, options);
}
export type CreateEventMutationHookResult = ReturnType<typeof useCreateEventMutation>;
export type CreateEventMutationResult = Apollo.MutationResult<CreateEventMutation>;
export type CreateEventMutationOptions = Apollo.BaseMutationOptions<CreateEventMutation, CreateEventMutationVariables>;
export const CheckUserExistsDocument = gql`
query CheckUserExists($email: String!) {
checkUserExists(email: $email) {
exists
}
}
`;
/**
* __useCheckUserExistsQuery__
*
* To run a query within a React component, call `useCheckUserExistsQuery` and pass it any options that fit your needs.
* When your component renders, `useCheckUserExistsQuery` 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 } = useCheckUserExistsQuery({
* variables: {
* email: // value for 'email'
* },
* });
*/
export function useCheckUserExistsQuery(baseOptions: Apollo.QueryHookOptions<CheckUserExistsQuery, CheckUserExistsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<CheckUserExistsQuery, CheckUserExistsQueryVariables>(CheckUserExistsDocument, options);
}
export function useCheckUserExistsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CheckUserExistsQuery, CheckUserExistsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<CheckUserExistsQuery, CheckUserExistsQueryVariables>(CheckUserExistsDocument, options);
}
export type CheckUserExistsQueryHookResult = ReturnType<typeof useCheckUserExistsQuery>;
export type CheckUserExistsLazyQueryHookResult = ReturnType<typeof useCheckUserExistsLazyQuery>;
export type CheckUserExistsQueryResult = Apollo.QueryResult<CheckUserExistsQuery, CheckUserExistsQueryVariables>;
export const ChallengeDocument = gql`
mutation Challenge($email: String!, $password: String!) {
challenge(email: $email, password: $password) {
loginToken {
expiresAt
token
}
}
}
`;
export type ChallengeMutationFn = Apollo.MutationFunction<ChallengeMutation, ChallengeMutationVariables>;
/**
* __useChallengeMutation__
*
* To run a mutation, you first call `useChallengeMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useChallengeMutation` 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 [challengeMutation, { data, loading, error }] = useChallengeMutation({
* variables: {
* email: // value for 'email'
* password: // value for 'password'
* },
* });
*/
export function useChallengeMutation(baseOptions?: Apollo.MutationHookOptions<ChallengeMutation, ChallengeMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<ChallengeMutation, ChallengeMutationVariables>(ChallengeDocument, options);
}
export type ChallengeMutationHookResult = ReturnType<typeof useChallengeMutation>;
export type ChallengeMutationResult = Apollo.MutationResult<ChallengeMutation>;
export type ChallengeMutationOptions = Apollo.BaseMutationOptions<ChallengeMutation, ChallengeMutationVariables>;
export const SignUpDocument = gql`
mutation SignUp($email: String!, $password: String!, $workspaceInviteHash: String) {
signUp(
email: $email
password: $password
workspaceInviteHash: $workspaceInviteHash
) {
loginToken {
expiresAt
token
}
}
}
`;
export type SignUpMutationFn = Apollo.MutationFunction<SignUpMutation, SignUpMutationVariables>;
/**
* __useSignUpMutation__
*
* To run a mutation, you first call `useSignUpMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSignUpMutation` 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 [signUpMutation, { data, loading, error }] = useSignUpMutation({
* variables: {
* email: // value for 'email'
* password: // value for 'password'
* workspaceInviteHash: // value for 'workspaceInviteHash'
* },
* });
*/
export function useSignUpMutation(baseOptions?: Apollo.MutationHookOptions<SignUpMutation, SignUpMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SignUpMutation, SignUpMutationVariables>(SignUpDocument, options);
}
export type SignUpMutationHookResult = ReturnType<typeof useSignUpMutation>;
export type SignUpMutationResult = Apollo.MutationResult<SignUpMutation>;
export type SignUpMutationOptions = Apollo.BaseMutationOptions<SignUpMutation, SignUpMutationVariables>;
export const VerifyDocument = gql`
mutation Verify($loginToken: String!) {
verify(loginToken: $loginToken) {
user {
id
email
displayName
firstName
lastName
workspaceMember {
id
workspace {
id
domainName
displayName
logo
}
}
}
tokens {
accessToken {
token
expiresAt
}
refreshToken {
token
expiresAt
}
}
}
}
`;
export type VerifyMutationFn = Apollo.MutationFunction<VerifyMutation, VerifyMutationVariables>;
/**
* __useVerifyMutation__
*
* To run a mutation, you first call `useVerifyMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useVerifyMutation` 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 [verifyMutation, { data, loading, error }] = useVerifyMutation({
* variables: {
* loginToken: // value for 'loginToken'
* },
* });
*/
export function useVerifyMutation(baseOptions?: Apollo.MutationHookOptions<VerifyMutation, VerifyMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<VerifyMutation, VerifyMutationVariables>(VerifyDocument, options);
}
export type VerifyMutationHookResult = ReturnType<typeof useVerifyMutation>;
export type VerifyMutationResult = Apollo.MutationResult<VerifyMutation>;
export type VerifyMutationOptions = Apollo.BaseMutationOptions<VerifyMutation, VerifyMutationVariables>;
export const RenewTokenDocument = gql`
mutation RenewToken($refreshToken: String!) {
renewToken(refreshToken: $refreshToken) {
tokens {
accessToken {
expiresAt
token
}
refreshToken {
token
expiresAt
}
}
}
}
`;
export type RenewTokenMutationFn = Apollo.MutationFunction<RenewTokenMutation, RenewTokenMutationVariables>;
/**
* __useRenewTokenMutation__
*
* To run a mutation, you first call `useRenewTokenMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRenewTokenMutation` 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 [renewTokenMutation, { data, loading, error }] = useRenewTokenMutation({
* variables: {
* refreshToken: // value for 'refreshToken'
* },
* });
*/
export function useRenewTokenMutation(baseOptions?: Apollo.MutationHookOptions<RenewTokenMutation, RenewTokenMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<RenewTokenMutation, RenewTokenMutationVariables>(RenewTokenDocument, options);
}
export type RenewTokenMutationHookResult = ReturnType<typeof useRenewTokenMutation>;
export type RenewTokenMutationResult = Apollo.MutationResult<RenewTokenMutation>;
export type RenewTokenMutationOptions = Apollo.BaseMutationOptions<RenewTokenMutation, RenewTokenMutationVariables>;
export const GetClientConfigDocument = gql`
query GetClientConfig {
clientConfig {
authProviders {
google
password
}
demoMode
debugMode
telemetry {
enabled
anonymizationEnabled
}
}
}
`;
/**
* __useGetClientConfigQuery__
*
* To run a query within a React component, call `useGetClientConfigQuery` and pass it any options that fit your needs.
* When your component renders, `useGetClientConfigQuery` 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 } = useGetClientConfigQuery({
* variables: {
* },
* });
*/
export function useGetClientConfigQuery(baseOptions?: Apollo.QueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
export const CreateCommentDocument = gql` export const CreateCommentDocument = gql`
mutation CreateComment($commentId: String!, $commentText: String!, $authorId: String!, $commentThreadId: String!, $createdAt: DateTime!) { mutation CreateComment($commentId: String!, $commentText: String!, $authorId: String!, $commentThreadId: String!, $createdAt: DateTime!) {
createOneComment( createOneComment(
@ -4497,6 +4207,297 @@ export function useUpdateCommentThreadMutation(baseOptions?: Apollo.MutationHook
export type UpdateCommentThreadMutationHookResult = ReturnType<typeof useUpdateCommentThreadMutation>; export type UpdateCommentThreadMutationHookResult = ReturnType<typeof useUpdateCommentThreadMutation>;
export type UpdateCommentThreadMutationResult = Apollo.MutationResult<UpdateCommentThreadMutation>; export type UpdateCommentThreadMutationResult = Apollo.MutationResult<UpdateCommentThreadMutation>;
export type UpdateCommentThreadMutationOptions = Apollo.BaseMutationOptions<UpdateCommentThreadMutation, UpdateCommentThreadMutationVariables>; export type UpdateCommentThreadMutationOptions = Apollo.BaseMutationOptions<UpdateCommentThreadMutation, UpdateCommentThreadMutationVariables>;
export const CreateEventDocument = gql`
mutation CreateEvent($type: String!, $data: JSON!) {
createEvent(type: $type, data: $data) {
success
}
}
`;
export type CreateEventMutationFn = Apollo.MutationFunction<CreateEventMutation, CreateEventMutationVariables>;
/**
* __useCreateEventMutation__
*
* To run a mutation, you first call `useCreateEventMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateEventMutation` 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 [createEventMutation, { data, loading, error }] = useCreateEventMutation({
* variables: {
* type: // value for 'type'
* data: // value for 'data'
* },
* });
*/
export function useCreateEventMutation(baseOptions?: Apollo.MutationHookOptions<CreateEventMutation, CreateEventMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateEventMutation, CreateEventMutationVariables>(CreateEventDocument, options);
}
export type CreateEventMutationHookResult = ReturnType<typeof useCreateEventMutation>;
export type CreateEventMutationResult = Apollo.MutationResult<CreateEventMutation>;
export type CreateEventMutationOptions = Apollo.BaseMutationOptions<CreateEventMutation, CreateEventMutationVariables>;
export const CheckUserExistsDocument = gql`
query CheckUserExists($email: String!) {
checkUserExists(email: $email) {
exists
}
}
`;
/**
* __useCheckUserExistsQuery__
*
* To run a query within a React component, call `useCheckUserExistsQuery` and pass it any options that fit your needs.
* When your component renders, `useCheckUserExistsQuery` 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 } = useCheckUserExistsQuery({
* variables: {
* email: // value for 'email'
* },
* });
*/
export function useCheckUserExistsQuery(baseOptions: Apollo.QueryHookOptions<CheckUserExistsQuery, CheckUserExistsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<CheckUserExistsQuery, CheckUserExistsQueryVariables>(CheckUserExistsDocument, options);
}
export function useCheckUserExistsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CheckUserExistsQuery, CheckUserExistsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<CheckUserExistsQuery, CheckUserExistsQueryVariables>(CheckUserExistsDocument, options);
}
export type CheckUserExistsQueryHookResult = ReturnType<typeof useCheckUserExistsQuery>;
export type CheckUserExistsLazyQueryHookResult = ReturnType<typeof useCheckUserExistsLazyQuery>;
export type CheckUserExistsQueryResult = Apollo.QueryResult<CheckUserExistsQuery, CheckUserExistsQueryVariables>;
export const ChallengeDocument = gql`
mutation Challenge($email: String!, $password: String!) {
challenge(email: $email, password: $password) {
loginToken {
expiresAt
token
}
}
}
`;
export type ChallengeMutationFn = Apollo.MutationFunction<ChallengeMutation, ChallengeMutationVariables>;
/**
* __useChallengeMutation__
*
* To run a mutation, you first call `useChallengeMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useChallengeMutation` 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 [challengeMutation, { data, loading, error }] = useChallengeMutation({
* variables: {
* email: // value for 'email'
* password: // value for 'password'
* },
* });
*/
export function useChallengeMutation(baseOptions?: Apollo.MutationHookOptions<ChallengeMutation, ChallengeMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<ChallengeMutation, ChallengeMutationVariables>(ChallengeDocument, options);
}
export type ChallengeMutationHookResult = ReturnType<typeof useChallengeMutation>;
export type ChallengeMutationResult = Apollo.MutationResult<ChallengeMutation>;
export type ChallengeMutationOptions = Apollo.BaseMutationOptions<ChallengeMutation, ChallengeMutationVariables>;
export const SignUpDocument = gql`
mutation SignUp($email: String!, $password: String!, $workspaceInviteHash: String) {
signUp(
email: $email
password: $password
workspaceInviteHash: $workspaceInviteHash
) {
loginToken {
expiresAt
token
}
}
}
`;
export type SignUpMutationFn = Apollo.MutationFunction<SignUpMutation, SignUpMutationVariables>;
/**
* __useSignUpMutation__
*
* To run a mutation, you first call `useSignUpMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSignUpMutation` 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 [signUpMutation, { data, loading, error }] = useSignUpMutation({
* variables: {
* email: // value for 'email'
* password: // value for 'password'
* workspaceInviteHash: // value for 'workspaceInviteHash'
* },
* });
*/
export function useSignUpMutation(baseOptions?: Apollo.MutationHookOptions<SignUpMutation, SignUpMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SignUpMutation, SignUpMutationVariables>(SignUpDocument, options);
}
export type SignUpMutationHookResult = ReturnType<typeof useSignUpMutation>;
export type SignUpMutationResult = Apollo.MutationResult<SignUpMutation>;
export type SignUpMutationOptions = Apollo.BaseMutationOptions<SignUpMutation, SignUpMutationVariables>;
export const VerifyDocument = gql`
mutation Verify($loginToken: String!) {
verify(loginToken: $loginToken) {
user {
id
email
displayName
firstName
lastName
workspaceMember {
id
workspace {
id
domainName
displayName
logo
}
}
}
tokens {
accessToken {
token
expiresAt
}
refreshToken {
token
expiresAt
}
}
}
}
`;
export type VerifyMutationFn = Apollo.MutationFunction<VerifyMutation, VerifyMutationVariables>;
/**
* __useVerifyMutation__
*
* To run a mutation, you first call `useVerifyMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useVerifyMutation` 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 [verifyMutation, { data, loading, error }] = useVerifyMutation({
* variables: {
* loginToken: // value for 'loginToken'
* },
* });
*/
export function useVerifyMutation(baseOptions?: Apollo.MutationHookOptions<VerifyMutation, VerifyMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<VerifyMutation, VerifyMutationVariables>(VerifyDocument, options);
}
export type VerifyMutationHookResult = ReturnType<typeof useVerifyMutation>;
export type VerifyMutationResult = Apollo.MutationResult<VerifyMutation>;
export type VerifyMutationOptions = Apollo.BaseMutationOptions<VerifyMutation, VerifyMutationVariables>;
export const RenewTokenDocument = gql`
mutation RenewToken($refreshToken: String!) {
renewToken(refreshToken: $refreshToken) {
tokens {
accessToken {
expiresAt
token
}
refreshToken {
token
expiresAt
}
}
}
}
`;
export type RenewTokenMutationFn = Apollo.MutationFunction<RenewTokenMutation, RenewTokenMutationVariables>;
/**
* __useRenewTokenMutation__
*
* To run a mutation, you first call `useRenewTokenMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRenewTokenMutation` 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 [renewTokenMutation, { data, loading, error }] = useRenewTokenMutation({
* variables: {
* refreshToken: // value for 'refreshToken'
* },
* });
*/
export function useRenewTokenMutation(baseOptions?: Apollo.MutationHookOptions<RenewTokenMutation, RenewTokenMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<RenewTokenMutation, RenewTokenMutationVariables>(RenewTokenDocument, options);
}
export type RenewTokenMutationHookResult = ReturnType<typeof useRenewTokenMutation>;
export type RenewTokenMutationResult = Apollo.MutationResult<RenewTokenMutation>;
export type RenewTokenMutationOptions = Apollo.BaseMutationOptions<RenewTokenMutation, RenewTokenMutationVariables>;
export const GetClientConfigDocument = gql`
query GetClientConfig {
clientConfig {
authProviders {
google
password
}
demoMode
debugMode
telemetry {
enabled
anonymizationEnabled
}
}
}
`;
/**
* __useGetClientConfigQuery__
*
* To run a query within a React component, call `useGetClientConfigQuery` and pass it any options that fit your needs.
* When your component renders, `useGetClientConfigQuery` 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 } = useGetClientConfigQuery({
* variables: {
* },
* });
*/
export function useGetClientConfigQuery(baseOptions?: Apollo.QueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
export const GetCompaniesDocument = gql` export const GetCompaniesDocument = gql`
query GetCompanies($orderBy: [CompanyOrderByWithRelationInput!], $where: CompanyWhereInput) { query GetCompanies($orderBy: [CompanyOrderByWithRelationInput!], $where: CompanyWhereInput) {
companies: findManyCompany(orderBy: $orderBy, where: $where) { companies: findManyCompany(orderBy: $orderBy, where: $where) {
@ -5227,9 +5228,6 @@ export const GetPipelinesDocument = gql`
name name
color color
index index
pipelineProgresses {
id
}
} }
} }
} }
@ -5263,9 +5261,10 @@ export type GetPipelinesQueryHookResult = ReturnType<typeof useGetPipelinesQuery
export type GetPipelinesLazyQueryHookResult = ReturnType<typeof useGetPipelinesLazyQuery>; export type GetPipelinesLazyQueryHookResult = ReturnType<typeof useGetPipelinesLazyQuery>;
export type GetPipelinesQueryResult = Apollo.QueryResult<GetPipelinesQuery, GetPipelinesQueryVariables>; export type GetPipelinesQueryResult = Apollo.QueryResult<GetPipelinesQuery, GetPipelinesQueryVariables>;
export const GetPipelineProgressDocument = gql` export const GetPipelineProgressDocument = gql`
query GetPipelineProgress($where: PipelineProgressWhereInput) { query GetPipelineProgress($where: PipelineProgressWhereInput, $orderBy: [PipelineProgressOrderByWithRelationInput!]) {
findManyPipelineProgress(where: $where, orderBy: {createdAt: asc}) { findManyPipelineProgress(where: $where, orderBy: $orderBy) {
id id
pipelineStageId
progressableType progressableType
progressableId progressableId
amount amount
@ -5287,6 +5286,7 @@ export const GetPipelineProgressDocument = gql`
* const { data, loading, error } = useGetPipelineProgressQuery({ * const { data, loading, error } = useGetPipelineProgressQuery({
* variables: { * variables: {
* where: // value for 'where' * where: // value for 'where'
* orderBy: // value for 'orderBy'
* }, * },
* }); * });
*/ */

View File

@ -1,7 +1,7 @@
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { companyBoardOptions } from '@/companies/components/companyBoardOptions';
import { EntityBoard } from '@/pipeline/components/EntityBoard'; import { EntityBoard } from '@/pipeline/components/EntityBoard';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
import { BoardDecorator } from '~/testing/decorators'; import { BoardDecorator } from '~/testing/decorators';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
@ -17,7 +17,12 @@ type Story = StoryObj<typeof EntityBoard>;
export const OneColumnBoard: Story = { export const OneColumnBoard: Story = {
render: getRenderWrapperForComponent( render: getRenderWrapperForComponent(
<EntityBoard boardOptions={companyBoardOptions} />, <EntityBoard
boardOptions={opportunitiesBoardOptions}
updateSorts={() => {
return;
}}
/>,
), ),
parameters: { parameters: {
msw: graphqlMocks, msw: graphqlMocks,

View File

@ -9,10 +9,6 @@ export const pipeline = {
name: 'New', name: 'New',
index: 0, index: 0,
color: '#B76796', color: '#B76796',
pipelineProgresses: [
{ id: 'fe256b39-3ec3-4fe7-8998-b76aa0bfb600' },
{ id: '4a886c90-f4f2-4984-8222-882ebbb905d6' },
],
}, },
{ {
id: 'pipeline-stage-2', id: 'pipeline-stage-2',

View File

@ -5,7 +5,7 @@ import styled from '@emotion/styled';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState'; import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
import { GET_PIPELINES } from '@/pipeline/queries'; import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
import { BoardCardContext } from '@/pipeline/states/BoardCardContext'; import { BoardCardContext } from '@/pipeline/states/BoardCardContext';
import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState'; import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState';
import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState'; import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState';
@ -108,7 +108,10 @@ export function CompanyBoardCard() {
amount: pipelineProgress.amount, amount: pipelineProgress.amount,
closeDate: pipelineProgress.closeDate || null, closeDate: pipelineProgress.closeDate || null,
}, },
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''], refetchQueries: [
getOperationName(GET_PIPELINE_PROGRESS) ?? '',
getOperationName(GET_PIPELINES) ?? '',
],
}); });
}, },
[updatePipelineProgress], [updatePipelineProgress],

View File

@ -1,21 +1,26 @@
import { Context } from 'react';
import { FilterDropdownEntitySearchSelect } from '@/ui/filter-n-sort/components/FilterDropdownEntitySearchSelect'; import { FilterDropdownEntitySearchSelect } from '@/ui/filter-n-sort/components/FilterDropdownEntitySearchSelect';
import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/filterDropdownSearchInputScopedState'; import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/filterDropdownSearchInputScopedState';
import { filterDropdownSelectedEntityIdScopedState } from '@/ui/filter-n-sort/states/filterDropdownSelectedEntityIdScopedState'; import { filterDropdownSelectedEntityIdScopedState } from '@/ui/filter-n-sort/states/filterDropdownSelectedEntityIdScopedState';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
import { TableContext } from '@/ui/table/states/TableContext';
import { useFilteredSearchCompanyQuery } from '../queries'; import { useFilteredSearchCompanyQuery } from '../queries';
export function FilterDropdownCompanySearchSelect() { export function FilterDropdownCompanySearchSelect({
context,
}: {
context: Context<string | null>;
}) {
const filterDropdownSearchInput = useRecoilScopedValue( const filterDropdownSearchInput = useRecoilScopedValue(
filterDropdownSearchInputScopedState, filterDropdownSearchInputScopedState,
TableContext, context,
); );
const [filterDropdownSelectedEntityId] = useRecoilScopedState( const [filterDropdownSelectedEntityId] = useRecoilScopedState(
filterDropdownSelectedEntityIdScopedState, filterDropdownSelectedEntityIdScopedState,
TableContext, context,
); );
const usersForSelect = useFilteredSearchCompanyQuery({ const usersForSelect = useFilteredSearchCompanyQuery({
@ -28,7 +33,7 @@ export function FilterDropdownCompanySearchSelect() {
return ( return (
<FilterDropdownEntitySearchSelect <FilterDropdownEntitySearchSelect
entitiesForSelect={usersForSelect} entitiesForSelect={usersForSelect}
context={TableContext} context={context}
/> />
); );
} }

View File

@ -1,6 +1,7 @@
import { useEffect } from 'react'; import { useEffect, useMemo } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil'; import { useRecoilCallback, useRecoilState } from 'recoil';
import { useInitializeCompanyBoardFilters } from '@/companies/hooks/useInitializeCompanyBoardFilters';
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState'; import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
import { import {
CompanyForBoard, CompanyForBoard,
@ -11,15 +12,30 @@ import { boardState } from '@/pipeline/states/boardState';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { isBoardLoadedState } from '@/pipeline/states/isBoardLoadedState'; import { isBoardLoadedState } from '@/pipeline/states/isBoardLoadedState';
import { BoardPipelineStageColumn } from '@/ui/board/components/Board'; import { BoardPipelineStageColumn } from '@/ui/board/components/Board';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql';
import { import {
Pipeline, Pipeline,
PipelineStage,
useGetCompaniesQuery, useGetCompaniesQuery,
useGetPipelineProgressQuery, useGetPipelineProgressQuery,
useGetPipelinesQuery, useGetPipelinesQuery,
} from '~/generated/graphql'; } from '~/generated/graphql';
export function HookCompanyBoard() { import { CompanyBoardContext } from '../states/CompanyBoardContext';
export function HooksCompanyBoard({
availableFilters,
orderBy,
}: {
availableFilters: FilterDefinition[];
orderBy: PipelineProgresses_Order_By[];
}) {
useInitializeCompanyBoardFilters({
availableFilters,
});
const [currentPipeline, setCurrentPipeline] = const [currentPipeline, setCurrentPipeline] =
useRecoilState(currentPipelineState); useRecoilState(currentPipelineState);
@ -44,29 +60,47 @@ export function HookCompanyBoard() {
title: pipelineStage.name, title: pipelineStage.name,
colorCode: pipelineStage.color, colorCode: pipelineStage.color,
index: pipelineStage.index || 0, index: pipelineStage.index || 0,
pipelineProgressIds: pipelineProgressIds: [],
pipelineStage.pipelineProgresses?.map(
(item) => item.id as string,
) || [],
})) || []; })) || [];
setBoard(initialBoard); setBoard(initialBoard);
setIsBoardLoaded(true);
}, },
}); });
const pipelineProgressIds = currentPipeline?.pipelineStages const pipelineStageIds = currentPipeline?.pipelineStages
?.map((pipelineStage: PipelineStage) => ?.map((pipelineStage) => pipelineStage.id)
(
pipelineStage.pipelineProgresses?.map((item) => item.id as string) || []
).flat(),
)
.flat(); .flat();
const filters = useRecoilScopedValue(filtersScopedState, CompanyBoardContext);
const whereFilters = useMemo(() => {
return {
AND: [
{ pipelineStageId: { in: pipelineStageIds } },
...filters.map(turnFilterIntoWhereClause),
],
};
}, [filters, pipelineStageIds]) as any;
const pipelineProgressesQuery = useGetPipelineProgressQuery({ const pipelineProgressesQuery = useGetPipelineProgressQuery({
variables: { variables: {
where: { where: whereFilters,
id: { in: pipelineProgressIds }, orderBy,
}, },
onCompleted: (data) => {
const pipelineProgresses = data?.findManyPipelineProgress || [];
setBoard((board) =>
board?.map((boardPipelineStage) => ({
...boardPipelineStage,
pipelineProgressIds: pipelineProgresses
.filter(
(pipelineProgress) =>
pipelineProgress.pipelineStageId ===
boardPipelineStage.pipelineStageId,
)
.map((pipelineProgress) => pipelineProgress.id),
})),
);
setIsBoardLoaded(true);
}, },
}); });

View File

@ -3,7 +3,7 @@ import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { GET_PIPELINES } from '@/pipeline/queries'; import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
import { BoardColumnContext } from '@/pipeline/states/BoardColumnContext'; import { BoardColumnContext } from '@/pipeline/states/BoardColumnContext';
import { boardState } from '@/pipeline/states/boardState'; import { boardState } from '@/pipeline/states/boardState';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
@ -37,7 +37,10 @@ export function NewCompanyProgressButton() {
} = usePreviousHotkeyScope(); } = usePreviousHotkeyScope();
const [createOnePipelineProgress] = useCreateOnePipelineProgressMutation({ const [createOnePipelineProgress] = useCreateOnePipelineProgressMutation({
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''], refetchQueries: [
getOperationName(GET_PIPELINE_PROGRESS) ?? '',
getOperationName(GET_PIPELINES) ?? '',
],
}); });
const handleEntitySelect = useCallback( const handleEntitySelect = useCallback(

View File

@ -0,0 +1,21 @@
import { useEffect } from 'react';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
export function useInitializeCompanyBoardFilters({
availableFilters,
}: {
availableFilters: FilterDefinition[];
}) {
const [, setAvailableFilters] = useRecoilScopedState(
availableFiltersScopedState,
CompanyBoardContext,
);
useEffect(() => {
setAvailableFilters(availableFilters);
}, [setAvailableFilters, availableFilters]);
}

View File

@ -1,10 +1,18 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350 import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
import { IconList } from '@tabler/icons-react';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { BoardHeader } from '@/ui/board/components/BoardHeader';
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { import {
PipelineProgress, PipelineProgress,
PipelineProgressOrderByWithRelationInput,
PipelineStage, PipelineStage,
useUpdateOnePipelineProgressStageMutation, useUpdateOnePipelineProgressStageMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
@ -13,14 +21,31 @@ import {
getOptimisticlyUpdatedBoard, getOptimisticlyUpdatedBoard,
StyledBoard, StyledBoard,
} from '../../ui/board/components/Board'; } from '../../ui/board/components/Board';
import { GET_PIPELINE_PROGRESS } from '../queries';
import { BoardColumnContext } from '../states/BoardColumnContext'; import { BoardColumnContext } from '../states/BoardColumnContext';
import { boardState } from '../states/boardState'; import { boardState } from '../states/boardState';
import { BoardOptions } from '../types/BoardOptions'; import { BoardOptions } from '../types/BoardOptions';
import { EntityBoardColumn } from './EntityBoardColumn'; import { EntityBoardColumn } from './EntityBoardColumn';
export function EntityBoard({ boardOptions }: { boardOptions: BoardOptions }) { const StyledBoardWithHeader = styled.div`
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
`;
export function EntityBoard({
boardOptions,
updateSorts,
}: {
boardOptions: BoardOptions;
updateSorts: (
sorts: Array<SelectedSortType<PipelineProgressOrderByWithRelationInput>>,
) => void;
}) {
const [board, setBoard] = useRecoilState(boardState); const [board, setBoard] = useRecoilState(boardState);
const theme = useTheme();
const [updatePipelineProgressStage] = const [updatePipelineProgressStage] =
useUpdateOnePipelineProgressStageMutation(); useUpdateOnePipelineProgressStageMutation();
@ -34,6 +59,7 @@ export function EntityBoard({ boardOptions }: { boardOptions: BoardOptions }) {
id: pipelineProgressId, id: pipelineProgressId,
pipelineStageId, pipelineStageId,
}, },
refetchQueries: [getOperationName(GET_PIPELINE_PROGRESS) ?? ''],
}); });
}, },
[updatePipelineProgressStage], [updatePipelineProgressStage],
@ -69,18 +95,27 @@ export function EntityBoard({ boardOptions }: { boardOptions: BoardOptions }) {
: []; : [];
return (board?.length ?? 0) > 0 ? ( return (board?.length ?? 0) > 0 ? (
<StyledBoard> <StyledBoardWithHeader>
<DragDropContext onDragEnd={onDragEnd}> <BoardHeader
{sortedBoard.map((column) => ( viewName="All opportunities"
<RecoilScope viewIcon={<IconList size={theme.icon.size.md} />}
SpecificContext={BoardColumnContext} availableSorts={boardOptions.sorts}
key={column.pipelineStageId} onSortsUpdate={updateSorts}
> context={CompanyBoardContext}
<EntityBoardColumn boardOptions={boardOptions} column={column} /> />
</RecoilScope> <StyledBoard>
))} <DragDropContext onDragEnd={onDragEnd}>
</DragDropContext> {sortedBoard.map((column) => (
</StyledBoard> <RecoilScope
SpecificContext={BoardColumnContext}
key={column.pipelineStageId}
>
<EntityBoardColumn boardOptions={boardOptions} column={column} />
</RecoilScope>
))}
</DragDropContext>
</StyledBoard>
</StyledBoardWithHeader>
) : ( ) : (
<></> <></>
); );

View File

@ -1,5 +1,14 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
import {
PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By,
SortOrder as Order_By,
} from '~/generated/graphql';
export type PipelineProgressesSelectedSortType =
SelectedSortType<PipelineProgresses_Order_By>;
export const GET_PIPELINES = gql` export const GET_PIPELINES = gql`
query GetPipelines($where: PipelineWhereInput) { query GetPipelines($where: PipelineWhereInput) {
findManyPipeline(where: $where) { findManyPipeline(where: $where) {
@ -11,18 +20,19 @@ export const GET_PIPELINES = gql`
name name
color color
index index
pipelineProgresses {
id
}
} }
} }
} }
`; `;
export const GET_PIPELINE_PROGRESS = gql` export const GET_PIPELINE_PROGRESS = gql`
query GetPipelineProgress($where: PipelineProgressWhereInput) { query GetPipelineProgress(
findManyPipelineProgress(where: $where, orderBy: { createdAt: asc }) { $where: PipelineProgressWhereInput
$orderBy: [PipelineProgressOrderByWithRelationInput!]
) {
findManyPipelineProgress(where: $where, orderBy: $orderBy) {
id id
pipelineStageId
progressableType progressableType
progressableId progressableId
amount amount
@ -83,3 +93,9 @@ export const ADD_ENTITY_TO_PIPELINE = gql`
} }
} }
`; `;
export const defaultPipelineProgressOrderBy: PipelineProgresses_Order_By[] = [
{
createdAt: Order_By.Asc,
},
];

View File

@ -1,4 +1,11 @@
import { FilterDefinitionByEntity } from '@/ui/filter-n-sort/types/FilterDefinitionByEntity';
import { SortType } from '@/ui/filter-n-sort/types/interface';
import { PipelineProgress } from '~/generated/graphql';
import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql';
export type BoardOptions = { export type BoardOptions = {
newCardComponent: React.ReactNode; newCardComponent: React.ReactNode;
cardComponent: React.ReactNode; cardComponent: React.ReactNode;
filters: FilterDefinitionByEntity<PipelineProgress>[];
sorts: Array<SortType<PipelineProgresses_Order_By>>;
}; };

View File

@ -0,0 +1,131 @@
import { Context, ReactNode, useCallback, useState } from 'react';
import styled from '@emotion/styled';
import { FilterDropdownButton } from '@/ui/filter-n-sort/components/FilterDropdownButton';
import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar';
import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton';
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
type OwnProps<SortField> = {
viewName: string;
viewIcon?: ReactNode;
availableSorts?: Array<SortType<SortField>>;
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
context: Context<string | null>;
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
`;
const StyledTableHeader = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.secondary};
display: flex;
flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium};
height: 40px;
justify-content: space-between;
padding-left: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledIcon = styled.div`
display: flex;
margin-left: ${({ theme }) => theme.spacing(1)};
margin-right: ${({ theme }) => theme.spacing(2)};
& > svg {
font-size: ${({ theme }) => theme.icon.size.sm};
}
`;
const StyledViewSection = styled.div`
display: flex;
`;
const StyledFilters = styled.div`
display: flex;
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: 2px;
`;
export function BoardHeader<SortField>({
viewName,
viewIcon,
availableSorts,
onSortsUpdate,
context,
}: OwnProps<SortField>) {
const [sorts, innerSetSorts] = useState<Array<SelectedSortType<SortField>>>(
[],
);
const sortSelect = useCallback(
(newSort: SelectedSortType<SortField>) => {
const newSorts = updateSortOrFilterByKey(sorts, newSort);
innerSetSorts(newSorts);
onSortsUpdate && onSortsUpdate(newSorts);
},
[onSortsUpdate, sorts],
);
const sortUnselect = useCallback(
(sortKey: string) => {
const newSorts = sorts.filter((sort) => sort.key !== sortKey);
innerSetSorts(newSorts);
onSortsUpdate && onSortsUpdate(newSorts);
},
[onSortsUpdate, sorts],
);
return (
<StyledContainer>
<StyledTableHeader>
<StyledViewSection>
<StyledIcon>{viewIcon}</StyledIcon>
{viewName}
</StyledViewSection>
<StyledFilters>
<FilterDropdownButton
context={context}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>
<SortDropdownButton<SortField>
isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []}
onSortSelect={sortSelect}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>
</StyledFilters>
</StyledTableHeader>
<SortAndFilterBar
context={context}
sorts={sorts}
onRemoveSort={sortUnselect}
onCancelClick={() => {
innerSetSorts([]);
onSortsUpdate && onSortsUpdate([]);
}}
/>
</StyledContainer>
);
}
function updateSortOrFilterByKey<SortOrFilter extends { key: string }>(
sorts: Readonly<SortOrFilter[]>,
newSort: SortOrFilter,
): SortOrFilter[] {
const newSorts = [...sorts];
const existingSortIndex = sorts.findIndex((sort) => sort.key === newSort.key);
if (existingSortIndex !== -1) {
newSorts[existingSortIndex] = newSort;
} else {
newSorts.push(newSort);
}
return newSorts;
}

View File

@ -1,26 +1,54 @@
import { useCallback, useState } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { companyBoardOptions } from '@/companies/components/companyBoardOptions'; import { HooksCompanyBoard } from '@/companies/components/HooksCompanyBoard';
import { HookCompanyBoard } from '@/companies/components/HookCompanyBoard';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext'; import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { BoardActionBarButtonDeletePipelineProgress } from '@/pipeline/components/BoardActionBarButtonDeletePipelineProgress'; import { BoardActionBarButtonDeletePipelineProgress } from '@/pipeline/components/BoardActionBarButtonDeletePipelineProgress';
import { EntityBoard } from '@/pipeline/components/EntityBoard'; import { EntityBoard } from '@/pipeline/components/EntityBoard';
import { EntityBoardActionBar } from '@/pipeline/components/EntityBoardActionBar'; import { EntityBoardActionBar } from '@/pipeline/components/EntityBoardActionBar';
import {
defaultPipelineProgressOrderBy,
PipelineProgressesSelectedSortType,
} from '@/pipeline/queries';
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
import { IconTargetArrow } from '@/ui/icon/index'; import { IconTargetArrow } from '@/ui/icon/index';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer'; import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { PipelineProgressOrderByWithRelationInput } from '~/generated/graphql';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
export function Opportunities() { export function Opportunities() {
const theme = useTheme(); const theme = useTheme();
const [orderBy, setOrderBy] = useState<
PipelineProgressOrderByWithRelationInput[]
>(defaultPipelineProgressOrderBy);
const updateSorts = useCallback(
(sorts: Array<PipelineProgressesSelectedSortType>) => {
setOrderBy(
sorts.length
? reduceSortsToOrderBy(sorts)
: defaultPipelineProgressOrderBy,
);
},
[],
);
return ( return (
<WithTopBarContainer <WithTopBarContainer
title="Opportunities" title="Opportunities"
icon={<IconTargetArrow size={theme.icon.size.md} />} icon={<IconTargetArrow size={theme.icon.size.md} />}
> >
<HookCompanyBoard />
<RecoilScope SpecificContext={CompanyBoardContext}> <RecoilScope SpecificContext={CompanyBoardContext}>
<EntityBoard boardOptions={companyBoardOptions} /> <HooksCompanyBoard
availableFilters={opportunitiesBoardOptions.filters}
orderBy={orderBy}
/>
<EntityBoard
boardOptions={opportunitiesBoardOptions}
updateSorts={updateSorts}
/>
<EntityBoardActionBar> <EntityBoardActionBar>
<BoardActionBarButtonDeletePipelineProgress /> <BoardActionBarButtonDeletePipelineProgress />
</EntityBoardActionBar> </EntityBoardActionBar>

View File

@ -0,0 +1,37 @@
import { FilterDropdownCompanySearchSelect } from '@/companies/components/FilterDropdownCompanySearchSelect';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { FilterDefinitionByEntity } from '@/ui/filter-n-sort/types/FilterDefinitionByEntity';
import {
IconBuildingSkyscraper,
IconCalendarEvent,
IconCurrencyDollar,
} from '@/ui/icon/index';
import { icon } from '@/ui/themes/icon';
import { PipelineProgress } from '~/generated/graphql';
export const opportunitiesFilters: FilterDefinitionByEntity<PipelineProgress>[] =
[
{
field: 'amount',
label: 'Amount',
icon: <IconCurrencyDollar size={icon.size.md} stroke={icon.stroke.sm} />,
type: 'number',
},
{
field: 'closeDate',
label: 'Close date',
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />,
type: 'date',
},
{
field: 'progressableId',
label: 'Company',
icon: (
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
),
type: 'entity',
entitySelectComponent: (
<FilterDropdownCompanySearchSelect context={CompanyBoardContext} />
),
},
];

View File

@ -0,0 +1,21 @@
import { SortType } from '@/ui/filter-n-sort/types/interface';
import { IconCalendarEvent, IconCurrencyDollar } from '@/ui/icon/index';
import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql';
export const opportunitiesSorts = [
{
key: 'createdAt',
label: 'Creation',
icon: <IconCalendarEvent size={16} />,
},
{
key: 'amount',
label: 'Amount',
icon: <IconCurrencyDollar size={16} />,
},
{
key: 'closeDate',
label: 'Expected close date',
icon: <IconCalendarEvent size={16} />,
},
] satisfies Array<SortType<PipelineProgresses_Order_By>>;

View File

@ -3,11 +3,16 @@ import { NewCompanyProgressButton } from '@/companies/components/NewCompanyProgr
import { BoardOptions } from '@/pipeline/types/BoardOptions'; import { BoardOptions } from '@/pipeline/types/BoardOptions';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
export const companyBoardOptions: BoardOptions = { import { opportunitiesFilters } from './opportunities-filters';
import { opportunitiesSorts } from './opportunities-sorts';
export const opportunitiesBoardOptions: BoardOptions = {
newCardComponent: ( newCardComponent: (
<RecoilScope> <RecoilScope>
<NewCompanyProgressButton /> <NewCompanyProgressButton />
</RecoilScope> </RecoilScope>
), ),
cardComponent: <CompanyBoardCard />, cardComponent: <CompanyBoardCard />,
filters: opportunitiesFilters,
sorts: opportunitiesSorts,
}; };

View File

@ -8,6 +8,7 @@ import {
IconPhone, IconPhone,
IconUser, IconUser,
} from '@/ui/icon/index'; } from '@/ui/icon/index';
import { TableContext } from '@/ui/table/states/TableContext';
import { icon } from '@/ui/themes/icon'; import { icon } from '@/ui/themes/icon';
import { Person } from '~/generated/graphql'; import { Person } from '~/generated/graphql';
@ -37,7 +38,9 @@ export const peopleFilters: FilterDefinitionByEntity<Person>[] = [
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} /> <IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
), ),
type: 'entity', type: 'entity',
entitySelectComponent: <FilterDropdownCompanySearchSelect />, entitySelectComponent: (
<FilterDropdownCompanySearchSelect context={TableContext} />
),
}, },
{ {
field: 'phone', field: 'phone',

View File

@ -3,9 +3,9 @@ import { ApolloProvider } from '@apollo/client';
import { Decorator } from '@storybook/react'; import { Decorator } from '@storybook/react';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { pipeline } from '@/companies/__stories__/mock-data'; import { HooksCompanyBoard } from '@/companies/components/HooksCompanyBoard';
import { HookCompanyBoard } from '@/companies/components/HookCompanyBoard';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext'; import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { defaultPipelineProgressOrderBy } from '@/pipeline/queries';
import { BoardCardContext } from '@/pipeline/states/BoardCardContext'; import { BoardCardContext } from '@/pipeline/states/BoardCardContext';
import { BoardColumnContext } from '@/pipeline/states/BoardColumnContext'; import { BoardColumnContext } from '@/pipeline/states/BoardColumnContext';
import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState'; import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState';
@ -14,6 +14,7 @@ import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedSta
import { CellContext } from '@/ui/table/states/CellContext'; import { CellContext } from '@/ui/table/states/CellContext';
import { RowContext } from '@/ui/table/states/RowContext'; import { RowContext } from '@/ui/table/states/RowContext';
import { mockedPipelineProgressData } from './mock-data/pipeline-progress';
import { ComponentStorybookLayout } from './ComponentStorybookLayout'; import { ComponentStorybookLayout } from './ComponentStorybookLayout';
import { mockedClient } from './mockedClient'; import { mockedClient } from './mockedClient';
@ -41,8 +42,11 @@ export const CellPositionDecorator: Decorator = (Story) => (
export const BoardDecorator: Decorator = (Story) => ( export const BoardDecorator: Decorator = (Story) => (
<> <>
<HookCompanyBoard />
<RecoilScope SpecificContext={CompanyBoardContext}> <RecoilScope SpecificContext={CompanyBoardContext}>
<HooksCompanyBoard
availableFilters={[]}
orderBy={defaultPipelineProgressOrderBy}
/>
<Story /> <Story />
</RecoilScope> </RecoilScope>
</> </>
@ -53,8 +57,7 @@ function HookLoadFakeBoardContextState() {
pipelineProgressIdScopedState, pipelineProgressIdScopedState,
BoardCardContext, BoardCardContext,
); );
const pipelineProgress = const pipelineProgress = mockedPipelineProgressData[1];
pipeline?.pipelineStages?.[0]?.pipelineProgresses?.[0];
useEffect(() => { useEffect(() => {
setPipelineProgressId(pipelineProgress?.id || ''); setPipelineProgressId(pipelineProgress?.id || '');
}, [pipelineProgress?.id, setPipelineProgressId]); }, [pipelineProgress?.id, setPipelineProgressId]);
@ -64,8 +67,11 @@ function HookLoadFakeBoardContextState() {
export const BoardCardDecorator: Decorator = (Story) => { export const BoardCardDecorator: Decorator = (Story) => {
return ( return (
<> <>
<HookCompanyBoard />
<RecoilScope SpecificContext={CompanyBoardContext}> <RecoilScope SpecificContext={CompanyBoardContext}>
<HooksCompanyBoard
availableFilters={[]}
orderBy={defaultPipelineProgressOrderBy}
/>
<RecoilScope SpecificContext={BoardColumnContext}> <RecoilScope SpecificContext={BoardColumnContext}>
<RecoilScope SpecificContext={BoardCardContext}> <RecoilScope SpecificContext={BoardCardContext}>
<HookLoadFakeBoardContextState /> <HookLoadFakeBoardContextState />

View File

@ -1,8 +1,17 @@
import { PipelineProgress, User } from '../../generated/graphql'; import {
PipelineProgress,
PipelineProgressableType,
User,
} from '../../generated/graphql';
type MockedPipelineProgress = Pick< type MockedPipelineProgress = Pick<
PipelineProgress, PipelineProgress,
'id' | 'amount' | 'closeDate' | 'progressableId' | 'id'
| 'amount'
| 'closeDate'
| 'progressableId'
| 'pipelineStageId'
| 'progressableType'
> & { > & {
accountOwner: Pick< accountOwner: Pick<
User, User,
@ -31,13 +40,17 @@ export const mockedPipelineProgressData: Array<MockedPipelineProgress> = [
closeDate: '2021-10-01T00:00:00.000Z', closeDate: '2021-10-01T00:00:00.000Z',
progressableId: '0', progressableId: '0',
accountOwner: accountOwner, accountOwner: accountOwner,
pipelineStageId: 'another-pipeline-stage-1',
progressableType: PipelineProgressableType.Company,
}, },
{ {
id: 'fe256b39-3ec3-4fe7-8998-b76aa0bfb600', id: 'fe256b39-3ec3-4fe7-8998-b76aa0bfb600',
progressableId: '89bb825c-171e-4bcc-9cf7-43448d6fb278', progressableId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
pipelineStageId: 'fe256b39-3ec3-4fe3-8998-b76aa0bfb600',
amount: 7, amount: 7,
closeDate: '2021-10-01T00:00:00.000Z', closeDate: '2021-10-01T00:00:00.000Z',
accountOwner, accountOwner,
progressableType: PipelineProgressableType.Company,
}, },
{ {
id: '4a886c90-f4f2-4984-8222-882ebbb905d6', id: '4a886c90-f4f2-4984-8222-882ebbb905d6',
@ -45,5 +58,7 @@ export const mockedPipelineProgressData: Array<MockedPipelineProgress> = [
amount: 100, amount: 100,
closeDate: '2021-10-01T00:00:00.000Z', closeDate: '2021-10-01T00:00:00.000Z',
accountOwner, accountOwner,
pipelineStageId: 'fe256b39-3ec3-4fe3-8998-b76aa0bfb600',
progressableType: PipelineProgressableType.Company,
}, },
]; ];