Fix/opportunities board (#2610)
* WIP * wip * update pipelineStepId * rename pipeline stage to pipeline step * rename pipelineProgress to Opportunity * fix UUID type not queried * fix boardColumnTotal * fix micros * fixing filters, sorts and fields * wip * wip * Fix opportunity board re-render --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
This commit is contained in:
@ -143,7 +143,7 @@ export const CompanyBoardCard = () => {
|
||||
|
||||
const showCompactView = isCompactViewEnabled && isCardInCompactView;
|
||||
|
||||
const { pipelineProgress, company } = companyProgress ?? {};
|
||||
const { opportunity, company } = companyProgress ?? {};
|
||||
|
||||
const visibleBoardCardFields = useRecoilScopedValue(
|
||||
visibleBoardCardFieldsScopedSelector,
|
||||
@ -175,7 +175,7 @@ export const CompanyBoardCard = () => {
|
||||
};
|
||||
|
||||
// boardCardId check can be moved to a wrapper to avoid unnecessary logic above
|
||||
if (!company || !pipelineProgress || !boardCardId) {
|
||||
if (!company || !opportunity || !boardCardId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ export type CompanyProgressPickerProps = {
|
||||
companyId: string | null;
|
||||
onSubmit: (
|
||||
newCompanyId: EntityForSelect | null,
|
||||
newPipelineStageId: string | null,
|
||||
newPipelineStepId: string | null,
|
||||
) => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
@ -39,40 +39,40 @@ export const CompanyProgressPicker = ({
|
||||
const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] =
|
||||
useState(false);
|
||||
|
||||
const [selectedPipelineStageId, setSelectedPipelineStageId] = useState<
|
||||
const [selectedPipelineStepId, setSelectedPipelineStepId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
const [currentPipeline] = useRecoilState(currentPipelineState);
|
||||
|
||||
const currentPipelineStages = useMemo(
|
||||
() => currentPipeline?.pipelineStages ?? [],
|
||||
const currentPipelineSteps = useMemo(
|
||||
() => currentPipeline?.pipelineSteps ?? [],
|
||||
[currentPipeline],
|
||||
);
|
||||
|
||||
const handlePipelineStageChange = (newPipelineStageId: string) => {
|
||||
setSelectedPipelineStageId(newPipelineStageId);
|
||||
const handlePipelineStepChange = (newPipelineStepId: string) => {
|
||||
setSelectedPipelineStepId(newPipelineStepId);
|
||||
setIsProgressSelectionUnfolded(false);
|
||||
};
|
||||
|
||||
const handleEntitySelected = async (
|
||||
selectedCompany: EntityForSelect | null | undefined,
|
||||
) => {
|
||||
onSubmit(selectedCompany ?? null, selectedPipelineStageId);
|
||||
onSubmit(selectedCompany ?? null, selectedPipelineStepId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPipelineStages?.[0]?.id) {
|
||||
setSelectedPipelineStageId(currentPipelineStages?.[0]?.id);
|
||||
if (currentPipelineSteps?.[0]?.id) {
|
||||
setSelectedPipelineStepId(currentPipelineSteps?.[0]?.id);
|
||||
}
|
||||
}, [currentPipelineStages]);
|
||||
}, [currentPipelineSteps]);
|
||||
|
||||
const selectedPipelineStage = useMemo(
|
||||
const selectedPipelineStep = useMemo(
|
||||
() =>
|
||||
currentPipelineStages.find(
|
||||
(pipelineStage: any) => pipelineStage.id === selectedPipelineStageId,
|
||||
currentPipelineSteps.find(
|
||||
(pipelineStep: any) => pipelineStep.id === selectedPipelineStepId,
|
||||
),
|
||||
[currentPipelineStages, selectedPipelineStageId],
|
||||
[currentPipelineSteps, selectedPipelineStepId],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -82,14 +82,14 @@ export const CompanyProgressPicker = ({
|
||||
>
|
||||
{isProgressSelectionUnfolded ? (
|
||||
<DropdownMenuItemsContainer>
|
||||
{currentPipelineStages.map((pipelineStage: any, index: number) => (
|
||||
{currentPipelineSteps.map((pipelineStep: any, index: number) => (
|
||||
<MenuItem
|
||||
key={pipelineStage.id}
|
||||
key={pipelineStep.id}
|
||||
testId={`select-pipeline-stage-${index}`}
|
||||
onClick={() => {
|
||||
handlePipelineStageChange(pipelineStage.id);
|
||||
handlePipelineStepChange(pipelineStep.id);
|
||||
}}
|
||||
text={pipelineStage.name}
|
||||
text={pipelineStep.name}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
@ -100,7 +100,7 @@ export const CompanyProgressPicker = ({
|
||||
EndIcon={IconChevronDown}
|
||||
onClick={() => setIsProgressSelectionUnfolded(true)}
|
||||
>
|
||||
{selectedPipelineStage?.name}
|
||||
{selectedPipelineStep?.name}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSearchInput
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { useComputeDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useComputeDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
||||
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
|
||||
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
import { useBoardActionBarEntries } from '@/ui/layout/board/hooks/useBoardActionBarEntries';
|
||||
@ -14,11 +17,12 @@ import { availableBoardCardFieldsScopedState } from '@/ui/layout/board/states/av
|
||||
import { boardCardFieldsScopedState } from '@/ui/layout/board/states/boardCardFieldsScopedState';
|
||||
import { isBoardLoadedState } from '@/ui/layout/board/states/isBoardLoadedState';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2';
|
||||
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 { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
|
||||
import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
|
||||
@ -41,6 +45,13 @@ export const HooksCompanyBoardEffect = () => {
|
||||
|
||||
const currentViewFields = useRecoilValue(currentViewFieldsState);
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNamePlural: 'opportunities',
|
||||
});
|
||||
|
||||
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
||||
useComputeDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||
|
||||
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);
|
||||
|
||||
const { BoardRecoilScopeContext } = useBoardContext();
|
||||
@ -50,10 +61,6 @@ export const HooksCompanyBoardEffect = () => {
|
||||
BoardRecoilScopeContext,
|
||||
);
|
||||
|
||||
const [, setAvailableBoardCardFields] = useRecoilScopedState(
|
||||
availableBoardCardFieldsScopedState,
|
||||
BoardRecoilScopeContext,
|
||||
);
|
||||
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds();
|
||||
const updateCompanyBoard = useUpdateCompanyBoard();
|
||||
|
||||
@ -70,9 +77,9 @@ export const HooksCompanyBoardEffect = () => {
|
||||
|
||||
const whereFilters = useMemo(() => {
|
||||
return {
|
||||
AND: [
|
||||
and: [
|
||||
{
|
||||
pipelineStageId: {
|
||||
pipelineStepId: {
|
||||
in: pipelineSteps.map((pipelineStep) => pipelineStep.id),
|
||||
},
|
||||
},
|
||||
@ -86,8 +93,10 @@ export const HooksCompanyBoardEffect = () => {
|
||||
objectNamePlural: 'opportunities',
|
||||
filter: whereFilters,
|
||||
onCompleted: useCallback(
|
||||
(_data: PaginatedObjectTypeResults<Opportunity>) => {
|
||||
const pipelineProgresses: Array<Opportunity> = [];
|
||||
(data: PaginatedObjectTypeResults<Opportunity>) => {
|
||||
const pipelineProgresses: Array<Opportunity> = data.edges.map(
|
||||
(edge) => edge.node,
|
||||
);
|
||||
|
||||
updateCompanyBoardCardIds(pipelineProgresses);
|
||||
|
||||
@ -112,15 +121,63 @@ export const HooksCompanyBoardEffect = () => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setAvailableFilterDefinitions(opportunitiesBoardOptions.filterDefinitions);
|
||||
setAvailableSortDefinitions?.(opportunitiesBoardOptions.sortDefinitions);
|
||||
setAvailableFieldDefinitions?.([]);
|
||||
if (!objectMetadataItem) {
|
||||
return;
|
||||
}
|
||||
setAvailableFilterDefinitions?.(filterDefinitions);
|
||||
setAvailableSortDefinitions?.(sortDefinitions);
|
||||
setAvailableFieldDefinitions?.(columnDefinitions);
|
||||
}, [
|
||||
columnDefinitions,
|
||||
filterDefinitions,
|
||||
objectMetadataItem,
|
||||
setAvailableFieldDefinitions,
|
||||
setAvailableFilterDefinitions,
|
||||
setAvailableSortDefinitions,
|
||||
sortDefinitions,
|
||||
]);
|
||||
|
||||
const setAvailableBoardCardFields = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(availableBoardCardFields: any) => {
|
||||
const availableBoardCardFieldsFromState = snapshot
|
||||
.getLoadable(
|
||||
availableBoardCardFieldsScopedState({
|
||||
scopeId: 'company-board-view',
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (
|
||||
!isDeeplyEqual(
|
||||
availableBoardCardFieldsFromState,
|
||||
availableBoardCardFields,
|
||||
)
|
||||
) {
|
||||
set(
|
||||
availableBoardCardFieldsScopedState({
|
||||
scopeId: 'company-board-view',
|
||||
}),
|
||||
availableBoardCardFields,
|
||||
);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useRecoilScopedStateV2(
|
||||
availableBoardCardFieldsScopedState,
|
||||
'company-board-view',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const availableTableColumns = columnDefinitions.filter(
|
||||
filterAvailableTableColumns,
|
||||
);
|
||||
|
||||
setAvailableBoardCardFields(availableTableColumns);
|
||||
}, [columnDefinitions, setAvailableBoardCardFields]);
|
||||
|
||||
useEffect(() => {
|
||||
setViewObjectMetadataId?.('company');
|
||||
setViewType?.(ViewType.Kanban);
|
||||
@ -137,21 +194,19 @@ export const HooksCompanyBoardEffect = () => {
|
||||
if (!loading && opportunities && companies) {
|
||||
setActionBarEntries();
|
||||
setContextMenuEntries();
|
||||
setAvailableBoardCardFields([]);
|
||||
|
||||
updateCompanyBoard(pipelineSteps, opportunities, companies);
|
||||
setEntityCountInCurrentView(companies.length);
|
||||
}
|
||||
}, [
|
||||
companies,
|
||||
loading,
|
||||
updateCompanyBoard,
|
||||
opportunities,
|
||||
pipelineSteps,
|
||||
setActionBarEntries,
|
||||
setContextMenuEntries,
|
||||
searchParams,
|
||||
setEntityCountInCurrentView,
|
||||
setAvailableBoardCardFields,
|
||||
opportunities,
|
||||
companies,
|
||||
pipelineSteps,
|
||||
updateCompanyBoard,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -12,7 +12,7 @@ export const NewCompanyProgressButton = () => {
|
||||
const [isCreatingCard, setIsCreatingCard] = useState(false);
|
||||
const column = useContext(BoardColumnContext);
|
||||
|
||||
const pipelineStageId = column?.columnDefinition.id || '';
|
||||
const pipelineStepId = column?.columnDefinition.id || '';
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
@ -25,7 +25,7 @@ export const NewCompanyProgressButton = () => {
|
||||
setIsCreatingCard(false);
|
||||
goBackToPreviousHotkeyScope();
|
||||
|
||||
if (!pipelineStageId) {
|
||||
if (!pipelineStepId) {
|
||||
enqueueSnackBar('Pipeline stage id is not defined', {
|
||||
variant: 'error',
|
||||
});
|
||||
@ -33,7 +33,7 @@ export const NewCompanyProgressButton = () => {
|
||||
throw new Error('Pipeline stage id is not defined');
|
||||
}
|
||||
|
||||
//createCompanyProgress(company.id, pipelineStageId);
|
||||
//createCompanyProgress(company.id, pipelineStepId);
|
||||
};
|
||||
|
||||
const handleNewClick = useCallback(() => {
|
||||
|
||||
@ -7,7 +7,7 @@ import { boardColumnsState } from '@/ui/layout/board/states/boardColumnsState';
|
||||
export const useUpdateCompanyBoardCardIds = () =>
|
||||
useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(pipelineProgresses: Opportunity[]) => {
|
||||
(pipelineProgresses: Pick<Opportunity, 'pipelineStepId' | 'id'>[]) => {
|
||||
const boardColumns = snapshot
|
||||
.getLoadable(boardColumnsState)
|
||||
.valueOrThrow();
|
||||
@ -16,7 +16,7 @@ export const useUpdateCompanyBoardCardIds = () =>
|
||||
const boardCardIds = pipelineProgresses
|
||||
.filter(
|
||||
(pipelineProgressToFilter) =>
|
||||
pipelineProgressToFilter.pipelineStageId === boardColumn.id,
|
||||
pipelineProgressToFilter.pipelineStepId === boardColumn.id,
|
||||
)
|
||||
.map((pipelineProgress) => pipelineProgress.id);
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ export const useUpdateCompanyBoard = () =>
|
||||
({ set, snapshot }) =>
|
||||
(
|
||||
pipelineSteps: PipelineStep[],
|
||||
pipelineProgresses: Opportunity[],
|
||||
opportunities: Opportunity[],
|
||||
companies: CompanyForBoard[],
|
||||
) => {
|
||||
const indexCompanyByIdReducer = (
|
||||
@ -37,27 +37,26 @@ export const useUpdateCompanyBoard = () =>
|
||||
{} as { [key: string]: CompanyForBoard },
|
||||
) ?? {};
|
||||
|
||||
const indexPipelineProgressByIdReducer = (
|
||||
const indexOpportunityByIdReducer = (
|
||||
acc: CompanyProgressDict,
|
||||
pipelineProgress: Opportunity,
|
||||
opportunity: Opportunity,
|
||||
) => {
|
||||
const company =
|
||||
pipelineProgress.companyId &&
|
||||
companiesDict[pipelineProgress.companyId];
|
||||
opportunity.companyId && companiesDict[opportunity.companyId];
|
||||
|
||||
if (!company) return acc;
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[pipelineProgress.id]: {
|
||||
pipelineProgress,
|
||||
[opportunity.id]: {
|
||||
opportunity,
|
||||
company,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const companyBoardIndex = pipelineProgresses.reduce(
|
||||
indexPipelineProgressByIdReducer,
|
||||
const companyBoardIndex = opportunities.reduce(
|
||||
indexOpportunityByIdReducer,
|
||||
{} as CompanyProgressDict,
|
||||
);
|
||||
|
||||
@ -68,7 +67,7 @@ export const useUpdateCompanyBoard = () =>
|
||||
|
||||
if (!isDeeplyEqual(currentCompanyProgress, companyProgress)) {
|
||||
set(companyProgressesFamilyState(id), companyProgress);
|
||||
set(entityFieldsFamilyState(id), companyProgress.pipelineProgress);
|
||||
set(entityFieldsFamilyState(id), companyProgress.opportunity);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,29 +80,29 @@ export const useUpdateCompanyBoard = () =>
|
||||
.valueOrThrow();
|
||||
|
||||
if (!isDeeplyEqual(pipelineSteps, currentPipelineSteps)) {
|
||||
set(currentPipelineStepsState, currentPipelineSteps);
|
||||
set(currentPipelineStepsState, pipelineSteps);
|
||||
}
|
||||
|
||||
const orderedPipelineStages = [...pipelineSteps].sort((a, b) => {
|
||||
const orderedPipelineSteps = [...pipelineSteps].sort((a, b) => {
|
||||
if (!a.position || !b.position) return 0;
|
||||
return a.position - b.position;
|
||||
});
|
||||
|
||||
const newBoardColumns: BoardColumnDefinition[] =
|
||||
orderedPipelineStages?.map((pipelineStage) => {
|
||||
if (!isThemeColor(pipelineStage.color)) {
|
||||
orderedPipelineSteps?.map((pipelineStep) => {
|
||||
if (!isThemeColor(pipelineStep.color)) {
|
||||
logError(
|
||||
`Color ${pipelineStage.color} is not recognized in useUpdateCompanyBoard.`,
|
||||
`Color ${pipelineStep.color} is not recognized in useUpdateCompanyBoard.`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: pipelineStage.id,
|
||||
title: pipelineStage.name,
|
||||
colorCode: isThemeColor(pipelineStage.color)
|
||||
? pipelineStage.color
|
||||
id: pipelineStep.id,
|
||||
title: pipelineStep.name,
|
||||
colorCode: isThemeColor(pipelineStep.color)
|
||||
? pipelineStep.color
|
||||
: undefined,
|
||||
position: pipelineStage.position ?? 0,
|
||||
position: pipelineStep.position ?? 0,
|
||||
};
|
||||
});
|
||||
if (currentBoardColumns.length === 0) {
|
||||
@ -111,12 +110,12 @@ export const useUpdateCompanyBoard = () =>
|
||||
set(savedBoardColumnsState, newBoardColumns);
|
||||
}
|
||||
for (const boardColumn of newBoardColumns) {
|
||||
const boardCardIds = pipelineProgresses
|
||||
const boardCardIds = opportunities
|
||||
.filter(
|
||||
(pipelineProgressToFilter) =>
|
||||
pipelineProgressToFilter.pipelineStageId === boardColumn.id,
|
||||
(opportunityToFilter) =>
|
||||
opportunityToFilter.pipelineStepId === boardColumn.id,
|
||||
)
|
||||
.map((pipelineProgress) => pipelineProgress.id);
|
||||
.map((opportunity) => opportunity.id);
|
||||
|
||||
const currentBoardCardIds = snapshot
|
||||
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
|
||||
|
||||
@ -2,11 +2,10 @@ import { Company } from '@/companies/types/Company';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
|
||||
export type CompanyForBoard = Pick<Company, 'id' | 'name' | 'domainName'>;
|
||||
export type PipelineProgressForBoard = Opportunity;
|
||||
|
||||
export type CompanyProgress = {
|
||||
company: CompanyForBoard;
|
||||
pipelineProgress: PipelineProgressForBoard;
|
||||
opportunity: Opportunity;
|
||||
};
|
||||
|
||||
export type CompanyProgressDict = {
|
||||
|
||||
Reference in New Issue
Block a user