Update company card (#512)

* Add card rows

* WIP - add amount

* Refactor board state to separate pipeline progress data and company data

* Add migration and generated code

* Pass pipeline progress properties to the comapny card

* WIP-editable

* Enable amount edition

* Nits

* Remove useless import

* Fix empty board bug

* Use cell for editable values on company card

* Add fields

* Enable edition for closeDate

* Add dummy edits for recurring and probability

* Nits

* remove useless fields

* Nits

* Fix user provider

* Add generated code

* Fix nits, reorder migrations, fix login

* Fix tests

* Fix lint
This commit is contained in:
Emilien Chauvet
2023-07-06 18:41:44 -07:00
committed by GitHub
parent 1144bd13ed
commit 7d6adbaa73
68 changed files with 721 additions and 108 deletions

View File

@ -756,11 +756,11 @@ export type CompanyOrderByRelationAggregateInput = {
export type CompanyOrderByWithRelationInput = {
accountOwner?: InputMaybe<UserOrderByWithRelationInput>;
accountOwnerId?: InputMaybe<SortOrder>;
accountOwnerId?: InputMaybe<SortOrderInput>;
address?: InputMaybe<SortOrder>;
createdAt?: InputMaybe<SortOrder>;
domainName?: InputMaybe<SortOrder>;
employees?: InputMaybe<SortOrder>;
employees?: InputMaybe<SortOrderInput>;
id?: InputMaybe<SortOrder>;
name?: InputMaybe<SortOrder>;
people?: InputMaybe<PersonOrderByRelationAggregateInput>;
@ -1247,6 +1247,11 @@ export type NullableStringFieldUpdateOperationsInput = {
set?: InputMaybe<Scalars['String']>;
};
export enum NullsOrder {
First = 'first',
Last = 'last'
}
export type Person = {
__typename?: 'Person';
_commentCount: Scalars['Int'];
@ -1327,7 +1332,7 @@ export type PersonOrderByRelationAggregateInput = {
export type PersonOrderByWithRelationInput = {
city?: InputMaybe<SortOrder>;
company?: InputMaybe<CompanyOrderByWithRelationInput>;
companyId?: InputMaybe<SortOrder>;
companyId?: InputMaybe<SortOrderInput>;
createdAt?: InputMaybe<SortOrder>;
email?: InputMaybe<SortOrder>;
firstName?: InputMaybe<SortOrder>;
@ -1535,6 +1540,8 @@ export type PipelineOrderByWithRelationInput = {
export type PipelineProgress = {
__typename?: 'PipelineProgress';
amount?: Maybe<Scalars['Int']>;
closeDate?: Maybe<Scalars['DateTime']>;
createdAt: Scalars['DateTime'];
id: Scalars['ID'];
pipeline: Pipeline;
@ -1547,6 +1554,8 @@ export type PipelineProgress = {
};
export type PipelineProgressCreateInput = {
amount?: InputMaybe<Scalars['Int']>;
closeDate?: InputMaybe<Scalars['DateTime']>;
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
pipeline: PipelineCreateNestedOneWithoutPipelineProgressesInput;
@ -1557,6 +1566,7 @@ export type PipelineProgressCreateInput = {
};
export type PipelineProgressCreateManyPipelineInput = {
amount?: InputMaybe<Scalars['Int']>;
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
pipelineStageId: Scalars['String'];
@ -1571,6 +1581,7 @@ export type PipelineProgressCreateManyPipelineInputEnvelope = {
};
export type PipelineProgressCreateManyPipelineStageInput = {
amount?: InputMaybe<Scalars['Int']>;
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
pipelineId: Scalars['String'];
@ -1585,6 +1596,7 @@ export type PipelineProgressCreateManyPipelineStageInputEnvelope = {
};
export type PipelineProgressCreateManyWorkspaceInput = {
amount?: InputMaybe<Scalars['Int']>;
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
pipelineId: Scalars['String'];
@ -1629,6 +1641,7 @@ export type PipelineProgressCreateOrConnectWithoutWorkspaceInput = {
};
export type PipelineProgressCreateWithoutPipelineInput = {
amount?: InputMaybe<Scalars['Int']>;
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
pipelineStage: PipelineStageCreateNestedOneWithoutPipelineProgressesInput;
@ -1638,6 +1651,7 @@ export type PipelineProgressCreateWithoutPipelineInput = {
};
export type PipelineProgressCreateWithoutPipelineStageInput = {
amount?: InputMaybe<Scalars['Int']>;
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
pipeline: PipelineCreateNestedOneWithoutPipelineProgressesInput;
@ -1647,6 +1661,7 @@ export type PipelineProgressCreateWithoutPipelineStageInput = {
};
export type PipelineProgressCreateWithoutWorkspaceInput = {
amount?: InputMaybe<Scalars['Int']>;
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
pipeline: PipelineCreateNestedOneWithoutPipelineProgressesInput;
@ -1667,6 +1682,8 @@ export type PipelineProgressOrderByRelationAggregateInput = {
};
export type PipelineProgressOrderByWithRelationInput = {
amount?: InputMaybe<SortOrderInput>;
closeDate?: InputMaybe<SortOrderInput>;
createdAt?: InputMaybe<SortOrder>;
id?: InputMaybe<SortOrder>;
pipeline?: InputMaybe<PipelineOrderByWithRelationInput>;
@ -1679,6 +1696,8 @@ export type PipelineProgressOrderByWithRelationInput = {
};
export enum PipelineProgressScalarFieldEnum {
Amount = 'amount',
CloseDate = 'closeDate',
CreatedAt = 'createdAt',
DeletedAt = 'deletedAt',
Id = 'id',
@ -1694,6 +1713,7 @@ export type PipelineProgressScalarWhereInput = {
AND?: InputMaybe<Array<PipelineProgressScalarWhereInput>>;
NOT?: InputMaybe<Array<PipelineProgressScalarWhereInput>>;
OR?: InputMaybe<Array<PipelineProgressScalarWhereInput>>;
amount?: InputMaybe<IntNullableFilter>;
createdAt?: InputMaybe<DateTimeFilter>;
id?: InputMaybe<StringFilter>;
pipelineId?: InputMaybe<StringFilter>;
@ -1704,6 +1724,8 @@ export type PipelineProgressScalarWhereInput = {
};
export type PipelineProgressUpdateInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
closeDate?: InputMaybe<NullableDateTimeFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>;
pipeline?: InputMaybe<PipelineUpdateOneRequiredWithoutPipelineProgressesNestedInput>;
@ -1714,6 +1736,7 @@ export type PipelineProgressUpdateInput = {
};
export type PipelineProgressUpdateManyMutationInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>;
progressableId?: InputMaybe<StringFieldUpdateOperationsInput>;
@ -1794,6 +1817,7 @@ export type PipelineProgressUpdateWithWhereUniqueWithoutWorkspaceInput = {
};
export type PipelineProgressUpdateWithoutPipelineInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>;
pipelineStage?: InputMaybe<PipelineStageUpdateOneRequiredWithoutPipelineProgressesNestedInput>;
@ -1803,6 +1827,7 @@ export type PipelineProgressUpdateWithoutPipelineInput = {
};
export type PipelineProgressUpdateWithoutPipelineStageInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>;
pipeline?: InputMaybe<PipelineUpdateOneRequiredWithoutPipelineProgressesNestedInput>;
@ -1812,6 +1837,7 @@ export type PipelineProgressUpdateWithoutPipelineStageInput = {
};
export type PipelineProgressUpdateWithoutWorkspaceInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>;
pipeline?: InputMaybe<PipelineUpdateOneRequiredWithoutPipelineProgressesNestedInput>;
@ -1843,6 +1869,8 @@ export type PipelineProgressWhereInput = {
AND?: InputMaybe<Array<PipelineProgressWhereInput>>;
NOT?: InputMaybe<Array<PipelineProgressWhereInput>>;
OR?: InputMaybe<Array<PipelineProgressWhereInput>>;
amount?: InputMaybe<IntNullableFilter>;
closeDate?: InputMaybe<DateTimeNullableFilter>;
createdAt?: InputMaybe<DateTimeFilter>;
id?: InputMaybe<StringFilter>;
pipeline?: InputMaybe<PipelineRelationFilter>;
@ -2328,6 +2356,11 @@ export enum SortOrder {
Desc = 'desc'
}
export type SortOrderInput = {
nulls?: InputMaybe<NullsOrder>;
sort: SortOrder;
};
export type StringFieldUpdateOperationsInput = {
set?: InputMaybe<Scalars['String']>;
};
@ -2443,7 +2476,7 @@ export type UserCreateWithoutWorkspaceMemberInput = {
};
export type UserOrderByWithRelationInput = {
avatarUrl?: InputMaybe<SortOrder>;
avatarUrl?: InputMaybe<SortOrderInput>;
comments?: InputMaybe<CommentOrderByRelationAggregateInput>;
companies?: InputMaybe<CompanyOrderByRelationAggregateInput>;
createdAt?: InputMaybe<SortOrder>;
@ -2453,10 +2486,10 @@ export type UserOrderByWithRelationInput = {
firstName?: InputMaybe<SortOrder>;
id?: InputMaybe<SortOrder>;
lastName?: InputMaybe<SortOrder>;
lastSeen?: InputMaybe<SortOrder>;
lastSeen?: InputMaybe<SortOrderInput>;
locale?: InputMaybe<SortOrder>;
metadata?: InputMaybe<SortOrder>;
phoneNumber?: InputMaybe<SortOrder>;
metadata?: InputMaybe<SortOrderInput>;
phoneNumber?: InputMaybe<SortOrderInput>;
updatedAt?: InputMaybe<SortOrder>;
};
@ -2912,15 +2945,24 @@ 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, pipelineProgresses?: Array<{ __typename?: 'PipelineProgress', id: string, progressableType: PipelineProgressableType, progressableId: 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, pipelineProgresses?: Array<{ __typename?: 'PipelineProgress', id: string, progressableType: PipelineProgressableType, progressableId: string, amount?: number | null, closeDate?: string | null }> | null }> | null }> };
export type UpdateOnePipelineProgressMutationVariables = Exact<{
id?: InputMaybe<Scalars['String']>;
amount?: InputMaybe<Scalars['Int']>;
closeDate?: InputMaybe<Scalars['DateTime']>;
}>;
export type UpdateOnePipelineProgressMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string } | null };
export type UpdateOnePipelineProgressStageMutationVariables = Exact<{
id?: InputMaybe<Scalars['String']>;
pipelineStageId?: InputMaybe<Scalars['String']>;
}>;
export type UpdateOnePipelineProgressMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string } | null };
export type UpdateOnePipelineProgressStageMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string } | null };
export type CreateOnePipelineProgressMutationVariables = Exact<{
uuid: Scalars['String'];
@ -3932,6 +3974,8 @@ export const GetPipelinesDocument = gql`
id
progressableType
progressableId
amount
closeDate
}
}
}
@ -3966,10 +4010,10 @@ export type GetPipelinesQueryHookResult = ReturnType<typeof useGetPipelinesQuery
export type GetPipelinesLazyQueryHookResult = ReturnType<typeof useGetPipelinesLazyQuery>;
export type GetPipelinesQueryResult = Apollo.QueryResult<GetPipelinesQuery, GetPipelinesQueryVariables>;
export const UpdateOnePipelineProgressDocument = gql`
mutation UpdateOnePipelineProgress($id: String, $pipelineStageId: String) {
mutation UpdateOnePipelineProgress($id: String, $amount: Int, $closeDate: DateTime) {
updateOnePipelineProgress(
where: {id: $id}
data: {pipelineStage: {connect: {id: $pipelineStageId}}}
data: {amount: {set: $amount}, closeDate: {set: $closeDate}}
) {
id
}
@ -3991,7 +4035,8 @@ export type UpdateOnePipelineProgressMutationFn = Apollo.MutationFunction<Update
* const [updateOnePipelineProgressMutation, { data, loading, error }] = useUpdateOnePipelineProgressMutation({
* variables: {
* id: // value for 'id'
* pipelineStageId: // value for 'pipelineStageId'
* amount: // value for 'amount'
* closeDate: // value for 'closeDate'
* },
* });
*/
@ -4002,6 +4047,43 @@ export function useUpdateOnePipelineProgressMutation(baseOptions?: Apollo.Mutati
export type UpdateOnePipelineProgressMutationHookResult = ReturnType<typeof useUpdateOnePipelineProgressMutation>;
export type UpdateOnePipelineProgressMutationResult = Apollo.MutationResult<UpdateOnePipelineProgressMutation>;
export type UpdateOnePipelineProgressMutationOptions = Apollo.BaseMutationOptions<UpdateOnePipelineProgressMutation, UpdateOnePipelineProgressMutationVariables>;
export const UpdateOnePipelineProgressStageDocument = gql`
mutation UpdateOnePipelineProgressStage($id: String, $pipelineStageId: String) {
updateOnePipelineProgress(
where: {id: $id}
data: {pipelineStage: {connect: {id: $pipelineStageId}}}
) {
id
}
}
`;
export type UpdateOnePipelineProgressStageMutationFn = Apollo.MutationFunction<UpdateOnePipelineProgressStageMutation, UpdateOnePipelineProgressStageMutationVariables>;
/**
* __useUpdateOnePipelineProgressStageMutation__
*
* To run a mutation, you first call `useUpdateOnePipelineProgressStageMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateOnePipelineProgressStageMutation` 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 [updateOnePipelineProgressStageMutation, { data, loading, error }] = useUpdateOnePipelineProgressStageMutation({
* variables: {
* id: // value for 'id'
* pipelineStageId: // value for 'pipelineStageId'
* },
* });
*/
export function useUpdateOnePipelineProgressStageMutation(baseOptions?: Apollo.MutationHookOptions<UpdateOnePipelineProgressStageMutation, UpdateOnePipelineProgressStageMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UpdateOnePipelineProgressStageMutation, UpdateOnePipelineProgressStageMutationVariables>(UpdateOnePipelineProgressStageDocument, options);
}
export type UpdateOnePipelineProgressStageMutationHookResult = ReturnType<typeof useUpdateOnePipelineProgressStageMutation>;
export type UpdateOnePipelineProgressStageMutationResult = Apollo.MutationResult<UpdateOnePipelineProgressStageMutation>;
export type UpdateOnePipelineProgressStageMutationOptions = Apollo.BaseMutationOptions<UpdateOnePipelineProgressStageMutation, UpdateOnePipelineProgressStageMutationVariables>;
export const CreateOnePipelineProgressDocument = gql`
mutation CreateOnePipelineProgress($uuid: String!, $entityType: PipelineProgressableType!, $entityId: String!, $pipelineId: String!, $pipelineStageId: String!) {
createOnePipelineProgress(

View File

@ -23,17 +23,17 @@ const root = ReactDOM.createRoot(
root.render(
<RecoilRoot>
<ApolloProvider>
<BrowserRouter>
<AppThemeProvider>
<StrictMode>
<UserProvider>
<AppThemeProvider>
<StrictMode>
<UserProvider>
<BrowserRouter>
<ClientConfigProvider>
<App />
</ClientConfigProvider>
</UserProvider>
</StrictMode>
</AppThemeProvider>
</BrowserRouter>
</BrowserRouter>
</UserProvider>
</StrictMode>
</AppThemeProvider>
</ApolloProvider>
</RecoilRoot>,
);

View File

@ -10,7 +10,7 @@ import {
import { useRecoilState } from 'recoil';
import { BoardColumn } from '@/ui/components/board/BoardColumn';
import { Company } from '~/generated/graphql';
import { Company, PipelineProgress } from '~/generated/graphql';
import {
Column,
@ -24,10 +24,11 @@ import { selectedBoardItemsState } from '../states/selectedBoardItemsState';
import { CompanyBoardCard } from './CompanyBoardCard';
import { NewButton } from './NewButton';
export type CompanyProgress = Pick<
Company,
'id' | 'name' | 'domainName' | 'createdAt'
>;
export type CompanyProgress = {
company: Pick<Company, 'id' | 'name' | 'domainName'>;
pipelineProgress: Pick<PipelineProgress, 'id' | 'amount' | 'closeDate'>;
};
export type CompanyProgressDict = {
[key: string]: CompanyProgress;
};
@ -37,7 +38,10 @@ type BoardProps = {
columns: Omit<Column, 'itemKeys'>[];
initialBoard: Column[];
initialItems: CompanyProgressDict;
onUpdate?: (itemKey: string, columnId: Column['id']) => Promise<void>;
onCardMove?: (itemKey: string, columnId: Column['id']) => Promise<void>;
onCardUpdate: (
pipelineProgress: Pick<PipelineProgress, 'id' | 'amount' | 'closeDate'>,
) => Promise<void>;
};
const StyledPlaceholder = styled.div`
@ -66,7 +70,8 @@ export function Board({
columns,
initialBoard,
initialItems,
onUpdate,
onCardMove,
onCardUpdate,
pipelineId,
}: BoardProps) {
const [board, setBoard] = useRecoilState(boardColumnsState);
@ -79,6 +84,7 @@ export function Board({
useEffect(() => {
if (isInitialBoardLoaded) return;
setBoard(initialBoard);
if (Object.keys(initialItems).length === 0) return;
setBoardItems(initialItems);
setIsInitialBoardLoaded(true);
}, [
@ -100,13 +106,13 @@ export function Board({
const destinationColumnId = result.destination?.droppableId;
draggedEntityId &&
destinationColumnId &&
onUpdate &&
(await onUpdate(draggedEntityId, destinationColumnId));
onCardMove &&
(await onCardMove(draggedEntityId, destinationColumnId));
} catch (e) {
console.error(e);
}
},
[board, onUpdate, setBoard],
[board, onCardMove, setBoard],
);
function handleSelect(itemKey: string) {
@ -144,8 +150,12 @@ export function Board({
{...draggableProvided?.draggableProps}
>
<CompanyBoardCard
company={boardItems[itemKey]}
company={boardItems[itemKey].company}
pipelineProgress={
boardItems[itemKey].pipelineProgress
}
selected={selectedBoardItems.includes(itemKey)}
onCardUpdate={onCardUpdate}
onSelect={() => handleSelect(itemKey)}
/>
</div>

View File

@ -1,11 +1,17 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconCurrencyDollar } from '@tabler/icons-react';
import { Company } from '../../../generated/graphql';
import { PersonChip } from '../../people/components/PersonChip';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
import { CellContext } from '@/ui/tables/states/CellContext';
import { RowContext } from '@/ui/tables/states/RowContext';
import { Company, PipelineProgress } from '../../../generated/graphql';
import { Checkbox } from '../../ui/components/form/Checkbox';
import { IconCalendarEvent, IconUser, IconUsers } from '../../ui/icons';
import { getLogoUrlFromDomainName, humanReadableDate } from '../../utils/utils';
import { IconCalendarEvent } from '../../ui/icons';
import { getLogoUrlFromDomainName } from '../../utils/utils';
const StyledBoardCard = styled.div<{ selected: boolean }>`
background-color: ${({ theme, selected }) =>
@ -44,7 +50,6 @@ const StyledBoardCardHeader = styled.div`
const StyledBoardCardBody = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2)};
span {
align-items: center;
@ -59,17 +64,37 @@ const StyledBoardCardBody = styled.div`
type CompanyProp = Pick<
Company,
'id' | 'name' | 'domainName' | 'employees' | 'createdAt' | 'accountOwner'
'id' | 'name' | 'domainName' | 'employees' | 'accountOwner'
>;
type PipelineProgressProp = Pick<
PipelineProgress,
'id' | 'amount' | 'closeDate'
>;
// TODO: Remove when refactoring EditableCell into EditableField
function HackScope({ children }: { children: React.ReactNode }) {
return (
<RecoilScope>
<RecoilScope SpecificContext={RowContext}>
<RecoilScope SpecificContext={CellContext}>{children}</RecoilScope>
</RecoilScope>
</RecoilScope>
);
}
export function CompanyBoardCard({
company,
pipelineProgress,
selected,
onSelect,
onCardUpdate,
}: {
company: CompanyProp;
pipelineProgress: PipelineProgressProp;
selected: boolean;
onSelect: (company: CompanyProp) => void;
onCardUpdate: (pipelineProgress: PipelineProgressProp) => Promise<void>;
}) {
const theme = useTheme();
return (
@ -86,15 +111,33 @@ export function CompanyBoardCard({
</StyledBoardCardHeader>
<StyledBoardCardBody>
<span>
<IconUser size={theme.icon.size.md} />
<PersonChip name={company.accountOwner?.displayName || ''} />
</span>
<span>
<IconUsers size={theme.icon.size.md} /> {company.employees}
<IconCurrencyDollar size={theme.icon.size.md} />
<HackScope>
<EditableText
content={pipelineProgress.amount?.toString() || ''}
placeholder="Opportunity amount"
changeHandler={(value) =>
onCardUpdate({
...pipelineProgress,
amount: parseInt(value),
})
}
/>
</HackScope>
</span>
<span>
<IconCalendarEvent size={theme.icon.size.md} />
{humanReadableDate(new Date(company.createdAt as string))}
<HackScope>
<EditableDate
value={new Date(pipelineProgress.closeDate || Date.now())}
changeHandler={(value) => {
onCardUpdate({
...pipelineProgress,
closeDate: value.toISOString(),
});
}}
/>
</HackScope>
</span>
</StyledBoardCardBody>
</StyledBoardCard>

View File

@ -39,10 +39,11 @@ export function NewButton({ pipelineId, columnId }: OwnProps) {
setBoardItems({
...boardItems,
[newUuid]: {
id: company.id,
name: company.name,
domainName: company.domainName,
createdAt: new Date().toISOString(),
company,
pipelineProgress: {
id: newUuid,
amount: 0,
},
},
});
setBoard(newBoard);

View File

@ -21,6 +21,7 @@ export const OneColumnBoard: Story = {
columns={initialBoard}
initialBoard={initialBoard}
initialItems={items}
onCardUpdate={async (_) => {}} // eslint-disable-line @typescript-eslint/no-empty-function
/>,
),
};

View File

@ -1,8 +1,11 @@
import { StrictMode, useState } from 'react';
import { useState } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { Company } from '../../../../generated/graphql';
import { mockedCompaniesData } from '../../../../testing/mock-data/companies';
import { mockedPipelineProgressData } from '../../../../testing/mock-data/pipeline-progress';
import { CompanyBoardCard } from '../CompanyBoardCard';
const meta: Meta<typeof CompanyBoardCard> = {
@ -19,16 +22,14 @@ const FakeSelectableCompanyBoardCard = () => {
return (
<CompanyBoardCard
company={mockedCompaniesData[0] as Company}
pipelineProgress={mockedPipelineProgressData[0]}
selected={selected}
onSelect={() => setSelected(!selected)}
onCardUpdate={async (_) => {}} // eslint-disable-line @typescript-eslint/no-empty-function
/>
);
};
export const CompanyCompanyBoardCard: Story = {
render: () => (
<StrictMode>
<FakeSelectableCompanyBoardCard />
</StrictMode>
),
render: getRenderWrapperForComponent(<FakeSelectableCompanyBoardCard />),
};

View File

@ -4,20 +4,24 @@ import { mockedCompaniesData } from '~/testing/mock-data/companies';
import { CompanyProgressDict } from '../Board';
export const items: CompanyProgressDict = {
'item-1': mockedCompaniesData[0],
'item-2': mockedCompaniesData[1],
'item-3': mockedCompaniesData[2],
'item-4': mockedCompaniesData[3],
'item-1': {
company: mockedCompaniesData[0],
pipelineProgress: { id: '0', amount: 1 },
},
'item-2': {
company: mockedCompaniesData[1],
pipelineProgress: { id: '1', amount: 1 },
},
'item-3': {
company: mockedCompaniesData[2],
pipelineProgress: { id: '2', amount: 1 },
},
'item-4': {
company: mockedCompaniesData[3],
pipelineProgress: { id: '3', amount: 1 },
},
};
for (let i = 7; i <= 20; i++) {
const key = `item-${i}`;
items[key] = {
...mockedCompaniesData[i % mockedCompaniesData.length],
id: key,
};
}
export const initialBoard = [
{
id: 'column-1',

View File

@ -1,11 +1,21 @@
import {
Company,
PipelineProgress,
useGetCompaniesQuery,
useGetPipelinesQuery,
} from '../../../generated/graphql';
import { Column } from '../../ui/components/board/Board';
type Item = Pick<Company, 'id' | 'name' | 'createdAt' | 'domainName'>;
type ItemCompany = Pick<Company, 'id' | 'name' | 'domainName'>;
type ItemPipelineProgress = Pick<
PipelineProgress,
'id' | 'amount' | 'progressableId'
>;
type Item = {
company: ItemCompany;
pipelineProgress: ItemPipelineProgress;
};
type Items = { [key: string]: Item };
export function useBoard(pipelineId: string) {
@ -27,12 +37,9 @@ export function useBoard(pipelineId: string) {
const pipelineProgresses = pipelineStages?.reduce(
(acc, pipelineStage) => [
...acc,
...(pipelineStage.pipelineProgresses?.map((item) => ({
progressableId: item?.progressableId,
pipelineProgressId: item?.id,
})) || []),
...(pipelineStage.pipelineProgresses || []),
],
[] as { progressableId: string; pipelineProgressId: string }[],
[] as ItemPipelineProgress[],
);
const entitiesQueryResult = useGetCompaniesQuery({
@ -43,20 +50,25 @@ export function useBoard(pipelineId: string) {
},
});
const indexByIdReducer = (acc: Items, entity: Item) => ({
const indexCompanyByIdReducer = (
acc: { [key: string]: ItemCompany },
entity: ItemCompany,
) => ({
...acc,
[entity.id]: entity,
});
const companiesDict = entitiesQueryResult.data?.companies.reduce(
indexByIdReducer,
{} as Items,
indexCompanyByIdReducer,
{} as { [key: string]: ItemCompany },
);
const items = pipelineProgresses?.reduce((acc, pipelineProgress) => {
if (companiesDict?.[pipelineProgress.progressableId]) {
acc[pipelineProgress.pipelineProgressId] =
companiesDict[pipelineProgress.progressableId];
acc[pipelineProgress.id] = {
pipelineProgress,
company: companiesDict[pipelineProgress.progressableId],
};
}
return acc;
}, {} as Items);

View File

@ -14,14 +14,36 @@ export const GET_PIPELINES = gql`
id
progressableType
progressableId
amount
closeDate
}
}
}
}
`;
export const UPDATE_PIPELINE_STAGE = gql`
mutation UpdateOnePipelineProgress($id: String, $pipelineStageId: String) {
export const UPDATE_PIPELINE_PROGRESS = gql`
mutation UpdateOnePipelineProgress(
$id: String
$amount: Int
$closeDate: DateTime
) {
updateOnePipelineProgress(
where: { id: $id }
data: { amount: { set: $amount }, closeDate: { set: $closeDate } }
) {
id
amount
closeDate
}
}
`;
export const UPDATE_PIPELINE_PROGRESS_STAGE = gql`
mutation UpdateOnePipelineProgressStage(
$id: String
$pipelineStageId: String
) {
updateOnePipelineProgress(
where: { id: $id }
data: { pipelineStage: { connect: { id: $pipelineStageId } } }

View File

@ -11,6 +11,7 @@ import {
PipelineStage,
useGetPipelinesQuery,
useUpdateOnePipelineProgressMutation,
useUpdateOnePipelineProgressStageMutation,
} from '../../generated/graphql';
import { Board } from '../../modules/pipeline-progress/components/Board';
import { useBoard } from '../../modules/pipeline-progress/hooks/useBoard';
@ -32,17 +33,37 @@ export function Opportunities() {
[initialBoard],
);
const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation();
const [updatePipelineProgressStage] =
useUpdateOnePipelineProgressStageMutation();
const onUpdate = useCallback(
const handleCardUpdate = useCallback(
async (
pipelineProgress: Pick<PipelineProgress, 'id' | 'amount' | 'closeDate'>,
) => {
updatePipelineProgress({
variables: {
id: pipelineProgress.id,
amount: pipelineProgress.amount,
closeDate: pipelineProgress.closeDate || null,
},
});
},
[updatePipelineProgress],
);
const handleCardMove = useCallback(
async (
pipelineProgressId: NonNullable<PipelineProgress['id']>,
pipelineStageId: NonNullable<PipelineStage['id']>,
) => {
updatePipelineProgress({
variables: { id: pipelineProgressId, pipelineStageId },
updatePipelineProgressStage({
variables: {
id: pipelineProgressId,
pipelineStageId,
},
});
},
[updatePipelineProgress],
[updatePipelineProgressStage],
);
return (
@ -57,7 +78,8 @@ export function Opportunities() {
columns={columns || []}
initialBoard={initialBoard}
initialItems={items}
onUpdate={onUpdate}
onCardMove={handleCardMove}
onCardUpdate={handleCardUpdate}
/>
<EntityBoardActionBar>
<BoardActionBarButtonDeletePipelineProgress />

View File

@ -1,11 +1,13 @@
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { currentUserState } from '@/auth/states/currentUserState';
import { useGetCurrentUserQuery } from '~/generated/graphql';
export function UserProvider({ children }: React.PropsWithChildren) {
const [, setCurrentUser] = useRecoilState(currentUserState);
const [currentUser, setCurrentUser] = useRecoilState(currentUserState);
const isLoggedIn = useIsLogged();
const { data, loading } = useGetCurrentUserQuery();
useEffect(() => {
@ -14,5 +16,5 @@ export function UserProvider({ children }: React.PropsWithChildren) {
}
}, [setCurrentUser, data]);
return loading ? <></> : <>{children}</>;
return loading || (isLoggedIn && !currentUser) ? <></> : <>{children}</>;
}

View File

@ -0,0 +1,26 @@
import { PipelineProgress, User } from '../../generated/graphql';
type MockedPipelineProgress = Pick<
PipelineProgress,
'id' | 'amount' | 'closeDate'
> & {
accountOwner: Pick<
User,
'id' | 'email' | 'displayName' | '__typename' | 'firstName' | 'lastName'
> | null;
};
export const mockedPipelineProgressData: Array<MockedPipelineProgress> = [
{
id: '0ac8761c-1ad6-11ee-be56-0242ac120002',
amount: 78,
accountOwner: {
email: 'charles@test.com',
displayName: 'Charles Test',
firstName: 'Charles',
lastName: 'Test',
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
__typename: 'User',
},
},
];