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 showCompactView = isCompactViewEnabled && isCardInCompactView;
|
||||||
|
|
||||||
const { pipelineProgress, company } = companyProgress ?? {};
|
const { opportunity, company } = companyProgress ?? {};
|
||||||
|
|
||||||
const visibleBoardCardFields = useRecoilScopedValue(
|
const visibleBoardCardFields = useRecoilScopedValue(
|
||||||
visibleBoardCardFieldsScopedSelector,
|
visibleBoardCardFieldsScopedSelector,
|
||||||
@ -175,7 +175,7 @@ export const CompanyBoardCard = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// boardCardId check can be moved to a wrapper to avoid unnecessary logic above
|
// boardCardId check can be moved to a wrapper to avoid unnecessary logic above
|
||||||
if (!company || !pipelineProgress || !boardCardId) {
|
if (!company || !opportunity || !boardCardId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export type CompanyProgressPickerProps = {
|
|||||||
companyId: string | null;
|
companyId: string | null;
|
||||||
onSubmit: (
|
onSubmit: (
|
||||||
newCompanyId: EntityForSelect | null,
|
newCompanyId: EntityForSelect | null,
|
||||||
newPipelineStageId: string | null,
|
newPipelineStepId: string | null,
|
||||||
) => void;
|
) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
@ -39,40 +39,40 @@ export const CompanyProgressPicker = ({
|
|||||||
const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] =
|
const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
const [selectedPipelineStageId, setSelectedPipelineStageId] = useState<
|
const [selectedPipelineStepId, setSelectedPipelineStepId] = useState<
|
||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
const [currentPipeline] = useRecoilState(currentPipelineState);
|
const [currentPipeline] = useRecoilState(currentPipelineState);
|
||||||
|
|
||||||
const currentPipelineStages = useMemo(
|
const currentPipelineSteps = useMemo(
|
||||||
() => currentPipeline?.pipelineStages ?? [],
|
() => currentPipeline?.pipelineSteps ?? [],
|
||||||
[currentPipeline],
|
[currentPipeline],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePipelineStageChange = (newPipelineStageId: string) => {
|
const handlePipelineStepChange = (newPipelineStepId: string) => {
|
||||||
setSelectedPipelineStageId(newPipelineStageId);
|
setSelectedPipelineStepId(newPipelineStepId);
|
||||||
setIsProgressSelectionUnfolded(false);
|
setIsProgressSelectionUnfolded(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEntitySelected = async (
|
const handleEntitySelected = async (
|
||||||
selectedCompany: EntityForSelect | null | undefined,
|
selectedCompany: EntityForSelect | null | undefined,
|
||||||
) => {
|
) => {
|
||||||
onSubmit(selectedCompany ?? null, selectedPipelineStageId);
|
onSubmit(selectedCompany ?? null, selectedPipelineStepId);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPipelineStages?.[0]?.id) {
|
if (currentPipelineSteps?.[0]?.id) {
|
||||||
setSelectedPipelineStageId(currentPipelineStages?.[0]?.id);
|
setSelectedPipelineStepId(currentPipelineSteps?.[0]?.id);
|
||||||
}
|
}
|
||||||
}, [currentPipelineStages]);
|
}, [currentPipelineSteps]);
|
||||||
|
|
||||||
const selectedPipelineStage = useMemo(
|
const selectedPipelineStep = useMemo(
|
||||||
() =>
|
() =>
|
||||||
currentPipelineStages.find(
|
currentPipelineSteps.find(
|
||||||
(pipelineStage: any) => pipelineStage.id === selectedPipelineStageId,
|
(pipelineStep: any) => pipelineStep.id === selectedPipelineStepId,
|
||||||
),
|
),
|
||||||
[currentPipelineStages, selectedPipelineStageId],
|
[currentPipelineSteps, selectedPipelineStepId],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -82,14 +82,14 @@ export const CompanyProgressPicker = ({
|
|||||||
>
|
>
|
||||||
{isProgressSelectionUnfolded ? (
|
{isProgressSelectionUnfolded ? (
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{currentPipelineStages.map((pipelineStage: any, index: number) => (
|
{currentPipelineSteps.map((pipelineStep: any, index: number) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={pipelineStage.id}
|
key={pipelineStep.id}
|
||||||
testId={`select-pipeline-stage-${index}`}
|
testId={`select-pipeline-stage-${index}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlePipelineStageChange(pipelineStage.id);
|
handlePipelineStepChange(pipelineStep.id);
|
||||||
}}
|
}}
|
||||||
text={pipelineStage.name}
|
text={pipelineStep.name}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
@ -100,7 +100,7 @@ export const CompanyProgressPicker = ({
|
|||||||
EndIcon={IconChevronDown}
|
EndIcon={IconChevronDown}
|
||||||
onClick={() => setIsProgressSelectionUnfolded(true)}
|
onClick={() => setIsProgressSelectionUnfolded(true)}
|
||||||
>
|
>
|
||||||
{selectedPipelineStage?.name}
|
{selectedPipelineStep?.name}
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuSearchInput
|
<DropdownMenuSearchInput
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { Company } from '@/companies/types/Company';
|
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 { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
||||||
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
|
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
|
||||||
|
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||||
import { useBoardActionBarEntries } from '@/ui/layout/board/hooks/useBoardActionBarEntries';
|
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 { boardCardFieldsScopedState } from '@/ui/layout/board/states/boardCardFieldsScopedState';
|
||||||
import { isBoardLoadedState } from '@/ui/layout/board/states/isBoardLoadedState';
|
import { isBoardLoadedState } from '@/ui/layout/board/states/isBoardLoadedState';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
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 { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
|
||||||
import { useView } from '@/views/hooks/useView';
|
import { useView } from '@/views/hooks/useView';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { mapViewFieldsToBoardFieldDefinitions } from '@/views/utils/mapViewFieldsToBoardFieldDefinitions';
|
import { mapViewFieldsToBoardFieldDefinitions } from '@/views/utils/mapViewFieldsToBoardFieldDefinitions';
|
||||||
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
|
import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
|
||||||
import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
|
import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
|
||||||
@ -41,6 +45,13 @@ export const HooksCompanyBoardEffect = () => {
|
|||||||
|
|
||||||
const currentViewFields = useRecoilValue(currentViewFieldsState);
|
const currentViewFields = useRecoilValue(currentViewFieldsState);
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNamePlural: 'opportunities',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
||||||
|
useComputeDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||||
|
|
||||||
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);
|
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);
|
||||||
|
|
||||||
const { BoardRecoilScopeContext } = useBoardContext();
|
const { BoardRecoilScopeContext } = useBoardContext();
|
||||||
@ -50,10 +61,6 @@ export const HooksCompanyBoardEffect = () => {
|
|||||||
BoardRecoilScopeContext,
|
BoardRecoilScopeContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [, setAvailableBoardCardFields] = useRecoilScopedState(
|
|
||||||
availableBoardCardFieldsScopedState,
|
|
||||||
BoardRecoilScopeContext,
|
|
||||||
);
|
|
||||||
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds();
|
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds();
|
||||||
const updateCompanyBoard = useUpdateCompanyBoard();
|
const updateCompanyBoard = useUpdateCompanyBoard();
|
||||||
|
|
||||||
@ -70,9 +77,9 @@ export const HooksCompanyBoardEffect = () => {
|
|||||||
|
|
||||||
const whereFilters = useMemo(() => {
|
const whereFilters = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
AND: [
|
and: [
|
||||||
{
|
{
|
||||||
pipelineStageId: {
|
pipelineStepId: {
|
||||||
in: pipelineSteps.map((pipelineStep) => pipelineStep.id),
|
in: pipelineSteps.map((pipelineStep) => pipelineStep.id),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -86,8 +93,10 @@ export const HooksCompanyBoardEffect = () => {
|
|||||||
objectNamePlural: 'opportunities',
|
objectNamePlural: 'opportunities',
|
||||||
filter: whereFilters,
|
filter: whereFilters,
|
||||||
onCompleted: useCallback(
|
onCompleted: useCallback(
|
||||||
(_data: PaginatedObjectTypeResults<Opportunity>) => {
|
(data: PaginatedObjectTypeResults<Opportunity>) => {
|
||||||
const pipelineProgresses: Array<Opportunity> = [];
|
const pipelineProgresses: Array<Opportunity> = data.edges.map(
|
||||||
|
(edge) => edge.node,
|
||||||
|
);
|
||||||
|
|
||||||
updateCompanyBoardCardIds(pipelineProgresses);
|
updateCompanyBoardCardIds(pipelineProgresses);
|
||||||
|
|
||||||
@ -112,15 +121,63 @@ export const HooksCompanyBoardEffect = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAvailableFilterDefinitions(opportunitiesBoardOptions.filterDefinitions);
|
if (!objectMetadataItem) {
|
||||||
setAvailableSortDefinitions?.(opportunitiesBoardOptions.sortDefinitions);
|
return;
|
||||||
setAvailableFieldDefinitions?.([]);
|
}
|
||||||
|
setAvailableFilterDefinitions?.(filterDefinitions);
|
||||||
|
setAvailableSortDefinitions?.(sortDefinitions);
|
||||||
|
setAvailableFieldDefinitions?.(columnDefinitions);
|
||||||
}, [
|
}, [
|
||||||
|
columnDefinitions,
|
||||||
|
filterDefinitions,
|
||||||
|
objectMetadataItem,
|
||||||
setAvailableFieldDefinitions,
|
setAvailableFieldDefinitions,
|
||||||
setAvailableFilterDefinitions,
|
setAvailableFilterDefinitions,
|
||||||
setAvailableSortDefinitions,
|
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(() => {
|
useEffect(() => {
|
||||||
setViewObjectMetadataId?.('company');
|
setViewObjectMetadataId?.('company');
|
||||||
setViewType?.(ViewType.Kanban);
|
setViewType?.(ViewType.Kanban);
|
||||||
@ -137,21 +194,19 @@ export const HooksCompanyBoardEffect = () => {
|
|||||||
if (!loading && opportunities && companies) {
|
if (!loading && opportunities && companies) {
|
||||||
setActionBarEntries();
|
setActionBarEntries();
|
||||||
setContextMenuEntries();
|
setContextMenuEntries();
|
||||||
setAvailableBoardCardFields([]);
|
|
||||||
updateCompanyBoard(pipelineSteps, opportunities, companies);
|
updateCompanyBoard(pipelineSteps, opportunities, companies);
|
||||||
setEntityCountInCurrentView(companies.length);
|
setEntityCountInCurrentView(companies.length);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
companies,
|
||||||
loading,
|
loading,
|
||||||
updateCompanyBoard,
|
opportunities,
|
||||||
|
pipelineSteps,
|
||||||
setActionBarEntries,
|
setActionBarEntries,
|
||||||
setContextMenuEntries,
|
setContextMenuEntries,
|
||||||
searchParams,
|
|
||||||
setEntityCountInCurrentView,
|
setEntityCountInCurrentView,
|
||||||
setAvailableBoardCardFields,
|
updateCompanyBoard,
|
||||||
opportunities,
|
|
||||||
companies,
|
|
||||||
pipelineSteps,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export const NewCompanyProgressButton = () => {
|
|||||||
const [isCreatingCard, setIsCreatingCard] = useState(false);
|
const [isCreatingCard, setIsCreatingCard] = useState(false);
|
||||||
const column = useContext(BoardColumnContext);
|
const column = useContext(BoardColumnContext);
|
||||||
|
|
||||||
const pipelineStageId = column?.columnDefinition.id || '';
|
const pipelineStepId = column?.columnDefinition.id || '';
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export const NewCompanyProgressButton = () => {
|
|||||||
setIsCreatingCard(false);
|
setIsCreatingCard(false);
|
||||||
goBackToPreviousHotkeyScope();
|
goBackToPreviousHotkeyScope();
|
||||||
|
|
||||||
if (!pipelineStageId) {
|
if (!pipelineStepId) {
|
||||||
enqueueSnackBar('Pipeline stage id is not defined', {
|
enqueueSnackBar('Pipeline stage id is not defined', {
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
});
|
});
|
||||||
@ -33,7 +33,7 @@ export const NewCompanyProgressButton = () => {
|
|||||||
throw new Error('Pipeline stage id is not defined');
|
throw new Error('Pipeline stage id is not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
//createCompanyProgress(company.id, pipelineStageId);
|
//createCompanyProgress(company.id, pipelineStepId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNewClick = useCallback(() => {
|
const handleNewClick = useCallback(() => {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { boardColumnsState } from '@/ui/layout/board/states/boardColumnsState';
|
|||||||
export const useUpdateCompanyBoardCardIds = () =>
|
export const useUpdateCompanyBoardCardIds = () =>
|
||||||
useRecoilCallback(
|
useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
(pipelineProgresses: Opportunity[]) => {
|
(pipelineProgresses: Pick<Opportunity, 'pipelineStepId' | 'id'>[]) => {
|
||||||
const boardColumns = snapshot
|
const boardColumns = snapshot
|
||||||
.getLoadable(boardColumnsState)
|
.getLoadable(boardColumnsState)
|
||||||
.valueOrThrow();
|
.valueOrThrow();
|
||||||
@ -16,7 +16,7 @@ export const useUpdateCompanyBoardCardIds = () =>
|
|||||||
const boardCardIds = pipelineProgresses
|
const boardCardIds = pipelineProgresses
|
||||||
.filter(
|
.filter(
|
||||||
(pipelineProgressToFilter) =>
|
(pipelineProgressToFilter) =>
|
||||||
pipelineProgressToFilter.pipelineStageId === boardColumn.id,
|
pipelineProgressToFilter.pipelineStepId === boardColumn.id,
|
||||||
)
|
)
|
||||||
.map((pipelineProgress) => pipelineProgress.id);
|
.map((pipelineProgress) => pipelineProgress.id);
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export const useUpdateCompanyBoard = () =>
|
|||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
(
|
(
|
||||||
pipelineSteps: PipelineStep[],
|
pipelineSteps: PipelineStep[],
|
||||||
pipelineProgresses: Opportunity[],
|
opportunities: Opportunity[],
|
||||||
companies: CompanyForBoard[],
|
companies: CompanyForBoard[],
|
||||||
) => {
|
) => {
|
||||||
const indexCompanyByIdReducer = (
|
const indexCompanyByIdReducer = (
|
||||||
@ -37,27 +37,26 @@ export const useUpdateCompanyBoard = () =>
|
|||||||
{} as { [key: string]: CompanyForBoard },
|
{} as { [key: string]: CompanyForBoard },
|
||||||
) ?? {};
|
) ?? {};
|
||||||
|
|
||||||
const indexPipelineProgressByIdReducer = (
|
const indexOpportunityByIdReducer = (
|
||||||
acc: CompanyProgressDict,
|
acc: CompanyProgressDict,
|
||||||
pipelineProgress: Opportunity,
|
opportunity: Opportunity,
|
||||||
) => {
|
) => {
|
||||||
const company =
|
const company =
|
||||||
pipelineProgress.companyId &&
|
opportunity.companyId && companiesDict[opportunity.companyId];
|
||||||
companiesDict[pipelineProgress.companyId];
|
|
||||||
|
|
||||||
if (!company) return acc;
|
if (!company) return acc;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
[pipelineProgress.id]: {
|
[opportunity.id]: {
|
||||||
pipelineProgress,
|
opportunity,
|
||||||
company,
|
company,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const companyBoardIndex = pipelineProgresses.reduce(
|
const companyBoardIndex = opportunities.reduce(
|
||||||
indexPipelineProgressByIdReducer,
|
indexOpportunityByIdReducer,
|
||||||
{} as CompanyProgressDict,
|
{} as CompanyProgressDict,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ export const useUpdateCompanyBoard = () =>
|
|||||||
|
|
||||||
if (!isDeeplyEqual(currentCompanyProgress, companyProgress)) {
|
if (!isDeeplyEqual(currentCompanyProgress, companyProgress)) {
|
||||||
set(companyProgressesFamilyState(id), companyProgress);
|
set(companyProgressesFamilyState(id), companyProgress);
|
||||||
set(entityFieldsFamilyState(id), companyProgress.pipelineProgress);
|
set(entityFieldsFamilyState(id), companyProgress.opportunity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,29 +80,29 @@ export const useUpdateCompanyBoard = () =>
|
|||||||
.valueOrThrow();
|
.valueOrThrow();
|
||||||
|
|
||||||
if (!isDeeplyEqual(pipelineSteps, currentPipelineSteps)) {
|
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;
|
if (!a.position || !b.position) return 0;
|
||||||
return a.position - b.position;
|
return a.position - b.position;
|
||||||
});
|
});
|
||||||
|
|
||||||
const newBoardColumns: BoardColumnDefinition[] =
|
const newBoardColumns: BoardColumnDefinition[] =
|
||||||
orderedPipelineStages?.map((pipelineStage) => {
|
orderedPipelineSteps?.map((pipelineStep) => {
|
||||||
if (!isThemeColor(pipelineStage.color)) {
|
if (!isThemeColor(pipelineStep.color)) {
|
||||||
logError(
|
logError(
|
||||||
`Color ${pipelineStage.color} is not recognized in useUpdateCompanyBoard.`,
|
`Color ${pipelineStep.color} is not recognized in useUpdateCompanyBoard.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: pipelineStage.id,
|
id: pipelineStep.id,
|
||||||
title: pipelineStage.name,
|
title: pipelineStep.name,
|
||||||
colorCode: isThemeColor(pipelineStage.color)
|
colorCode: isThemeColor(pipelineStep.color)
|
||||||
? pipelineStage.color
|
? pipelineStep.color
|
||||||
: undefined,
|
: undefined,
|
||||||
position: pipelineStage.position ?? 0,
|
position: pipelineStep.position ?? 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
if (currentBoardColumns.length === 0) {
|
if (currentBoardColumns.length === 0) {
|
||||||
@ -111,12 +110,12 @@ export const useUpdateCompanyBoard = () =>
|
|||||||
set(savedBoardColumnsState, newBoardColumns);
|
set(savedBoardColumnsState, newBoardColumns);
|
||||||
}
|
}
|
||||||
for (const boardColumn of newBoardColumns) {
|
for (const boardColumn of newBoardColumns) {
|
||||||
const boardCardIds = pipelineProgresses
|
const boardCardIds = opportunities
|
||||||
.filter(
|
.filter(
|
||||||
(pipelineProgressToFilter) =>
|
(opportunityToFilter) =>
|
||||||
pipelineProgressToFilter.pipelineStageId === boardColumn.id,
|
opportunityToFilter.pipelineStepId === boardColumn.id,
|
||||||
)
|
)
|
||||||
.map((pipelineProgress) => pipelineProgress.id);
|
.map((opportunity) => opportunity.id);
|
||||||
|
|
||||||
const currentBoardCardIds = snapshot
|
const currentBoardCardIds = snapshot
|
||||||
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
|
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
|
||||||
|
|||||||
@ -2,11 +2,10 @@ import { Company } from '@/companies/types/Company';
|
|||||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||||
|
|
||||||
export type CompanyForBoard = Pick<Company, 'id' | 'name' | 'domainName'>;
|
export type CompanyForBoard = Pick<Company, 'id' | 'name' | 'domainName'>;
|
||||||
export type PipelineProgressForBoard = Opportunity;
|
|
||||||
|
|
||||||
export type CompanyProgress = {
|
export type CompanyProgress = {
|
||||||
company: CompanyForBoard;
|
company: CompanyForBoard;
|
||||||
pipelineProgress: PipelineProgressForBoard;
|
opportunity: Opportunity;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CompanyProgressDict = {
|
export type CompanyProgressDict = {
|
||||||
|
|||||||
@ -1,47 +1,34 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper';
|
import { AvatarType } from '@/users/components/Avatar';
|
||||||
import { Nullable } from '~/types/Nullable';
|
import { Nullable } from '~/types/Nullable';
|
||||||
|
|
||||||
export const useObjectMainIdentifier = (
|
export const useObjectMainIdentifier = (
|
||||||
objectMetadataItem?: Nullable<ObjectMetadataItem>,
|
objectMetadataItem?: Nullable<ObjectMetadataItem>,
|
||||||
) => {
|
) => {
|
||||||
if (!objectMetadataItem) {
|
if (!objectMetadataItem) {
|
||||||
return {
|
return {};
|
||||||
mainIdentifierMapper: undefined,
|
|
||||||
mainIdentifierFieldMetadataId: undefined,
|
|
||||||
basePathToShowPage: undefined,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainIdentifierMapper: MainIdentifierMapper = (record: any) => {
|
const labelIdentifierFieldPaths = ['person', 'workspaceMember'].includes(
|
||||||
if (objectMetadataItem.nameSingular === 'company') {
|
objectMetadataItem.nameSingular,
|
||||||
return {
|
)
|
||||||
id: record.id,
|
? ['name.firstName', 'name.lastName']
|
||||||
name: record.name,
|
: ['name'];
|
||||||
avatarUrl: record.avatarUrl,
|
const imageIdentifierFormat: AvatarType = ['company'].includes(
|
||||||
avatarType: 'squared',
|
objectMetadataItem.nameSingular,
|
||||||
record: record,
|
)
|
||||||
};
|
? 'squared'
|
||||||
}
|
: 'rounded';
|
||||||
|
const imageIdentifierUrlPrefix = ['company'].includes(
|
||||||
if (objectMetadataItem.nameSingular === 'workspaceMember') {
|
objectMetadataItem.nameSingular,
|
||||||
return {
|
)
|
||||||
id: record.id,
|
? 'https://favicon.twenty.com/'
|
||||||
name: record.name.firstName + ' ' + record.name.lastName,
|
: '';
|
||||||
avatarUrl: record.avatarUrl,
|
const imageIdentifierUrlField = ['company'].includes(
|
||||||
avatarType: 'rounded',
|
objectMetadataItem.nameSingular,
|
||||||
record: record,
|
)
|
||||||
};
|
? 'domainName'
|
||||||
}
|
: 'avatarUrl';
|
||||||
|
|
||||||
return {
|
|
||||||
id: record.id,
|
|
||||||
name: record.name,
|
|
||||||
avatarUrl: record.avatarUrl,
|
|
||||||
avatarType: 'rounded',
|
|
||||||
record: record,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mainIdentifierFieldMetadataId = objectMetadataItem.fields.find(
|
const mainIdentifierFieldMetadataId = objectMetadataItem.fields.find(
|
||||||
({ name }) => name === 'name',
|
({ name }) => name === 'name',
|
||||||
@ -50,7 +37,10 @@ export const useObjectMainIdentifier = (
|
|||||||
const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
|
const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mainIdentifierMapper,
|
labelIdentifierFieldPaths,
|
||||||
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
|
imageIdentifierFormat,
|
||||||
mainIdentifierFieldMetadataId,
|
mainIdentifierFieldMetadataId,
|
||||||
basePathToShowPage,
|
basePathToShowPage,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
|
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
|
||||||
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper';
|
|
||||||
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
||||||
import { getLogoUrlFromDomainName } from '~/utils';
|
import { AvatarType } from '@/users/components/Avatar';
|
||||||
|
|
||||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||||
|
|
||||||
@ -18,34 +17,26 @@ export const formatFieldMetadataItemAsColumnDefinition = ({
|
|||||||
const relationObjectMetadataItem =
|
const relationObjectMetadataItem =
|
||||||
field.toRelationMetadata?.fromObjectMetadata;
|
field.toRelationMetadata?.fromObjectMetadata;
|
||||||
|
|
||||||
const mainIdentifierMapper: MainIdentifierMapper = (record: any) => {
|
const labelIdentifierFieldPaths = ['person', 'workspaceMember'].includes(
|
||||||
if (relationObjectMetadataItem?.nameSingular === 'company') {
|
relationObjectMetadataItem?.nameSingular ?? '',
|
||||||
return {
|
)
|
||||||
id: record.id,
|
? ['name.firstName', 'name.lastName']
|
||||||
name: record.name,
|
: ['name'];
|
||||||
avatarUrl: getLogoUrlFromDomainName(record.domainName),
|
const imageIdentifierFormat: AvatarType = ['company'].includes(
|
||||||
avatarType: 'squared',
|
relationObjectMetadataItem?.nameSingular ?? '',
|
||||||
record: record,
|
)
|
||||||
};
|
? 'squared'
|
||||||
}
|
: 'rounded';
|
||||||
if (relationObjectMetadataItem?.nameSingular === 'workspaceMember') {
|
const imageIdentifierUrlPrefix = ['company'].includes(
|
||||||
return {
|
relationObjectMetadataItem?.nameSingular ?? '',
|
||||||
id: record.id,
|
)
|
||||||
name: record.name.firstName + ' ' + record.name.lastName,
|
? 'https://favicon.twenty.com/'
|
||||||
avatarUrl: record.avatarUrl,
|
: '';
|
||||||
avatarType: 'rounded',
|
const imageIdentifierUrlField = ['company'].includes(
|
||||||
record: record,
|
relationObjectMetadataItem?.nameSingular ?? '',
|
||||||
};
|
)
|
||||||
}
|
? 'domainName'
|
||||||
|
: 'avatarUrl';
|
||||||
return {
|
|
||||||
id: record.id,
|
|
||||||
name: record.name,
|
|
||||||
avatarUrl: record.avatarUrl,
|
|
||||||
avatarType: 'rounded',
|
|
||||||
record: record,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position,
|
position,
|
||||||
@ -56,11 +47,15 @@ export const formatFieldMetadataItemAsColumnDefinition = ({
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: field.name,
|
fieldName: field.name,
|
||||||
placeHolder: field.label,
|
placeHolder: field.label,
|
||||||
mainIdentifierMapper: mainIdentifierMapper,
|
labelIdentifierFieldPaths,
|
||||||
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
|
imageIdentifierFormat,
|
||||||
relationType: parseFieldRelationType(field),
|
relationType: parseFieldRelationType(field),
|
||||||
searchFields: ['name'],
|
searchFields: ['name'],
|
||||||
objectMetadataNamePlural: relationObjectMetadataItem?.namePlural,
|
objectMetadataNamePlural: relationObjectMetadataItem?.namePlural ?? '',
|
||||||
objectMetadataNameSingular: relationObjectMetadataItem?.nameSingular,
|
objectMetadataNameSingular:
|
||||||
|
relationObjectMetadataItem?.nameSingular ?? '',
|
||||||
},
|
},
|
||||||
iconName: field.icon ?? 'Icon123',
|
iconName: field.icon ?? 'Icon123',
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
|
|||||||
@ -22,9 +22,12 @@ export const RecordTableEffect = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mainIdentifierMapper,
|
|
||||||
basePathToShowPage,
|
basePathToShowPage,
|
||||||
mainIdentifierFieldMetadataId,
|
mainIdentifierFieldMetadataId,
|
||||||
|
labelIdentifierFieldPaths,
|
||||||
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
|
imageIdentifierFormat,
|
||||||
} = useObjectMainIdentifier(objectMetadataItem);
|
} = useObjectMainIdentifier(objectMetadataItem);
|
||||||
|
|
||||||
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
||||||
@ -40,13 +43,12 @@ export const RecordTableEffect = () => {
|
|||||||
} = useView();
|
} = useView();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (basePathToShowPage && mainIdentifierFieldMetadataId) {
|
||||||
mainIdentifierMapper &&
|
|
||||||
basePathToShowPage &&
|
|
||||||
mainIdentifierFieldMetadataId
|
|
||||||
) {
|
|
||||||
setObjectMetadataConfig?.({
|
setObjectMetadataConfig?.({
|
||||||
mainIdentifierMapper,
|
labelIdentifierFieldPaths,
|
||||||
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
|
imageIdentifierFormat,
|
||||||
basePathToShowPage,
|
basePathToShowPage,
|
||||||
mainIdentifierFieldMetadataId,
|
mainIdentifierFieldMetadataId,
|
||||||
});
|
});
|
||||||
@ -55,8 +57,11 @@ export const RecordTableEffect = () => {
|
|||||||
basePathToShowPage,
|
basePathToShowPage,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
mainIdentifierFieldMetadataId,
|
mainIdentifierFieldMetadataId,
|
||||||
mainIdentifierMapper,
|
|
||||||
setObjectMetadataConfig,
|
setObjectMetadataConfig,
|
||||||
|
labelIdentifierFieldPaths,
|
||||||
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
|
imageIdentifierFormat,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
|
||||||
@ -20,20 +21,25 @@ export const useDeleteOneObjectRecord = <T>({
|
|||||||
// TODO: type this with a minimal type at least with Record<string, any>
|
// TODO: type this with a minimal type at least with Record<string, any>
|
||||||
const [mutate] = useMutation(deleteOneMutation);
|
const [mutate] = useMutation(deleteOneMutation);
|
||||||
|
|
||||||
const deleteOneObject =
|
const deleteOneObject = useCallback(
|
||||||
objectNameSingular && foundObjectMetadataItem
|
async (idToDelete: string) => {
|
||||||
? async (idToDelete: string) => {
|
if (objectNameSingular && foundObjectMetadataItem) {
|
||||||
const deletedObject = await mutate({
|
const deletedObject = await mutate({
|
||||||
variables: {
|
variables: {
|
||||||
idToDelete,
|
idToDelete,
|
||||||
},
|
},
|
||||||
refetchQueries: [getOperationName(findManyQuery) ?? ''],
|
refetchQueries: [getOperationName(findManyQuery) ?? ''],
|
||||||
});
|
});
|
||||||
return deletedObject.data[
|
|
||||||
`create${capitalize(objectNameSingular)}`
|
return deletedObject.data[
|
||||||
] as T;
|
`create${capitalize(objectNameSingular)}`
|
||||||
}
|
] as T;
|
||||||
: undefined;
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[foundObjectMetadataItem, mutate, objectNameSingular, findManyQuery],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
deleteOneObject,
|
deleteOneObject,
|
||||||
|
|||||||
@ -16,5 +16,12 @@ export const filterAvailableTableColumns = (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFieldRelation(columnDefinition) &&
|
||||||
|
columnDefinition.metadata?.fieldName === 'pipelineStep'
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export const PipelineAddButton = () => {
|
|||||||
|
|
||||||
const handleCompanySelected = (
|
const handleCompanySelected = (
|
||||||
selectedCompany: EntityForSelect | null,
|
selectedCompany: EntityForSelect | null,
|
||||||
selectedPipelineStageId: string | null,
|
selectedPipelineStepId: string | null,
|
||||||
) => {
|
) => {
|
||||||
if (!selectedCompany?.id) {
|
if (!selectedCompany?.id) {
|
||||||
enqueueSnackBar(
|
enqueueSnackBar(
|
||||||
@ -33,7 +33,7 @@ export const PipelineAddButton = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedPipelineStageId) {
|
if (!selectedPipelineStepId) {
|
||||||
enqueueSnackBar(
|
enqueueSnackBar(
|
||||||
'There was a problem with the pipeline stage selection, please retry.',
|
'There was a problem with the pipeline stage selection, please retry.',
|
||||||
{
|
{
|
||||||
@ -45,7 +45,7 @@ export const PipelineAddButton = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
//createCompanyProgress(selectedCompany.id, selectedPipelineStageId);
|
//createCompanyProgress(selectedCompany.id, selectedPipelineStepId);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { BoardColumnDefinition } from '@/ui/layout/board/types/BoardColumnDefini
|
|||||||
|
|
||||||
import { currentPipelineState } from '../states/currentPipelineState';
|
import { currentPipelineState } from '../states/currentPipelineState';
|
||||||
|
|
||||||
export const usePipelineStages = () => {
|
export const usePipelineSteps = () => {
|
||||||
const currentPipeline = useRecoilValue(currentPipelineState);
|
const currentPipeline = useRecoilValue(currentPipelineState);
|
||||||
|
|
||||||
const { createOneObject: createOnePipelineStep } =
|
const { createOneObject: createOnePipelineStep } =
|
||||||
@ -20,7 +20,7 @@ export const usePipelineStages = () => {
|
|||||||
objectNameSingular: 'pipelineStep',
|
objectNameSingular: 'pipelineStep',
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlePipelineStageAdd = async (boardColumn: BoardColumnDefinition) => {
|
const handlePipelineStepAdd = async (boardColumn: BoardColumnDefinition) => {
|
||||||
if (!currentPipeline?.id) return;
|
if (!currentPipeline?.id) return;
|
||||||
|
|
||||||
return createOnePipelineStep?.({
|
return createOnePipelineStep?.({
|
||||||
@ -37,11 +37,11 @@ export const usePipelineStages = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePipelineStageDelete = async (boardColumnId: string) => {
|
const handlePipelineStepDelete = async (boardColumnId: string) => {
|
||||||
if (!currentPipeline?.id) return;
|
if (!currentPipeline?.id) return;
|
||||||
|
|
||||||
return deleteOnePipelineStep?.(boardColumnId);
|
return deleteOnePipelineStep?.(boardColumnId);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { handlePipelineStageAdd, handlePipelineStageDelete };
|
return { handlePipelineStepAdd, handlePipelineStepDelete };
|
||||||
};
|
};
|
||||||
@ -2,6 +2,7 @@ import { Person } from '@/people/types/Person';
|
|||||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||||
|
|
||||||
export type Opportunity = {
|
export type Opportunity = {
|
||||||
|
[key: string]: any;
|
||||||
id: string;
|
id: string;
|
||||||
amount: {
|
amount: {
|
||||||
amountMicros: number;
|
amountMicros: number;
|
||||||
@ -13,5 +14,4 @@ export type Opportunity = {
|
|||||||
pipelineStep: PipelineStep;
|
pipelineStep: PipelineStep;
|
||||||
pointOfContactId: string;
|
pointOfContactId: string;
|
||||||
pointOfContact: Pick<Person, 'id' | 'name' | 'avatarUrl'>;
|
pointOfContact: Pick<Person, 'id' | 'name' | 'avatarUrl'>;
|
||||||
[key: string]: any;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -92,7 +92,10 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
defaultValue: relationDefaultValue,
|
defaultValue: relationDefaultValue,
|
||||||
recordMapper: mainIdentifierMapper,
|
labelIdentifierFieldPaths,
|
||||||
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
|
imageIdentifierFormat,
|
||||||
} = useRelationFieldPreview({
|
} = useRelationFieldPreview({
|
||||||
relationObjectMetadataId,
|
relationObjectMetadataId,
|
||||||
skipDefaultValue:
|
skipDefaultValue:
|
||||||
@ -104,6 +107,15 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
? relationDefaultValue
|
? relationDefaultValue
|
||||||
: dataTypes[fieldMetadata.type].defaultValue;
|
: dataTypes[fieldMetadata.type].defaultValue;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!labelIdentifierFieldPaths ||
|
||||||
|
!imageIdentifierUrlField ||
|
||||||
|
!imageIdentifierUrlPrefix ||
|
||||||
|
!imageIdentifierFormat
|
||||||
|
) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer className={className}>
|
<StyledContainer className={className}>
|
||||||
<StyledObjectSummary>
|
<StyledObjectSummary>
|
||||||
@ -146,7 +158,13 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
iconName: 'FieldIcon',
|
iconName: 'FieldIcon',
|
||||||
fieldMetadataId: fieldMetadata.id || '',
|
fieldMetadataId: fieldMetadata.id || '',
|
||||||
label: fieldMetadata.label,
|
label: fieldMetadata.label,
|
||||||
metadata: { fieldName, mainIdentifierMapper },
|
metadata: {
|
||||||
|
fieldName,
|
||||||
|
labelIdentifierFieldPaths,
|
||||||
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
|
imageIdentifierFormat,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
hotkeyScope: 'field-preview',
|
hotkeyScope: 'field-preview',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useObjectMainIdentifier } from '@/object-metadata/hooks/useObjectMainIdentifier';
|
import { useObjectMainIdentifier } from '@/object-metadata/hooks/useObjectMainIdentifier';
|
||||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||||
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
||||||
import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper';
|
|
||||||
|
|
||||||
export const useRelationFieldPreview = ({
|
export const useRelationFieldPreview = ({
|
||||||
relationObjectMetadataId,
|
relationObjectMetadataId,
|
||||||
@ -21,24 +20,18 @@ export const useRelationFieldPreview = ({
|
|||||||
skip: skipDefaultValue || !relationObjectMetadataItem,
|
skip: skipDefaultValue || !relationObjectMetadataItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mainIdentifierMapper } = useObjectMainIdentifier(
|
const {
|
||||||
relationObjectMetadataItem,
|
labelIdentifierFieldPaths,
|
||||||
);
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
const recordMapper: MainIdentifierMapper | undefined =
|
imageIdentifierFormat,
|
||||||
relationObjectMetadataItem && mainIdentifierMapper
|
} = useObjectMainIdentifier(relationObjectMetadataItem);
|
||||||
? (record: { id: string }) => {
|
|
||||||
const mappedRecord = mainIdentifierMapper(record);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...mappedRecord,
|
|
||||||
name: mappedRecord.name || relationObjectMetadataItem.labelSingular,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultValue: relationObjects?.[0],
|
defaultValue: relationObjects?.[0],
|
||||||
recordMapper,
|
labelIdentifierFieldPaths,
|
||||||
|
imageIdentifierUrlField,
|
||||||
|
imageIdentifierUrlPrefix,
|
||||||
|
imageIdentifierFormat,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -74,6 +74,7 @@ export const BoardOptionsDropdownContent = ({
|
|||||||
hiddenBoardCardFieldsScopedSelector,
|
hiddenBoardCardFieldsScopedSelector,
|
||||||
BoardRecoilScopeContext,
|
BoardRecoilScopeContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasHiddenFields = hiddenBoardCardFields.length > 0;
|
const hasHiddenFields = hiddenBoardCardFields.length > 0;
|
||||||
const visibleBoardCardFields = useRecoilScopedValue(
|
const visibleBoardCardFields = useRecoilScopedValue(
|
||||||
visibleBoardCardFieldsScopedSelector,
|
visibleBoardCardFieldsScopedSelector,
|
||||||
|
|||||||
@ -65,11 +65,11 @@ export const EntityBoard = ({
|
|||||||
const { unselectAllActiveCards } = useCurrentCardSelected();
|
const { unselectAllActiveCards } = useCurrentCardSelected();
|
||||||
|
|
||||||
const updatePipelineProgressStageInDB = useCallback(
|
const updatePipelineProgressStageInDB = useCallback(
|
||||||
async (pipelineProgressId: string, pipelineStageId: string) => {
|
async (pipelineProgressId: string, pipelineStepId: string) => {
|
||||||
await updateOneOpportunity?.({
|
await updateOneOpportunity?.({
|
||||||
idToUpdate: pipelineProgressId,
|
idToUpdate: pipelineProgressId,
|
||||||
input: {
|
input: {
|
||||||
pipelineStepId: pipelineStageId,
|
pipelineStepId: pipelineStepId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export const EntityBoard = ({
|
|||||||
__typename: 'PipelineProgress',
|
__typename: 'PipelineProgress',
|
||||||
}),
|
}),
|
||||||
fields: {
|
fields: {
|
||||||
pipelineStageId: () => pipelineStageId,
|
pipelineStepId: () => pipelineStepId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { IconTrash } from '@/ui/display/icon';
|
import { IconTrash } from '@/ui/display/icon';
|
||||||
@ -5,19 +6,22 @@ import { useDeleteSelectedBoardCards } from '@/ui/layout/board/hooks/useDeleteSe
|
|||||||
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
|
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
|
||||||
|
|
||||||
export const useBoardActionBarEntries = () => {
|
export const useBoardActionBarEntries = () => {
|
||||||
const setActionBarEntries = useSetRecoilState(actionBarEntriesState);
|
const setActionBarEntriesRecoil = useSetRecoilState(actionBarEntriesState);
|
||||||
|
|
||||||
const deleteSelectedBoardCards = useDeleteSelectedBoardCards();
|
const deleteSelectedBoardCards = useDeleteSelectedBoardCards();
|
||||||
|
|
||||||
|
const setActionBarEntries = useCallback(() => {
|
||||||
|
setActionBarEntriesRecoil([
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
Icon: IconTrash,
|
||||||
|
accent: 'danger',
|
||||||
|
onClick: deleteSelectedBoardCards,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [deleteSelectedBoardCards, setActionBarEntriesRecoil]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setActionBarEntries: () =>
|
setActionBarEntries,
|
||||||
setActionBarEntries([
|
|
||||||
{
|
|
||||||
label: 'Delete',
|
|
||||||
Icon: IconTrash,
|
|
||||||
accent: 'danger',
|
|
||||||
onClick: deleteSelectedBoardCards,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export const useBoardColumns = () => {
|
|||||||
objectNameSingular: 'pipelineStep',
|
objectNameSingular: 'pipelineStep',
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedPipelineStages = (stages: BoardColumnDefinition[]) => {
|
const updatedPipelineSteps = (stages: BoardColumnDefinition[]) => {
|
||||||
if (!stages.length) return;
|
if (!stages.length) return;
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
@ -33,7 +33,7 @@ export const useBoardColumns = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const persistBoardColumns = async () => {
|
const persistBoardColumns = async () => {
|
||||||
await updatedPipelineStages(boardColumns);
|
await updatedPipelineSteps(boardColumns);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveBoardColumn = (
|
const handleMoveBoardColumn = (
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { IconTrash } from '@/ui/display/icon';
|
import { IconTrash } from '@/ui/display/icon';
|
||||||
@ -5,19 +6,24 @@ import { useDeleteSelectedBoardCards } from '@/ui/layout/board/hooks/useDeleteSe
|
|||||||
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
|
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
|
||||||
|
|
||||||
export const useBoardContextMenuEntries = () => {
|
export const useBoardContextMenuEntries = () => {
|
||||||
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
|
const setContextMenuEntriesRecoil = useSetRecoilState(
|
||||||
|
contextMenuEntriesState,
|
||||||
|
);
|
||||||
|
|
||||||
const deleteSelectedBoardCards = useDeleteSelectedBoardCards();
|
const deleteSelectedBoardCards = useDeleteSelectedBoardCards();
|
||||||
|
|
||||||
|
const setContextMenuEntries = useCallback(() => {
|
||||||
|
setContextMenuEntriesRecoil([
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
Icon: IconTrash,
|
||||||
|
accent: 'danger',
|
||||||
|
onClick: deleteSelectedBoardCards,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [deleteSelectedBoardCards, setContextMenuEntriesRecoil]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setContextMenuEntries: () =>
|
setContextMenuEntries,
|
||||||
setContextMenuEntries([
|
|
||||||
{
|
|
||||||
label: 'Delete',
|
|
||||||
Icon: IconTrash,
|
|
||||||
accent: 'danger',
|
|
||||||
onClick: () => deleteSelectedBoardCards(),
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export const useDeleteSelectedBoardCards = () => {
|
|||||||
apolloClient.cache.evict({ id: `Opportunity:${id}` });
|
apolloClient.cache.evict({ id: `Opportunity:${id}` });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[apolloClient.cache, deleteOneOpportunity, removeCardIds],
|
[apolloClient.cache, removeCardIds, deleteOneOpportunity],
|
||||||
);
|
);
|
||||||
|
|
||||||
return deleteSelectedBoardCards;
|
return deleteSelectedBoardCards;
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import { atomFamily } from 'recoil';
|
|
||||||
|
|
||||||
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
|
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
|
||||||
|
|
||||||
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
|
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
|
||||||
|
|
||||||
export const availableBoardCardFieldsScopedState = atomFamily<
|
export const availableBoardCardFieldsScopedState = createScopedState<
|
||||||
BoardFieldDefinition<FieldMetadata>[],
|
BoardFieldDefinition<FieldMetadata>[]
|
||||||
string
|
|
||||||
>({
|
>({
|
||||||
key: 'availableBoardCardFieldsScopedState',
|
key: 'availableBoardCardFieldsScopedState',
|
||||||
default: [],
|
defaultValue: [],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,20 +9,21 @@ import { boardCardIdsByColumnIdFamilyState } from '../boardCardIdsByColumnIdFami
|
|||||||
export const boardColumnTotalsFamilySelector = selectorFamily({
|
export const boardColumnTotalsFamilySelector = selectorFamily({
|
||||||
key: 'boardColumnTotalsFamilySelector',
|
key: 'boardColumnTotalsFamilySelector',
|
||||||
get:
|
get:
|
||||||
(pipelineStageId: string) =>
|
(pipelineStepId: string) =>
|
||||||
({ get }) => {
|
({ get }) => {
|
||||||
const cardIds = get(boardCardIdsByColumnIdFamilyState(pipelineStageId));
|
const cardIds = get(boardCardIdsByColumnIdFamilyState(pipelineStepId));
|
||||||
|
|
||||||
const pipelineProgresses = cardIds.map((pipelineProgressId: string) =>
|
const opportunities = cardIds.map((opportunityId: string) =>
|
||||||
get(companyProgressesFamilyState(pipelineProgressId)),
|
get(companyProgressesFamilyState(opportunityId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const pipelineStageTotal: number =
|
const pipelineStepTotal: number =
|
||||||
pipelineProgresses?.reduce(
|
opportunities?.reduce(
|
||||||
(acc: number, curr: any) => acc + curr?.pipelineProgress.amount,
|
(acc: number, curr: any) =>
|
||||||
|
acc + curr?.opportunity.amount.amountMicros / 1000000,
|
||||||
0,
|
0,
|
||||||
) || 0;
|
) || 0;
|
||||||
|
|
||||||
return pipelineStageTotal;
|
return pipelineStepTotal;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export const hiddenBoardCardFieldsScopedSelector = selectorFamily({
|
|||||||
const fields = get(boardCardFieldsScopedState(scopeId));
|
const fields = get(boardCardFieldsScopedState(scopeId));
|
||||||
const fieldKeys = fields.map(({ fieldMetadataId }) => fieldMetadataId);
|
const fieldKeys = fields.map(({ fieldMetadataId }) => fieldMetadataId);
|
||||||
const otherAvailableKeys = get(
|
const otherAvailableKeys = get(
|
||||||
availableBoardCardFieldsScopedState(scopeId),
|
availableBoardCardFieldsScopedState({ scopeId }),
|
||||||
).filter(({ fieldMetadataId }) => !fieldKeys.includes(fieldMetadataId));
|
).filter(({ fieldMetadataId }) => !fieldKeys.includes(fieldMetadataId));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -5,19 +5,20 @@ import { useRelationField } from '../../hooks/useRelationField';
|
|||||||
export const RelationFieldDisplay = () => {
|
export const RelationFieldDisplay = () => {
|
||||||
const { fieldValue, fieldDefinition } = useRelationField();
|
const { fieldValue, fieldDefinition } = useRelationField();
|
||||||
|
|
||||||
|
const { mapToObjectIdentifiers } = useRelationField();
|
||||||
|
|
||||||
if (!fieldValue || !fieldDefinition) {
|
if (!fieldValue || !fieldDefinition) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainIdentifierMapped =
|
const objectIdentifiers = mapToObjectIdentifiers(fieldValue);
|
||||||
fieldDefinition.metadata.mainIdentifierMapper(fieldValue);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityChip
|
<EntityChip
|
||||||
entityId={fieldValue.id}
|
entityId={fieldValue.id}
|
||||||
name={mainIdentifierMapped.name}
|
name={objectIdentifiers.name}
|
||||||
avatarUrl={mainIdentifierMapped.avatarUrl}
|
avatarUrl={objectIdentifiers.avatarUrl}
|
||||||
avatarType={mainIdentifierMapped.avatarType}
|
avatarType={objectIdentifiers.avatarType}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,11 +30,40 @@ export const useRelationField = () => {
|
|||||||
|
|
||||||
const initialValue = fieldInitialValue?.isEmpty ? null : fieldValue;
|
const initialValue = fieldInitialValue?.isEmpty ? null : fieldValue;
|
||||||
|
|
||||||
|
const mapToObjectIdentifiers = (record: any) => {
|
||||||
|
let name = '';
|
||||||
|
for (const fieldPath of fieldDefinition.metadata
|
||||||
|
.labelIdentifierFieldPaths) {
|
||||||
|
const fieldPathParts = fieldPath.split('.');
|
||||||
|
|
||||||
|
if (fieldPathParts.length === 1) {
|
||||||
|
name += record[fieldPathParts[0]];
|
||||||
|
} else if (fieldPathParts.length === 2) {
|
||||||
|
name += record[fieldPathParts[0]][fieldPathParts[1]];
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid field path ${fieldPath}. Relation picker only supports field paths with 1 or 2 parts.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: record.id,
|
||||||
|
name: record[name],
|
||||||
|
avatarUrl:
|
||||||
|
fieldDefinition.metadata.imageIdentifierUrlPrefix +
|
||||||
|
record[fieldDefinition.metadata.imageIdentifierUrlField],
|
||||||
|
avatarType: fieldDefinition.metadata.imageIdentifierFormat,
|
||||||
|
record: record,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
fieldValue,
|
fieldValue,
|
||||||
initialValue,
|
initialValue,
|
||||||
initialSearchValue,
|
initialSearchValue,
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
|
mapToObjectIdentifiers,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { IconUserCircle } from '@/ui/display/icon';
|
|||||||
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||||
|
import { useRelationField } from '@/ui/object/field/meta-types/hooks/useRelationField';
|
||||||
import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition';
|
import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition';
|
||||||
import { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
@ -41,6 +42,8 @@ export const RelationPicker = ({
|
|||||||
|
|
||||||
const useFindManyQuery = (options: any) => useQuery(findManyQuery, options);
|
const useFindManyQuery = (options: any) => useQuery(findManyQuery, options);
|
||||||
|
|
||||||
|
const { mapToObjectIdentifiers } = useRelationField();
|
||||||
|
|
||||||
const workspaceMembers = useFilteredSearchEntityQuery({
|
const workspaceMembers = useFilteredSearchEntityQuery({
|
||||||
queryHook: useFindManyQuery,
|
queryHook: useFindManyQuery,
|
||||||
filters: [
|
filters: [
|
||||||
@ -50,7 +53,7 @@ export const RelationPicker = ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
orderByField: 'createdAt',
|
orderByField: 'createdAt',
|
||||||
mappingFunction: fieldDefinition.metadata.mainIdentifierMapper,
|
mappingFunction: mapToObjectIdentifiers,
|
||||||
selectedIds: recordId ? [recordId] : [],
|
selectedIds: recordId ? [recordId] : [],
|
||||||
objectNamePlural: fieldDefinition.metadata.objectMetadataNamePlural,
|
objectNamePlural: fieldDefinition.metadata.objectMetadataNamePlural,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||||
import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper';
|
|
||||||
|
|
||||||
export type FieldUuidMetadata = {
|
export type FieldUuidMetadata = {
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
@ -65,7 +64,10 @@ export type FieldRelationMetadata = {
|
|||||||
fieldName: string;
|
fieldName: string;
|
||||||
useEditButton?: boolean;
|
useEditButton?: boolean;
|
||||||
relationType?: FieldDefinitionRelationType;
|
relationType?: FieldDefinitionRelationType;
|
||||||
mainIdentifierMapper: MainIdentifierMapper;
|
labelIdentifierFieldPaths: string[];
|
||||||
|
imageIdentifierUrlField: string;
|
||||||
|
imageIdentifierUrlPrefix: string;
|
||||||
|
imageIdentifierFormat: 'squared' | 'rounded';
|
||||||
searchFields: string[];
|
searchFields: string[];
|
||||||
objectMetadataNameSingular: string;
|
objectMetadataNameSingular: string;
|
||||||
objectMetadataNamePlural: string;
|
objectMetadataNamePlural: string;
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import { AvatarType } from '@/users/components/Avatar';
|
|
||||||
|
|
||||||
export type MainIdentifierMapper = (record: any) => {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
avatarUrl?: string;
|
|
||||||
avatarType: AvatarType;
|
|
||||||
record: any;
|
|
||||||
};
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper';
|
|
||||||
|
|
||||||
export type FieldDefinitionRelationType =
|
|
||||||
| 'FROM_MANY_OBJECTS'
|
|
||||||
| 'FROM_ONE_OBJECT'
|
|
||||||
| 'TO_MANY_OBJECTS'
|
|
||||||
| 'TO_ONE_OBJECT';
|
|
||||||
|
|
||||||
export type RelationFieldConfig = {
|
|
||||||
relationType?: FieldDefinitionRelationType;
|
|
||||||
mainIdentifierMapper: MainIdentifierMapper;
|
|
||||||
searchFields: string[];
|
|
||||||
objectMetadataNameSingular: string;
|
|
||||||
};
|
|
||||||
@ -63,7 +63,6 @@ export const RecordTableCell = ({ cellIndex }: { cellIndex: number }) => {
|
|||||||
isMainIdentifier:
|
isMainIdentifier:
|
||||||
columnDefinition.fieldMetadataId ===
|
columnDefinition.fieldMetadataId ===
|
||||||
objectMetadataConfig?.mainIdentifierFieldMetadataId,
|
objectMetadataConfig?.mainIdentifierFieldMetadataId,
|
||||||
mainIdentifierMapper: objectMetadataConfig?.mainIdentifierMapper,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableCell customHotkeyScope={{ scope: customHotkeyScope }} />
|
<TableCell customHotkeyScope={{ scope: customHotkeyScope }} />
|
||||||
|
|||||||
@ -2,10 +2,9 @@ import { AvatarType } from '@/users/components/Avatar';
|
|||||||
|
|
||||||
export type ObjectMetadataConfig = {
|
export type ObjectMetadataConfig = {
|
||||||
mainIdentifierFieldMetadataId: string;
|
mainIdentifierFieldMetadataId: string;
|
||||||
mainIdentifierMapper: (record: any) => {
|
labelIdentifierFieldPaths: string[];
|
||||||
name: string;
|
imageIdentifierUrlField: string;
|
||||||
avatarUrl?: string;
|
imageIdentifierUrlPrefix: string;
|
||||||
avatarType: AvatarType;
|
imageIdentifierFormat: AvatarType;
|
||||||
};
|
|
||||||
basePathToShowPage: string;
|
basePathToShowPage: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope-
|
|||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
||||||
import { PipelineAddButton } from '@/pipeline/components/PipelineAddButton';
|
import { PipelineAddButton } from '@/pipeline/components/PipelineAddButton';
|
||||||
import { usePipelineStages } from '@/pipeline/hooks/usePipelineStages';
|
import { usePipelineSteps } from '@/pipeline/hooks/usePipelineSteps';
|
||||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||||
import { IconTargetArrow } from '@/ui/display/icon';
|
import { IconTargetArrow } from '@/ui/display/icon';
|
||||||
import { BoardOptionsContext } from '@/ui/layout/board/contexts/BoardOptionsContext';
|
import { BoardOptionsContext } from '@/ui/layout/board/contexts/BoardOptionsContext';
|
||||||
@ -23,8 +23,8 @@ const StyledBoardContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const Opportunities = () => {
|
export const Opportunities = () => {
|
||||||
const { handlePipelineStageAdd, handlePipelineStageDelete } =
|
const { handlePipelineStepAdd, handlePipelineStepDelete } =
|
||||||
usePipelineStages();
|
usePipelineSteps();
|
||||||
|
|
||||||
const { updateOneObject: updateOnePipelineStep } =
|
const { updateOneObject: updateOnePipelineStep } =
|
||||||
useUpdateOneObjectRecord<PipelineStep>({
|
useUpdateOneObjectRecord<PipelineStep>({
|
||||||
@ -68,8 +68,8 @@ export const Opportunities = () => {
|
|||||||
<CompanyBoardRecoilScopeContext.Provider value="opportunities">
|
<CompanyBoardRecoilScopeContext.Provider value="opportunities">
|
||||||
<StyledBoardContainer>
|
<StyledBoardContainer>
|
||||||
<CompanyBoard
|
<CompanyBoard
|
||||||
onColumnAdd={handlePipelineStageAdd}
|
onColumnAdd={handlePipelineStepAdd}
|
||||||
onColumnDelete={handlePipelineStageDelete}
|
onColumnDelete={handlePipelineStepDelete}
|
||||||
onEditColumnTitle={handleEditColumnTitle}
|
onEditColumnTitle={handleEditColumnTitle}
|
||||||
/>
|
/>
|
||||||
</StyledBoardContainer>
|
</StyledBoardContainer>
|
||||||
|
|||||||
@ -43,17 +43,17 @@ export const AddCompanyFromHeader: Story = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await step('Change pipeline stage', async () => {
|
await step('Change pipeline stage', async () => {
|
||||||
const pipelineStageDropdownHeader = await canvas.findByRole(
|
const pipelineStepDropdownHeader = await canvas.findByRole(
|
||||||
'listitem',
|
'listitem',
|
||||||
{ name: (_, element) => !!element?.textContent?.includes('New') },
|
{ name: (_, element) => !!element?.textContent?.includes('New') },
|
||||||
{ timeout: 1000 },
|
{ timeout: 1000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const pipelineStageDropdownUnfoldButton = within(
|
const pipelineStepDropdownUnfoldButton = within(
|
||||||
pipelineStageDropdownHeader,
|
pipelineStepDropdownHeader,
|
||||||
).getByRole('button');
|
).getByRole('button');
|
||||||
|
|
||||||
await userEvent.click(pipelineStageDropdownUnfoldButton);
|
await userEvent.click(pipelineStepDropdownUnfoldButton);
|
||||||
|
|
||||||
const menuItem1 = await canvas.findByRole(
|
const menuItem1 = await canvas.findByRole(
|
||||||
'listitem',
|
'listitem',
|
||||||
|
|||||||
Reference in New Issue
Block a user