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:
@ -20,7 +20,7 @@ export const useHandleCheckableActivityTargetChange = ({
|
||||
objectNameSingular: 'activityTargetV2',
|
||||
});
|
||||
const { deleteOneObject } = useDeleteOneObjectRecord({
|
||||
objectNamePlural: 'activityTargetV2',
|
||||
objectNameSingular: 'activityTargetV2',
|
||||
});
|
||||
|
||||
return async (
|
||||
|
||||
@ -12,7 +12,7 @@ type ActivityActionBarProps = {
|
||||
export const ActivityActionBar = ({ activityId }: ActivityActionBarProps) => {
|
||||
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||
const { deleteOneObject } = useDeleteOneObjectRecord({
|
||||
objectNamePlural: 'activitiesV2',
|
||||
objectNameSingular: 'activityV2',
|
||||
});
|
||||
|
||||
const deleteActivity = () => {
|
||||
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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],
|
||||
);
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -22,6 +22,7 @@ export const ObjectMetadataNavItems = () => {
|
||||
return (
|
||||
<>
|
||||
{objectMetadataItems.map((objectMetadataItem) => {
|
||||
if (objectMetadataItem.nameSingular === 'opportunityV2') return null;
|
||||
return (
|
||||
<NavItem
|
||||
key={objectMetadataItem.id}
|
||||
|
||||
@ -3,32 +3,37 @@ import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useDeleteOneObjectRecord = ({
|
||||
objectNamePlural,
|
||||
}: Pick<ObjectMetadataItemIdentifier, 'objectNamePlural'>) => {
|
||||
export const useDeleteOneObjectRecord = <T>({
|
||||
objectNameSingular,
|
||||
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => {
|
||||
const {
|
||||
foundObjectMetadataItem,
|
||||
objectNotFoundInMetadata,
|
||||
findManyQuery,
|
||||
deleteOneMutation,
|
||||
} = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
// TODO: type this with a minimal type at least with Record<string, any>
|
||||
const [mutate] = useMutation(deleteOneMutation);
|
||||
|
||||
const deleteOneObject = foundObjectMetadataItem
|
||||
? (idToDelete: string) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
refetchQueries: [getOperationName(findManyQuery) ?? ''],
|
||||
});
|
||||
}
|
||||
: undefined;
|
||||
const deleteOneObject =
|
||||
objectNameSingular && foundObjectMetadataItem
|
||||
? async (idToDelete: string) => {
|
||||
const deletedObject = await mutate({
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
refetchQueries: [getOperationName(findManyQuery) ?? ''],
|
||||
});
|
||||
return deletedObject.data[
|
||||
`create${capitalize(objectNameSingular)}`
|
||||
] as T;
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
deleteOneObject,
|
||||
|
||||
@ -3,6 +3,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord';
|
||||
import {
|
||||
IconCheckbox,
|
||||
@ -28,6 +29,11 @@ export const useRecordTableContextMenuEntries = () => {
|
||||
const { scopeId: objectNamePlural, resetTableRowSelection } =
|
||||
useRecordTable();
|
||||
|
||||
const { data } = useGetFavoritesQuery();
|
||||
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { createFavorite, deleteFavorite, favorites } = useFavorites({
|
||||
objectNamePlural,
|
||||
});
|
||||
@ -53,7 +59,7 @@ export const useRecordTableContextMenuEntries = () => {
|
||||
});
|
||||
|
||||
const { deleteOneObject } = useDeleteOneObjectRecord({
|
||||
objectNamePlural,
|
||||
objectNameSingular: foundObjectMetadataItem?.nameSingular,
|
||||
});
|
||||
|
||||
const handleDeleteClick = useRecoilCallback(({ snapshot }) => async () => {
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_COMPANY_PIPELINE_PROGRESS = gql`
|
||||
mutation CreateOneCompanyPipelineProgress(
|
||||
$uuid: String!
|
||||
$companyId: String!
|
||||
$pipelineId: String!
|
||||
$pipelineStageId: String!
|
||||
) {
|
||||
createOnePipelineProgress(
|
||||
data: {
|
||||
id: $uuid
|
||||
company: { connect: { id: $companyId } }
|
||||
pipeline: { connect: { id: $pipelineId } }
|
||||
pipelineStage: { connect: { id: $pipelineStageId } }
|
||||
}
|
||||
) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,11 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_PIPELINE_STAGE = gql`
|
||||
mutation CreatePipelineStage($data: PipelineStageCreateInput!) {
|
||||
pipelineStage: createOnePipelineStage(data: $data) {
|
||||
id
|
||||
name
|
||||
color
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,9 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const DELETE_PIPELINE_PROGRESS = gql`
|
||||
mutation DeleteManyPipelineProgress($ids: [String!]) {
|
||||
deleteManyPipelineProgress(where: { id: { in: $ids } }) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,11 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const DELETE_PIPELINE_STAGE = gql`
|
||||
mutation DeletePipelineStage($where: PipelineStageWhereUniqueInput!) {
|
||||
pipelineStage: deleteOnePipelineStage(where: $where) {
|
||||
id
|
||||
name
|
||||
color
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,18 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_PIPELINE_PROGRESS = gql`
|
||||
mutation UpdateOnePipelineProgress(
|
||||
$data: PipelineProgressUpdateInput!
|
||||
$where: PipelineProgressWhereUniqueInput!
|
||||
) {
|
||||
updateOnePipelineProgress(where: $where, data: $data) {
|
||||
id
|
||||
amount
|
||||
closeDate
|
||||
probability
|
||||
pointOfContact {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,15 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_PIPELINE_PROGRESS_STAGE = gql`
|
||||
mutation UpdateOnePipelineProgressStage(
|
||||
$id: String
|
||||
$pipelineStageId: String
|
||||
) {
|
||||
updateOnePipelineProgress(
|
||||
where: { id: $id }
|
||||
data: { pipelineStage: { connect: { id: $pipelineStageId } } }
|
||||
) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,11 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_PIPELINE_STAGE = gql`
|
||||
mutation UpdatePipelineStage($id: String, $data: PipelineStageUpdateInput!) {
|
||||
updateOnePipelineStage(where: { id: $id }, data: $data) {
|
||||
id
|
||||
name
|
||||
color
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,26 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_PIPELINE_PROGRESS = gql`
|
||||
query GetPipelineProgress(
|
||||
$where: PipelineProgressWhereInput
|
||||
$orderBy: [PipelineProgressOrderByWithRelationInput!]
|
||||
) {
|
||||
findManyPipelineProgress(where: $where, orderBy: $orderBy) {
|
||||
id
|
||||
pipelineStageId
|
||||
companyId
|
||||
personId
|
||||
amount
|
||||
closeDate
|
||||
pointOfContactId
|
||||
pointOfContact {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
avatarUrl
|
||||
}
|
||||
probability
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,17 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_PIPELINES = gql`
|
||||
query GetPipelines($where: PipelineWhereInput) {
|
||||
findManyPipeline(where: $where) {
|
||||
id
|
||||
name
|
||||
pipelineProgressableType
|
||||
pipelineStages {
|
||||
id
|
||||
name
|
||||
color
|
||||
position
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,25 +1,29 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
|
||||
import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
import { BoardColumnDefinition } from '@/ui/layout/board/types/BoardColumnDefinition';
|
||||
import {
|
||||
useCreatePipelineStageMutation,
|
||||
useDeletePipelineStageMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_PIPELINES } from '../graphql/queries/getPipelines';
|
||||
import { currentPipelineState } from '../states/currentPipelineState';
|
||||
|
||||
export const usePipelineStages = () => {
|
||||
const currentPipeline = useRecoilValue(currentPipelineState);
|
||||
|
||||
const [createPipelineStageMutation] = useCreatePipelineStageMutation();
|
||||
const [deletePipelineStageMutation] = useDeletePipelineStageMutation();
|
||||
const { createOneObject: createOnePipelineStep } =
|
||||
useCreateOneObjectRecord<PipelineStep>({
|
||||
objectNameSingular: 'pipelineStepV2',
|
||||
});
|
||||
|
||||
const { deleteOneObject: deleteOnePipelineStep } =
|
||||
useDeleteOneObjectRecord<PipelineStep>({
|
||||
objectNameSingular: 'pipelineStepV2',
|
||||
});
|
||||
|
||||
const handlePipelineStageAdd = async (boardColumn: BoardColumnDefinition) => {
|
||||
if (!currentPipeline?.id) return;
|
||||
|
||||
return createPipelineStageMutation({
|
||||
return createOnePipelineStep?.({
|
||||
variables: {
|
||||
data: {
|
||||
color: boardColumn.colorCode ?? 'gray',
|
||||
@ -30,17 +34,13 @@ export const usePipelineStages = () => {
|
||||
type: 'ongoing',
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
const handlePipelineStageDelete = async (boardColumnId: string) => {
|
||||
if (!currentPipeline?.id) return;
|
||||
|
||||
return deletePipelineStageMutation({
|
||||
variables: { where: { id: boardColumnId } },
|
||||
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
|
||||
});
|
||||
return deleteOnePipelineStep?.(boardColumnId);
|
||||
};
|
||||
|
||||
return { handlePipelineStageAdd, handlePipelineStageDelete };
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
export const currentPipelineStepsState = atom<PipelineStep[]>({
|
||||
key: 'currentPipelineStepsState',
|
||||
default: [],
|
||||
});
|
||||
17
front/src/modules/pipeline/types/Opportunity.ts
Normal file
17
front/src/modules/pipeline/types/Opportunity.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
import { Person } from '~/generated-metadata/graphql';
|
||||
|
||||
export type Opportunity = {
|
||||
id: string;
|
||||
amount: {
|
||||
amountMicros: number;
|
||||
currencyCode: string;
|
||||
};
|
||||
closeDate: Date;
|
||||
probability: number;
|
||||
pipelineStepId: string;
|
||||
pipelineStep: PipelineStep;
|
||||
pointOfContactId: string;
|
||||
pointOfContact: Pick<Person, 'id' | 'firstName' | 'lastName' | 'avatarUrl'>;
|
||||
[key: string]: any;
|
||||
};
|
||||
6
front/src/modules/pipeline/types/PipelineStep.ts
Normal file
6
front/src/modules/pipeline/types/PipelineStep.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export type PipelineStep = {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
position: number;
|
||||
};
|
||||
@ -1,10 +1,11 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
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 { useRecoilValue } from 'recoil';
|
||||
|
||||
import { GET_PIPELINE_PROGRESS } from '@/pipeline/graphql/queries/getPipelineProgress';
|
||||
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||
import { StyledBoard } from '@/ui/layout/board/components/StyledBoard';
|
||||
import { BoardColumnContext } from '@/ui/layout/board/contexts/BoardColumnContext';
|
||||
@ -13,11 +14,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import {
|
||||
PipelineProgress,
|
||||
PipelineStage,
|
||||
useUpdateOnePipelineProgressStageMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { PipelineProgress, PipelineStage } from '~/generated/graphql';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
import { useCurrentCardSelected } from '../hooks/useCurrentCardSelected';
|
||||
@ -59,8 +56,12 @@ export const EntityBoard = ({
|
||||
const boardColumns = useRecoilValue(boardColumnsState);
|
||||
const setCardSelected = useSetCardSelected();
|
||||
|
||||
const [updatePipelineProgressStage] =
|
||||
useUpdateOnePipelineProgressStageMutation();
|
||||
const { updateOneObject: updateOneOpportunity } =
|
||||
useUpdateOneObjectRecord<Opportunity>({
|
||||
objectNameSingular: 'opportunityV2',
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { unselectAllActiveCards } = useCurrentCardSelected();
|
||||
|
||||
@ -69,33 +70,25 @@ export const EntityBoard = ({
|
||||
pipelineProgressId: NonNullable<PipelineProgress['id']>,
|
||||
pipelineStageId: NonNullable<PipelineStage['id']>,
|
||||
) => {
|
||||
updatePipelineProgressStage({
|
||||
variables: {
|
||||
await updateOneOpportunity?.({
|
||||
idToUpdate: pipelineProgressId,
|
||||
input: {
|
||||
pipelineStepId: pipelineStageId,
|
||||
},
|
||||
});
|
||||
|
||||
const cache = apolloClient.cache;
|
||||
cache.modify({
|
||||
id: cache.identify({
|
||||
id: pipelineProgressId,
|
||||
pipelineStageId,
|
||||
__typename: 'PipelineProgress',
|
||||
}),
|
||||
fields: {
|
||||
pipelineStageId: () => pipelineStageId,
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOnePipelineProgress: {
|
||||
__typename: 'PipelineProgress',
|
||||
id: pipelineProgressId,
|
||||
},
|
||||
},
|
||||
update: (cache) => {
|
||||
cache.modify({
|
||||
id: cache.identify({
|
||||
id: pipelineProgressId,
|
||||
__typename: 'PipelineProgress',
|
||||
}),
|
||||
fields: {
|
||||
pipelineStageId: () => pipelineStageId,
|
||||
},
|
||||
});
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_PIPELINE_PROGRESS) ?? ''],
|
||||
});
|
||||
},
|
||||
[updatePipelineProgressStage],
|
||||
[apolloClient.cache, updateOneOpportunity],
|
||||
);
|
||||
|
||||
useListenClickOutsideByClassName({
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { IconTrash } from '@/ui/display/icon';
|
||||
import { useDeleteSelectedBoardCards } from '@/ui/layout/board/hooks/useDeleteSelectedBoardCards';
|
||||
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
|
||||
|
||||
import { useDeleteSelectedBoardCards } from './useDeleteSelectedBoardCards';
|
||||
|
||||
export const useBoardActionBarEntries = () => {
|
||||
const setActionBarEntries = useSetRecoilState(actionBarEntriesState);
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
|
||||
import { useUpdatePipelineStageMutation } from '~/generated/graphql';
|
||||
|
||||
import { boardColumnsState } from '../states/boardColumnsState';
|
||||
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||
@ -11,19 +12,20 @@ export const useBoardColumns = () => {
|
||||
|
||||
const { handleColumnMove } = useMoveViewColumns();
|
||||
|
||||
const [updatePipelineStageMutation] = useUpdatePipelineStageMutation();
|
||||
const { updateOneObject: updateOnePipelineStep } =
|
||||
useUpdateOneObjectRecord<PipelineStep>({
|
||||
objectNameSingular: 'pipelineStepV2',
|
||||
});
|
||||
|
||||
const updatedPipelineStages = (stages: BoardColumnDefinition[]) => {
|
||||
if (!stages.length) return;
|
||||
|
||||
return Promise.all(
|
||||
stages.map((stage) =>
|
||||
updatePipelineStageMutation({
|
||||
variables: {
|
||||
data: {
|
||||
position: stage.position,
|
||||
},
|
||||
id: stage.id,
|
||||
updateOnePipelineStep?.({
|
||||
idToUpdate: stage.id,
|
||||
input: {
|
||||
position: stage.position,
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { IconTrash } from '@/ui/display/icon';
|
||||
import { useDeleteSelectedBoardCards } from '@/ui/layout/board/hooks/useDeleteSelectedBoardCards';
|
||||
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
|
||||
|
||||
import { useDeleteSelectedBoardCards } from './useDeleteSelectedBoardCards';
|
||||
|
||||
export const useBoardContextMenuEntries = () => {
|
||||
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
|
||||
|
||||
|
||||
@ -1,40 +1,41 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { GET_PIPELINES } from '@/pipeline/graphql/queries/getPipelines';
|
||||
import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql';
|
||||
import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
|
||||
import { selectedCardIdsSelector } from '../states/selectors/selectedCardIdsSelector';
|
||||
|
||||
import { useRemoveCardIds } from './useRemoveCardIds';
|
||||
|
||||
export const useDeleteSelectedBoardCards = () => {
|
||||
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
|
||||
const removeCardIds = useRemoveCardIds();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({
|
||||
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
|
||||
});
|
||||
const { deleteOneObject: deleteOneOpportunity } =
|
||||
useDeleteOneObjectRecord<Opportunity>({
|
||||
objectNameSingular: 'opportunityV2',
|
||||
});
|
||||
|
||||
const deleteSelectedBoardCards = async () => {
|
||||
await deletePipelineProgress({
|
||||
variables: {
|
||||
ids: selectedCardIds,
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
deleteManyPipelineProgress: {
|
||||
count: selectedCardIds.length,
|
||||
},
|
||||
},
|
||||
update: (cache) => {
|
||||
const deleteSelectedBoardCards = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async () => {
|
||||
const selectedCardIds = snapshot
|
||||
.getLoadable(selectedCardIdsSelector)
|
||||
.getValue();
|
||||
|
||||
await Promise.all(
|
||||
selectedCardIds.map(async (id) => {
|
||||
await deleteOneOpportunity?.(id);
|
||||
}),
|
||||
);
|
||||
removeCardIds(selectedCardIds);
|
||||
selectedCardIds.forEach((id) => {
|
||||
cache.evict({ id: `PipelineProgress:${id}` });
|
||||
apolloClient.cache.evict({ id: `Opportunity:${id}` });
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
[apolloClient.cache, deleteOneOpportunity, removeCardIds],
|
||||
);
|
||||
|
||||
return deleteSelectedBoardCards;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user