V2 opportunities (#2565)

* changed isSystem to false

* wip

* wip

* wip

* add amount viewfield seed

* seed other viewFields

* upate tenant seeds

* Remove calls to old pipelines

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
bosiraphael
2023-11-17 19:12:22 +01:00
committed by GitHub
parent f62108d539
commit d481da183f
43 changed files with 454 additions and 864 deletions

View File

@ -2,6 +2,7 @@ import { ReactNode, useContext } from 'react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
import { EntityChipVariant } from '@/ui/display/chip/components/EntityChip';
import { IconEye } from '@/ui/display/icon/index';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
@ -17,7 +18,6 @@ import { RecordInlineCell } from '@/ui/object/record-inline-cell/components/Reco
import { InlineCellHotkeyScope } from '@/ui/object/record-inline-cell/types/InlineCellHotkeyScope';
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils';
import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState';
@ -150,6 +150,30 @@ export const CompanyBoardCard = () => {
BoardRecoilScopeContext,
);
const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => {
const { updateOneObject } = useUpdateOneObjectRecord({
objectNameSingular: 'opportunityV2',
});
const updateEntity = ({
variables,
}: {
variables: {
where: { id: string };
data: {
[fieldName: string]: any;
};
};
}) => {
updateOneObject?.({
idToUpdate: variables.where.id,
input: variables.data,
});
};
return [updateEntity, { loading: false }];
};
// boardCardId check can be moved to a wrapper to avoid unnecessary logic above
if (!company || !pipelineProgress || !boardCardId) {
return null;
@ -224,8 +248,7 @@ export const CompanyBoardCard = () => {
entityChipDisplayMapper:
viewField.entityChipDisplayMapper,
},
useUpdateEntityMutation:
useUpdateOnePipelineProgressMutation,
useUpdateEntityMutation: useUpdateOneObjectMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>

View File

@ -1,8 +1,12 @@
import { useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
import { pipelineAvailableFieldDefinitions } from '@/pipeline/constants/pipelineAvailableFieldDefinitions';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { PipelineStep } from '@/pipeline/types/PipelineStep';
import { useBoardActionBarEntries } from '@/ui/layout/board/hooks/useBoardActionBarEntries';
import { useBoardContext } from '@/ui/layout/board/hooks/useBoardContext';
import { useBoardContextMenuEntries } from '@/ui/layout/board/hooks/useBoardContextMenuEntries';
@ -15,13 +19,7 @@ import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates'
import { useView } from '@/views/hooks/useView';
import { ViewType } from '@/views/types/ViewType';
import { mapViewFieldsToBoardFieldDefinitions } from '@/views/utils/mapViewFieldsToBoardFieldDefinitions';
import {
Pipeline,
PipelineProgressableType,
useGetCompaniesQuery,
useGetPipelineProgressQuery,
useGetPipelinesQuery,
} from '~/generated/graphql';
import { Company } from '~/generated-metadata/graphql';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
@ -40,6 +38,10 @@ export const HooksCompanyBoardEffect = () => {
const { currentViewFiltersState, currentViewFieldsState } =
useViewScopedStates();
const [pipelineSteps, setPipelineSteps] = useState<PipelineStep[]>([]);
const [opportunities, setOpportunities] = useState<Opportunity[]>([]);
const [companies, setCompanies] = useState<Company[]>([]);
const currentViewFields = useRecoilValue(currentViewFieldsState);
const currentViewFilters = useRecoilValue(currentViewFiltersState);
@ -56,21 +58,62 @@ export const HooksCompanyBoardEffect = () => {
availableBoardCardFieldsScopedState,
BoardRecoilScopeContext,
);
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds();
const updateCompanyBoard = useUpdateCompanyBoard();
const { data: pipelineData, loading: loadingGetPipelines } =
useGetPipelinesQuery({
variables: {
where: {
pipelineProgressableType: {
equals: PipelineProgressableType.Company,
useFindManyObjectRecords({
objectNamePlural: 'pipelineStepsV2',
filter: {},
onCompleted: useCallback(
(data: PaginatedObjectTypeResults<PipelineStep>) => {
setPipelineSteps(data.edges.map((edge) => edge.node));
},
[],
),
});
const whereFilters = useMemo(() => {
return {
AND: [
{
pipelineStageId: {
in: pipelineSteps.map((pipelineStep) => pipelineStep.id),
},
},
},
});
...(currentViewFilters?.map(turnFilterIntoWhereClause) || []),
],
};
}, [currentViewFilters, pipelineSteps]) as any;
const pipeline = pipelineData?.findManyPipeline[0] as Pipeline | undefined;
useFindManyObjectRecords({
skip: !pipelineSteps.length,
objectNamePlural: 'opportunitiesV2',
filter: whereFilters,
onCompleted: useCallback(
(_data: PaginatedObjectTypeResults<Opportunity>) => {
const pipelineProgresses: Array<Opportunity> = [];
updateCompanyBoardCardIds(pipelineProgresses);
setOpportunities(pipelineProgresses);
setIsBoardLoaded(true);
},
[setIsBoardLoaded, updateCompanyBoardCardIds],
),
});
useFindManyObjectRecords({
skip: !opportunities.length,
objectNamePlural: 'companiesV2',
filter: {
id: {
in: opportunities.map((opportuntiy) => opportuntiy.companyId || ''),
},
},
onCompleted: useCallback((data: PaginatedObjectTypeResults<Company>) => {
setCompanies(data.edges.map((edge) => edge.node));
}, []),
});
useEffect(() => {
setAvailableFilterDefinitions(opportunitiesBoardOptions.filterDefinitions);
@ -87,77 +130,32 @@ export const HooksCompanyBoardEffect = () => {
setViewType?.(ViewType.Kanban);
}, [setViewObjectMetadataId, setViewType]);
const pipelineStageIds = pipeline?.pipelineStages
?.map((pipelineStage) => pipelineStage.id)
.flat();
const whereFilters = useMemo(() => {
return {
AND: [
{ pipelineStageId: { in: pipelineStageIds } },
...(currentViewFilters?.map(turnFilterIntoWhereClause) || []),
],
};
}, [currentViewFilters, pipelineStageIds]) as any;
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds();
const { data: pipelineProgressData, loading: loadingGetPipelineProgress } =
useGetPipelineProgressQuery({
variables: {
where: whereFilters,
},
onCompleted: (data) => {
const pipelineProgresses = data?.findManyPipelineProgress || [];
updateCompanyBoardCardIds(pipelineProgresses);
setIsBoardLoaded(true);
},
});
const pipelineProgresses = useMemo(() => {
return pipelineProgressData?.findManyPipelineProgress || [];
}, [pipelineProgressData]);
const { data: companiesData, loading: loadingGetCompanies } =
useGetCompaniesQuery({
variables: {
where: {
id: {
in: pipelineProgresses.map((item) => item.companyId || ''),
},
},
},
});
const [searchParams] = useSearchParams();
const loading =
loadingGetPipelines || loadingGetPipelineProgress || loadingGetCompanies;
const loading = !companies;
const { setActionBarEntries } = useBoardActionBarEntries();
const { setContextMenuEntries } = useBoardContextMenuEntries();
useEffect(() => {
if (!loading && pipeline && pipelineProgresses && companiesData) {
if (!loading && opportunities && companies) {
setActionBarEntries();
setContextMenuEntries();
setAvailableBoardCardFields(pipelineAvailableFieldDefinitions);
updateCompanyBoard(pipeline, pipelineProgresses, companiesData.companies);
setEntityCountInCurrentView(companiesData.companies.length);
updateCompanyBoard(pipelineSteps, opportunities, companies);
setEntityCountInCurrentView(companies.length);
}
}, [
loading,
pipeline,
pipelineProgresses,
companiesData,
updateCompanyBoard,
setActionBarEntries,
setContextMenuEntries,
searchParams,
setEntityCountInCurrentView,
setAvailableBoardCardFields,
opportunities,
companies,
pipelineSteps,
]);
useEffect(() => {

View File

@ -1,20 +1,15 @@
import { getOperationName } from '@apollo/client/utilities';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { v4 } from 'uuid';
import { GET_PIPELINE_PROGRESS } from '@/pipeline/graphql/queries/getPipelineProgress';
import { GET_PIPELINES } from '@/pipeline/graphql/queries/getPipelines';
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/layout/board/states/boardCardIdsByColumnIdFamilyState';
import { useCreateOneCompanyPipelineProgressMutation } from '~/generated/graphql';
export const useCreateCompanyProgress = () => {
const [createOneCompanyPipelineProgress] =
useCreateOneCompanyPipelineProgressMutation({
refetchQueries: [
getOperationName(GET_PIPELINE_PROGRESS) ?? '',
getOperationName(GET_PIPELINES) ?? '',
],
const { createOneObject: createOneOpportunity } =
useCreateOneObjectRecord<Opportunity>({
objectNameSingular: 'opportunityV2',
});
const [currentPipeline] = useRecoilState(currentPipelineState);
@ -33,15 +28,11 @@ export const useCreateCompanyProgress = () => {
newUuid,
]);
await createOneCompanyPipelineProgress({
variables: {
uuid: newUuid,
pipelineStageId: pipelineStageId,
pipelineId: currentPipeline?.id ?? '',
companyId: companyId,
},
await createOneOpportunity?.({
pipelineStepId: pipelineStageId,
companyId: companyId,
});
},
[createOneCompanyPipelineProgress, currentPipeline],
[createOneOpportunity, currentPipeline?.id],
);
};

View File

@ -1,15 +1,13 @@
import { useRecoilCallback } from 'recoil';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/layout/board/states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '@/ui/layout/board/states/boardColumnsState';
import { GetPipelineProgressQuery } from '~/generated/graphql';
export const useUpdateCompanyBoardCardIds = () =>
useRecoilCallback(
({ snapshot, set }) =>
(
pipelineProgresses: GetPipelineProgressQuery['findManyPipelineProgress'],
) => {
(pipelineProgresses: Opportunity[]) => {
const boardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();

View File

@ -1,31 +1,26 @@
import { useRecoilCallback } from 'recoil';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { currentPipelineStepsState } from '@/pipeline/states/currentPipelineStepsState';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { PipelineStep } from '@/pipeline/types/PipelineStep';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/layout/board/states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '@/ui/layout/board/states/boardColumnsState';
import { savedBoardColumnsState } from '@/ui/layout/board/states/savedBoardColumnsState';
import { BoardColumnDefinition } from '@/ui/layout/board/types/BoardColumnDefinition';
import { entityFieldsFamilyState } from '@/ui/object/field/states/entityFieldsFamilyState';
import { isThemeColor } from '@/ui/theme/utils/castStringAsThemeColor';
import { Pipeline } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { logError } from '~/utils/logError';
import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState';
import {
CompanyForBoard,
CompanyProgressDict,
PipelineProgressForBoard,
} from '../types/CompanyProgress';
import { CompanyForBoard, CompanyProgressDict } from '../types/CompanyProgress';
export const useUpdateCompanyBoard = () =>
useRecoilCallback(
({ set, snapshot }) =>
(
pipeline: Pipeline,
pipelineProgresses: (PipelineProgressForBoard & {
pipelineStageId: string;
})[],
pipelineSteps: PipelineStep[],
pipelineProgresses: Opportunity[],
companies: CompanyForBoard[],
) => {
const indexCompanyByIdReducer = (
@ -44,7 +39,7 @@ export const useUpdateCompanyBoard = () =>
const indexPipelineProgressByIdReducer = (
acc: CompanyProgressDict,
pipelineProgress: PipelineProgressForBoard,
pipelineProgress: Opportunity,
) => {
const company =
pipelineProgress.companyId &&
@ -77,21 +72,19 @@ export const useUpdateCompanyBoard = () =>
}
}
const currentPipeline = snapshot
.getLoadable(currentPipelineState)
const currentPipelineSteps = snapshot
.getLoadable(currentPipelineStepsState)
.valueOrThrow();
const currentBoardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();
if (!isDeeplyEqual(pipeline, currentPipeline)) {
set(currentPipelineState, pipeline);
if (!isDeeplyEqual(pipelineSteps, currentPipelineSteps)) {
set(currentPipelineStepsState, currentPipelineSteps);
}
const pipelineStages = pipeline?.pipelineStages ?? [];
const orderedPipelineStages = [...pipelineStages].sort((a, b) => {
const orderedPipelineStages = [...pipelineSteps].sort((a, b) => {
if (!a.position || !b.position) return 0;
return a.position - b.position;
});

View File

@ -1,17 +1,8 @@
import { Company, Person, PipelineProgress } from '~/generated/graphql';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { Company } from '~/generated/graphql';
export type CompanyForBoard = Pick<Company, 'id' | 'name' | 'domainName'>;
export type PipelineProgressForBoard = Pick<
PipelineProgress,
| 'id'
| 'amount'
| 'closeDate'
| 'companyId'
| 'probability'
| 'pointOfContactId'
> & {
pointOfContact?: Pick<Person, 'id' | 'displayName'> | null;
};
export type PipelineProgressForBoard = Opportunity;
export type CompanyProgress = {
company: CompanyForBoard;