From 09533e286bd3045c46536932d8199d84f8c52a8a Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 21 Nov 2023 01:24:25 +0100 Subject: [PATCH] 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 Co-authored-by: bosiraphael --- .../companies/components/CompanyBoardCard.tsx | 4 +- .../components/CompanyProgressPicker.tsx | 38 ++++---- .../components/HooksCompanyBoardEffect.tsx | 95 +++++++++++++++---- .../components/NewCompanyProgressButton.tsx | 6 +- .../companies/hooks/useUpdateBoardCardIds.ts | 4 +- .../hooks/useUpdateCompanyBoardColumns.ts | 47 +++++---- .../companies/types/CompanyProgress.ts | 3 +- .../hooks/useObjectMainIdentifier.ts | 62 +++++------- ...rmatFieldMetadataItemAsColumnDefinition.ts | 61 ++++++------ .../components/RecordTableEffect.tsx | 21 ++-- .../hooks/useDeleteOneObjectRecord.ts | 34 ++++--- .../utils/filterAvailableTableColumns.ts | 7 ++ .../pipeline/components/PipelineAddButton.tsx | 6 +- ...ePipelineStages.ts => usePipelineSteps.ts} | 8 +- .../src/modules/pipeline/types/Opportunity.ts | 2 +- .../components/SettingsObjectFieldPreview.tsx | 22 ++++- .../hooks/useRelationFieldPreview.ts | 27 ++---- .../BoardOptionsDropdownContent.tsx | 1 + .../layout/board/components/EntityBoard.tsx | 6 +- .../board/hooks/useBoardActionBarEntries.tsx | 24 +++-- .../ui/layout/board/hooks/useBoardColumns.ts | 4 +- .../hooks/useBoardContextMenuEntries.tsx | 26 +++-- .../hooks/useDeleteSelectedBoardCards.ts | 2 +- .../availableBoardCardFieldsScopedState.ts | 10 +- .../boardColumnTotalsFamilySelector.ts | 17 ++-- .../hiddenBoardCardFieldsScopedSelector.ts | 2 +- .../components/RelationFieldDisplay.tsx | 11 ++- .../meta-types/hooks/useRelationField.ts | 29 ++++++ .../components/internal/RelationPicker.tsx | 5 +- .../ui/object/field/types/FieldMetadata.ts | 6 +- .../field/types/MainIdentifierMapper.ts | 9 -- .../object/field/types/RelationFieldConfig.ts | 14 --- .../components/RecordTableCell.tsx | 1 - .../types/ObjectMetadataConfig.ts | 9 +- .../src/pages/opportunities/Opportunities.tsx | 10 +- .../__stories__/Opportunities.stories.tsx | 8 +- 36 files changed, 364 insertions(+), 277 deletions(-) rename front/src/modules/pipeline/hooks/{usePipelineStages.ts => usePipelineSteps.ts} (83%) delete mode 100644 front/src/modules/ui/object/field/types/MainIdentifierMapper.ts delete mode 100644 front/src/modules/ui/object/field/types/RelationFieldConfig.ts diff --git a/front/src/modules/companies/components/CompanyBoardCard.tsx b/front/src/modules/companies/components/CompanyBoardCard.tsx index 95a0e911b..1e5f91f32 100644 --- a/front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/front/src/modules/companies/components/CompanyBoardCard.tsx @@ -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; } diff --git a/front/src/modules/companies/components/CompanyProgressPicker.tsx b/front/src/modules/companies/components/CompanyProgressPicker.tsx index bf70bc294..e0d06e306 100644 --- a/front/src/modules/companies/components/CompanyProgressPicker.tsx +++ b/front/src/modules/companies/components/CompanyProgressPicker.tsx @@ -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 ? ( - {currentPipelineStages.map((pipelineStage: any, index: number) => ( + {currentPipelineSteps.map((pipelineStep: any, index: number) => ( { - handlePipelineStageChange(pipelineStage.id); + handlePipelineStepChange(pipelineStep.id); }} - text={pipelineStage.name} + text={pipelineStep.name} /> ))} @@ -100,7 +100,7 @@ export const CompanyProgressPicker = ({ EndIcon={IconChevronDown} onClick={() => setIsProgressSelectionUnfolded(true)} > - {selectedPipelineStage?.name} + {selectedPipelineStep?.name} { 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) => { - const pipelineProgresses: Array = []; + (data: PaginatedObjectTypeResults) => { + const pipelineProgresses: Array = 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(() => { diff --git a/front/src/modules/companies/components/NewCompanyProgressButton.tsx b/front/src/modules/companies/components/NewCompanyProgressButton.tsx index 2f3e386f1..2d69f0c2e 100644 --- a/front/src/modules/companies/components/NewCompanyProgressButton.tsx +++ b/front/src/modules/companies/components/NewCompanyProgressButton.tsx @@ -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(() => { diff --git a/front/src/modules/companies/hooks/useUpdateBoardCardIds.ts b/front/src/modules/companies/hooks/useUpdateBoardCardIds.ts index 332812581..97b50edd5 100644 --- a/front/src/modules/companies/hooks/useUpdateBoardCardIds.ts +++ b/front/src/modules/companies/hooks/useUpdateBoardCardIds.ts @@ -7,7 +7,7 @@ import { boardColumnsState } from '@/ui/layout/board/states/boardColumnsState'; export const useUpdateCompanyBoardCardIds = () => useRecoilCallback( ({ snapshot, set }) => - (pipelineProgresses: Opportunity[]) => { + (pipelineProgresses: Pick[]) => { 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); diff --git a/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts b/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts index 9e74ee755..7bb049049 100644 --- a/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts +++ b/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts @@ -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)) diff --git a/front/src/modules/companies/types/CompanyProgress.ts b/front/src/modules/companies/types/CompanyProgress.ts index 6791fca10..ab797dca2 100644 --- a/front/src/modules/companies/types/CompanyProgress.ts +++ b/front/src/modules/companies/types/CompanyProgress.ts @@ -2,11 +2,10 @@ import { Company } from '@/companies/types/Company'; import { Opportunity } from '@/pipeline/types/Opportunity'; export type CompanyForBoard = Pick; -export type PipelineProgressForBoard = Opportunity; export type CompanyProgress = { company: CompanyForBoard; - pipelineProgress: PipelineProgressForBoard; + opportunity: Opportunity; }; export type CompanyProgressDict = { diff --git a/front/src/modules/object-metadata/hooks/useObjectMainIdentifier.ts b/front/src/modules/object-metadata/hooks/useObjectMainIdentifier.ts index 1717361b4..dd4121f51 100644 --- a/front/src/modules/object-metadata/hooks/useObjectMainIdentifier.ts +++ b/front/src/modules/object-metadata/hooks/useObjectMainIdentifier.ts @@ -1,47 +1,34 @@ 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'; export const useObjectMainIdentifier = ( objectMetadataItem?: Nullable, ) => { if (!objectMetadataItem) { - return { - mainIdentifierMapper: undefined, - mainIdentifierFieldMetadataId: undefined, - basePathToShowPage: undefined, - }; + return {}; } - const mainIdentifierMapper: MainIdentifierMapper = (record: any) => { - if (objectMetadataItem.nameSingular === 'company') { - return { - id: record.id, - name: record.name, - avatarUrl: record.avatarUrl, - avatarType: 'squared', - record: record, - }; - } - - if (objectMetadataItem.nameSingular === 'workspaceMember') { - return { - id: record.id, - name: record.name.firstName + ' ' + record.name.lastName, - avatarUrl: record.avatarUrl, - avatarType: 'rounded', - record: record, - }; - } - - return { - id: record.id, - name: record.name, - avatarUrl: record.avatarUrl, - avatarType: 'rounded', - record: record, - }; - }; + const labelIdentifierFieldPaths = ['person', 'workspaceMember'].includes( + objectMetadataItem.nameSingular, + ) + ? ['name.firstName', 'name.lastName'] + : ['name']; + const imageIdentifierFormat: AvatarType = ['company'].includes( + objectMetadataItem.nameSingular, + ) + ? 'squared' + : 'rounded'; + const imageIdentifierUrlPrefix = ['company'].includes( + objectMetadataItem.nameSingular, + ) + ? 'https://favicon.twenty.com/' + : ''; + const imageIdentifierUrlField = ['company'].includes( + objectMetadataItem.nameSingular, + ) + ? 'domainName' + : 'avatarUrl'; const mainIdentifierFieldMetadataId = objectMetadataItem.fields.find( ({ name }) => name === 'name', @@ -50,7 +37,10 @@ export const useObjectMainIdentifier = ( const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`; return { - mainIdentifierMapper, + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, mainIdentifierFieldMetadataId, basePathToShowPage, }; diff --git a/front/src/modules/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition.ts b/front/src/modules/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition.ts index a7d18f2ae..d5ee3859b 100644 --- a/front/src/modules/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition.ts +++ b/front/src/modules/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition.ts @@ -1,8 +1,7 @@ import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType'; 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 { getLogoUrlFromDomainName } from '~/utils'; +import { AvatarType } from '@/users/components/Avatar'; import { FieldMetadataItem } from '../types/FieldMetadataItem'; @@ -18,34 +17,26 @@ export const formatFieldMetadataItemAsColumnDefinition = ({ const relationObjectMetadataItem = field.toRelationMetadata?.fromObjectMetadata; - const mainIdentifierMapper: MainIdentifierMapper = (record: any) => { - if (relationObjectMetadataItem?.nameSingular === 'company') { - return { - id: record.id, - name: record.name, - avatarUrl: getLogoUrlFromDomainName(record.domainName), - avatarType: 'squared', - record: record, - }; - } - if (relationObjectMetadataItem?.nameSingular === 'workspaceMember') { - return { - id: record.id, - name: record.name.firstName + ' ' + record.name.lastName, - avatarUrl: record.avatarUrl, - avatarType: 'rounded', - record: record, - }; - } - - return { - id: record.id, - name: record.name, - avatarUrl: record.avatarUrl, - avatarType: 'rounded', - record: record, - }; - }; + const labelIdentifierFieldPaths = ['person', 'workspaceMember'].includes( + relationObjectMetadataItem?.nameSingular ?? '', + ) + ? ['name.firstName', 'name.lastName'] + : ['name']; + const imageIdentifierFormat: AvatarType = ['company'].includes( + relationObjectMetadataItem?.nameSingular ?? '', + ) + ? 'squared' + : 'rounded'; + const imageIdentifierUrlPrefix = ['company'].includes( + relationObjectMetadataItem?.nameSingular ?? '', + ) + ? 'https://favicon.twenty.com/' + : ''; + const imageIdentifierUrlField = ['company'].includes( + relationObjectMetadataItem?.nameSingular ?? '', + ) + ? 'domainName' + : 'avatarUrl'; return { position, @@ -56,11 +47,15 @@ export const formatFieldMetadataItemAsColumnDefinition = ({ metadata: { fieldName: field.name, placeHolder: field.label, - mainIdentifierMapper: mainIdentifierMapper, + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, relationType: parseFieldRelationType(field), searchFields: ['name'], - objectMetadataNamePlural: relationObjectMetadataItem?.namePlural, - objectMetadataNameSingular: relationObjectMetadataItem?.nameSingular, + objectMetadataNamePlural: relationObjectMetadataItem?.namePlural ?? '', + objectMetadataNameSingular: + relationObjectMetadataItem?.nameSingular ?? '', }, iconName: field.icon ?? 'Icon123', isVisible: true, diff --git a/front/src/modules/object-record/components/RecordTableEffect.tsx b/front/src/modules/object-record/components/RecordTableEffect.tsx index ee5c768bb..da3f7db08 100644 --- a/front/src/modules/object-record/components/RecordTableEffect.tsx +++ b/front/src/modules/object-record/components/RecordTableEffect.tsx @@ -22,9 +22,12 @@ export const RecordTableEffect = () => { }); const { - mainIdentifierMapper, basePathToShowPage, mainIdentifierFieldMetadataId, + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, } = useObjectMainIdentifier(objectMetadataItem); const { columnDefinitions, filterDefinitions, sortDefinitions } = @@ -40,13 +43,12 @@ export const RecordTableEffect = () => { } = useView(); useEffect(() => { - if ( - mainIdentifierMapper && - basePathToShowPage && - mainIdentifierFieldMetadataId - ) { + if (basePathToShowPage && mainIdentifierFieldMetadataId) { setObjectMetadataConfig?.({ - mainIdentifierMapper, + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, basePathToShowPage, mainIdentifierFieldMetadataId, }); @@ -55,8 +57,11 @@ export const RecordTableEffect = () => { basePathToShowPage, objectMetadataItem, mainIdentifierFieldMetadataId, - mainIdentifierMapper, setObjectMetadataConfig, + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, ]); useEffect(() => { diff --git a/front/src/modules/object-record/hooks/useDeleteOneObjectRecord.ts b/front/src/modules/object-record/hooks/useDeleteOneObjectRecord.ts index 907b58e2f..0b9287518 100644 --- a/front/src/modules/object-record/hooks/useDeleteOneObjectRecord.ts +++ b/front/src/modules/object-record/hooks/useDeleteOneObjectRecord.ts @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { useMutation } from '@apollo/client'; import { getOperationName } from '@apollo/client/utilities'; @@ -20,20 +21,25 @@ export const useDeleteOneObjectRecord = ({ // TODO: type this with a minimal type at least with Record const [mutate] = useMutation(deleteOneMutation); - 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; + const deleteOneObject = useCallback( + async (idToDelete: string) => { + if (objectNameSingular && foundObjectMetadataItem) { + const deletedObject = await mutate({ + variables: { + idToDelete, + }, + refetchQueries: [getOperationName(findManyQuery) ?? ''], + }); + + return deletedObject.data[ + `create${capitalize(objectNameSingular)}` + ] as T; + } + + return null; + }, + [foundObjectMetadataItem, mutate, objectNameSingular, findManyQuery], + ); return { deleteOneObject, diff --git a/front/src/modules/object-record/utils/filterAvailableTableColumns.ts b/front/src/modules/object-record/utils/filterAvailableTableColumns.ts index a082bcb76..16ed62771 100644 --- a/front/src/modules/object-record/utils/filterAvailableTableColumns.ts +++ b/front/src/modules/object-record/utils/filterAvailableTableColumns.ts @@ -16,5 +16,12 @@ export const filterAvailableTableColumns = ( return false; } + if ( + isFieldRelation(columnDefinition) && + columnDefinition.metadata?.fieldName === 'pipelineStep' + ) { + return false; + } + return true; }; diff --git a/front/src/modules/pipeline/components/PipelineAddButton.tsx b/front/src/modules/pipeline/components/PipelineAddButton.tsx index e0be2cee2..4334230b6 100644 --- a/front/src/modules/pipeline/components/PipelineAddButton.tsx +++ b/front/src/modules/pipeline/components/PipelineAddButton.tsx @@ -19,7 +19,7 @@ export const PipelineAddButton = () => { const handleCompanySelected = ( selectedCompany: EntityForSelect | null, - selectedPipelineStageId: string | null, + selectedPipelineStepId: string | null, ) => { if (!selectedCompany?.id) { enqueueSnackBar( @@ -33,7 +33,7 @@ export const PipelineAddButton = () => { return; } - if (!selectedPipelineStageId) { + if (!selectedPipelineStepId) { enqueueSnackBar( 'There was a problem with the pipeline stage selection, please retry.', { @@ -45,7 +45,7 @@ export const PipelineAddButton = () => { return; } closeDropdown(); - //createCompanyProgress(selectedCompany.id, selectedPipelineStageId); + //createCompanyProgress(selectedCompany.id, selectedPipelineStepId); }; return ( diff --git a/front/src/modules/pipeline/hooks/usePipelineStages.ts b/front/src/modules/pipeline/hooks/usePipelineSteps.ts similarity index 83% rename from front/src/modules/pipeline/hooks/usePipelineStages.ts rename to front/src/modules/pipeline/hooks/usePipelineSteps.ts index 032ec555c..39f0e6468 100644 --- a/front/src/modules/pipeline/hooks/usePipelineStages.ts +++ b/front/src/modules/pipeline/hooks/usePipelineSteps.ts @@ -7,7 +7,7 @@ import { BoardColumnDefinition } from '@/ui/layout/board/types/BoardColumnDefini import { currentPipelineState } from '../states/currentPipelineState'; -export const usePipelineStages = () => { +export const usePipelineSteps = () => { const currentPipeline = useRecoilValue(currentPipelineState); const { createOneObject: createOnePipelineStep } = @@ -20,7 +20,7 @@ export const usePipelineStages = () => { objectNameSingular: 'pipelineStep', }); - const handlePipelineStageAdd = async (boardColumn: BoardColumnDefinition) => { + const handlePipelineStepAdd = async (boardColumn: BoardColumnDefinition) => { if (!currentPipeline?.id) return; return createOnePipelineStep?.({ @@ -37,11 +37,11 @@ export const usePipelineStages = () => { }); }; - const handlePipelineStageDelete = async (boardColumnId: string) => { + const handlePipelineStepDelete = async (boardColumnId: string) => { if (!currentPipeline?.id) return; return deleteOnePipelineStep?.(boardColumnId); }; - return { handlePipelineStageAdd, handlePipelineStageDelete }; + return { handlePipelineStepAdd, handlePipelineStepDelete }; }; diff --git a/front/src/modules/pipeline/types/Opportunity.ts b/front/src/modules/pipeline/types/Opportunity.ts index fc841efab..3525841f8 100644 --- a/front/src/modules/pipeline/types/Opportunity.ts +++ b/front/src/modules/pipeline/types/Opportunity.ts @@ -2,6 +2,7 @@ import { Person } from '@/people/types/Person'; import { PipelineStep } from '@/pipeline/types/PipelineStep'; export type Opportunity = { + [key: string]: any; id: string; amount: { amountMicros: number; @@ -13,5 +14,4 @@ export type Opportunity = { pipelineStep: PipelineStep; pointOfContactId: string; pointOfContact: Pick; - [key: string]: any; }; diff --git a/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx b/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx index f91a08467..bd71de588 100644 --- a/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx +++ b/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx @@ -92,7 +92,10 @@ export const SettingsObjectFieldPreview = ({ const { defaultValue: relationDefaultValue, - recordMapper: mainIdentifierMapper, + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, } = useRelationFieldPreview({ relationObjectMetadataId, skipDefaultValue: @@ -104,6 +107,15 @@ export const SettingsObjectFieldPreview = ({ ? relationDefaultValue : dataTypes[fieldMetadata.type].defaultValue; + if ( + !labelIdentifierFieldPaths || + !imageIdentifierUrlField || + !imageIdentifierUrlPrefix || + !imageIdentifierFormat + ) { + return <>; + } + return ( @@ -146,7 +158,13 @@ export const SettingsObjectFieldPreview = ({ iconName: 'FieldIcon', fieldMetadataId: fieldMetadata.id || '', label: fieldMetadata.label, - metadata: { fieldName, mainIdentifierMapper }, + metadata: { + fieldName, + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, + }, }, hotkeyScope: 'field-preview', }} diff --git a/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts b/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts index dfd78d429..f0300587d 100644 --- a/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts +++ b/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts @@ -1,7 +1,6 @@ import { useObjectMainIdentifier } from '@/object-metadata/hooks/useObjectMainIdentifier'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; -import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper'; export const useRelationFieldPreview = ({ relationObjectMetadataId, @@ -21,24 +20,18 @@ export const useRelationFieldPreview = ({ skip: skipDefaultValue || !relationObjectMetadataItem, }); - const { mainIdentifierMapper } = useObjectMainIdentifier( - relationObjectMetadataItem, - ); - - const recordMapper: MainIdentifierMapper | undefined = - relationObjectMetadataItem && mainIdentifierMapper - ? (record: { id: string }) => { - const mappedRecord = mainIdentifierMapper(record); - - return { - ...mappedRecord, - name: mappedRecord.name || relationObjectMetadataItem.labelSingular, - }; - } - : undefined; + const { + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, + } = useObjectMainIdentifier(relationObjectMetadataItem); return { defaultValue: relationObjects?.[0], - recordMapper, + labelIdentifierFieldPaths, + imageIdentifierUrlField, + imageIdentifierUrlPrefix, + imageIdentifierFormat, }; }; diff --git a/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx b/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx index 1adb3af84..472862def 100644 --- a/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx +++ b/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx @@ -74,6 +74,7 @@ export const BoardOptionsDropdownContent = ({ hiddenBoardCardFieldsScopedSelector, BoardRecoilScopeContext, ); + const hasHiddenFields = hiddenBoardCardFields.length > 0; const visibleBoardCardFields = useRecoilScopedValue( visibleBoardCardFieldsScopedSelector, diff --git a/front/src/modules/ui/layout/board/components/EntityBoard.tsx b/front/src/modules/ui/layout/board/components/EntityBoard.tsx index f786d5e86..2178fdc49 100644 --- a/front/src/modules/ui/layout/board/components/EntityBoard.tsx +++ b/front/src/modules/ui/layout/board/components/EntityBoard.tsx @@ -65,11 +65,11 @@ export const EntityBoard = ({ const { unselectAllActiveCards } = useCurrentCardSelected(); const updatePipelineProgressStageInDB = useCallback( - async (pipelineProgressId: string, pipelineStageId: string) => { + async (pipelineProgressId: string, pipelineStepId: string) => { await updateOneOpportunity?.({ idToUpdate: pipelineProgressId, input: { - pipelineStepId: pipelineStageId, + pipelineStepId: pipelineStepId, }, }); @@ -80,7 +80,7 @@ export const EntityBoard = ({ __typename: 'PipelineProgress', }), fields: { - pipelineStageId: () => pipelineStageId, + pipelineStepId: () => pipelineStepId, }, }); }, diff --git a/front/src/modules/ui/layout/board/hooks/useBoardActionBarEntries.tsx b/front/src/modules/ui/layout/board/hooks/useBoardActionBarEntries.tsx index f3e377aa4..13e0acc37 100644 --- a/front/src/modules/ui/layout/board/hooks/useBoardActionBarEntries.tsx +++ b/front/src/modules/ui/layout/board/hooks/useBoardActionBarEntries.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { useSetRecoilState } from 'recoil'; 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'; export const useBoardActionBarEntries = () => { - const setActionBarEntries = useSetRecoilState(actionBarEntriesState); + const setActionBarEntriesRecoil = useSetRecoilState(actionBarEntriesState); const deleteSelectedBoardCards = useDeleteSelectedBoardCards(); + const setActionBarEntries = useCallback(() => { + setActionBarEntriesRecoil([ + { + label: 'Delete', + Icon: IconTrash, + accent: 'danger', + onClick: deleteSelectedBoardCards, + }, + ]); + }, [deleteSelectedBoardCards, setActionBarEntriesRecoil]); + return { - setActionBarEntries: () => - setActionBarEntries([ - { - label: 'Delete', - Icon: IconTrash, - accent: 'danger', - onClick: deleteSelectedBoardCards, - }, - ]), + setActionBarEntries, }; }; diff --git a/front/src/modules/ui/layout/board/hooks/useBoardColumns.ts b/front/src/modules/ui/layout/board/hooks/useBoardColumns.ts index 458a9967e..834d61d97 100644 --- a/front/src/modules/ui/layout/board/hooks/useBoardColumns.ts +++ b/front/src/modules/ui/layout/board/hooks/useBoardColumns.ts @@ -17,7 +17,7 @@ export const useBoardColumns = () => { objectNameSingular: 'pipelineStep', }); - const updatedPipelineStages = (stages: BoardColumnDefinition[]) => { + const updatedPipelineSteps = (stages: BoardColumnDefinition[]) => { if (!stages.length) return; return Promise.all( @@ -33,7 +33,7 @@ export const useBoardColumns = () => { }; const persistBoardColumns = async () => { - await updatedPipelineStages(boardColumns); + await updatedPipelineSteps(boardColumns); }; const handleMoveBoardColumn = ( diff --git a/front/src/modules/ui/layout/board/hooks/useBoardContextMenuEntries.tsx b/front/src/modules/ui/layout/board/hooks/useBoardContextMenuEntries.tsx index d51437057..806a8939f 100644 --- a/front/src/modules/ui/layout/board/hooks/useBoardContextMenuEntries.tsx +++ b/front/src/modules/ui/layout/board/hooks/useBoardContextMenuEntries.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { useSetRecoilState } from 'recoil'; 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'; export const useBoardContextMenuEntries = () => { - const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState); + const setContextMenuEntriesRecoil = useSetRecoilState( + contextMenuEntriesState, + ); const deleteSelectedBoardCards = useDeleteSelectedBoardCards(); + const setContextMenuEntries = useCallback(() => { + setContextMenuEntriesRecoil([ + { + label: 'Delete', + Icon: IconTrash, + accent: 'danger', + onClick: deleteSelectedBoardCards, + }, + ]); + }, [deleteSelectedBoardCards, setContextMenuEntriesRecoil]); + return { - setContextMenuEntries: () => - setContextMenuEntries([ - { - label: 'Delete', - Icon: IconTrash, - accent: 'danger', - onClick: () => deleteSelectedBoardCards(), - }, - ]), + setContextMenuEntries, }; }; diff --git a/front/src/modules/ui/layout/board/hooks/useDeleteSelectedBoardCards.ts b/front/src/modules/ui/layout/board/hooks/useDeleteSelectedBoardCards.ts index a0db15049..ad31e1ff7 100644 --- a/front/src/modules/ui/layout/board/hooks/useDeleteSelectedBoardCards.ts +++ b/front/src/modules/ui/layout/board/hooks/useDeleteSelectedBoardCards.ts @@ -34,7 +34,7 @@ export const useDeleteSelectedBoardCards = () => { apolloClient.cache.evict({ id: `Opportunity:${id}` }); }); }, - [apolloClient.cache, deleteOneOpportunity, removeCardIds], + [apolloClient.cache, removeCardIds, deleteOneOpportunity], ); return deleteSelectedBoardCards; diff --git a/front/src/modules/ui/layout/board/states/availableBoardCardFieldsScopedState.ts b/front/src/modules/ui/layout/board/states/availableBoardCardFieldsScopedState.ts index 3202a6a36..86205c5ce 100644 --- a/front/src/modules/ui/layout/board/states/availableBoardCardFieldsScopedState.ts +++ b/front/src/modules/ui/layout/board/states/availableBoardCardFieldsScopedState.ts @@ -1,13 +1,11 @@ -import { atomFamily } from 'recoil'; - import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata'; +import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; import { BoardFieldDefinition } from '../types/BoardFieldDefinition'; -export const availableBoardCardFieldsScopedState = atomFamily< - BoardFieldDefinition[], - string +export const availableBoardCardFieldsScopedState = createScopedState< + BoardFieldDefinition[] >({ key: 'availableBoardCardFieldsScopedState', - default: [], + defaultValue: [], }); diff --git a/front/src/modules/ui/layout/board/states/selectors/boardColumnTotalsFamilySelector.ts b/front/src/modules/ui/layout/board/states/selectors/boardColumnTotalsFamilySelector.ts index 5a70fef68..77aca5f72 100644 --- a/front/src/modules/ui/layout/board/states/selectors/boardColumnTotalsFamilySelector.ts +++ b/front/src/modules/ui/layout/board/states/selectors/boardColumnTotalsFamilySelector.ts @@ -9,20 +9,21 @@ import { boardCardIdsByColumnIdFamilyState } from '../boardCardIdsByColumnIdFami export const boardColumnTotalsFamilySelector = selectorFamily({ key: 'boardColumnTotalsFamilySelector', get: - (pipelineStageId: string) => + (pipelineStepId: string) => ({ get }) => { - const cardIds = get(boardCardIdsByColumnIdFamilyState(pipelineStageId)); + const cardIds = get(boardCardIdsByColumnIdFamilyState(pipelineStepId)); - const pipelineProgresses = cardIds.map((pipelineProgressId: string) => - get(companyProgressesFamilyState(pipelineProgressId)), + const opportunities = cardIds.map((opportunityId: string) => + get(companyProgressesFamilyState(opportunityId)), ); - const pipelineStageTotal: number = - pipelineProgresses?.reduce( - (acc: number, curr: any) => acc + curr?.pipelineProgress.amount, + const pipelineStepTotal: number = + opportunities?.reduce( + (acc: number, curr: any) => + acc + curr?.opportunity.amount.amountMicros / 1000000, 0, ) || 0; - return pipelineStageTotal; + return pipelineStepTotal; }, }); diff --git a/front/src/modules/ui/layout/board/states/selectors/hiddenBoardCardFieldsScopedSelector.ts b/front/src/modules/ui/layout/board/states/selectors/hiddenBoardCardFieldsScopedSelector.ts index 29c56cc4c..3dae2d8e2 100644 --- a/front/src/modules/ui/layout/board/states/selectors/hiddenBoardCardFieldsScopedSelector.ts +++ b/front/src/modules/ui/layout/board/states/selectors/hiddenBoardCardFieldsScopedSelector.ts @@ -11,7 +11,7 @@ export const hiddenBoardCardFieldsScopedSelector = selectorFamily({ const fields = get(boardCardFieldsScopedState(scopeId)); const fieldKeys = fields.map(({ fieldMetadataId }) => fieldMetadataId); const otherAvailableKeys = get( - availableBoardCardFieldsScopedState(scopeId), + availableBoardCardFieldsScopedState({ scopeId }), ).filter(({ fieldMetadataId }) => !fieldKeys.includes(fieldMetadataId)); return [ diff --git a/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx b/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx index 092fe0a2a..414f52e10 100644 --- a/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx +++ b/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx @@ -5,19 +5,20 @@ import { useRelationField } from '../../hooks/useRelationField'; export const RelationFieldDisplay = () => { const { fieldValue, fieldDefinition } = useRelationField(); + const { mapToObjectIdentifiers } = useRelationField(); + if (!fieldValue || !fieldDefinition) { return <>; } - const mainIdentifierMapped = - fieldDefinition.metadata.mainIdentifierMapper(fieldValue); + const objectIdentifiers = mapToObjectIdentifiers(fieldValue); return ( ); }; diff --git a/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts b/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts index a48f60a26..ea8b80ea5 100644 --- a/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts +++ b/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts @@ -30,11 +30,40 @@ export const useRelationField = () => { 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 { fieldDefinition, fieldValue, initialValue, initialSearchValue, setFieldValue, + mapToObjectIdentifiers, }; }; diff --git a/front/src/modules/ui/object/field/meta-types/input/components/internal/RelationPicker.tsx b/front/src/modules/ui/object/field/meta-types/input/components/internal/RelationPicker.tsx index e093ed8c6..716731d1d 100644 --- a/front/src/modules/ui/object/field/meta-types/input/components/internal/RelationPicker.tsx +++ b/front/src/modules/ui/object/field/meta-types/input/components/internal/RelationPicker.tsx @@ -7,6 +7,7 @@ import { IconUserCircle } from '@/ui/display/icon'; import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect'; import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState'; 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 { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; @@ -41,6 +42,8 @@ export const RelationPicker = ({ const useFindManyQuery = (options: any) => useQuery(findManyQuery, options); + const { mapToObjectIdentifiers } = useRelationField(); + const workspaceMembers = useFilteredSearchEntityQuery({ queryHook: useFindManyQuery, filters: [ @@ -50,7 +53,7 @@ export const RelationPicker = ({ }, ], orderByField: 'createdAt', - mappingFunction: fieldDefinition.metadata.mainIdentifierMapper, + mappingFunction: mapToObjectIdentifiers, selectedIds: recordId ? [recordId] : [], objectNamePlural: fieldDefinition.metadata.objectMetadataNamePlural, }); diff --git a/front/src/modules/ui/object/field/types/FieldMetadata.ts b/front/src/modules/ui/object/field/types/FieldMetadata.ts index 704849463..24559560c 100644 --- a/front/src/modules/ui/object/field/types/FieldMetadata.ts +++ b/front/src/modules/ui/object/field/types/FieldMetadata.ts @@ -1,5 +1,4 @@ import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; -import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper'; export type FieldUuidMetadata = { fieldName: string; @@ -65,7 +64,10 @@ export type FieldRelationMetadata = { fieldName: string; useEditButton?: boolean; relationType?: FieldDefinitionRelationType; - mainIdentifierMapper: MainIdentifierMapper; + labelIdentifierFieldPaths: string[]; + imageIdentifierUrlField: string; + imageIdentifierUrlPrefix: string; + imageIdentifierFormat: 'squared' | 'rounded'; searchFields: string[]; objectMetadataNameSingular: string; objectMetadataNamePlural: string; diff --git a/front/src/modules/ui/object/field/types/MainIdentifierMapper.ts b/front/src/modules/ui/object/field/types/MainIdentifierMapper.ts deleted file mode 100644 index 2b275de6b..000000000 --- a/front/src/modules/ui/object/field/types/MainIdentifierMapper.ts +++ /dev/null @@ -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; -}; diff --git a/front/src/modules/ui/object/field/types/RelationFieldConfig.ts b/front/src/modules/ui/object/field/types/RelationFieldConfig.ts deleted file mode 100644 index e2e32229c..000000000 --- a/front/src/modules/ui/object/field/types/RelationFieldConfig.ts +++ /dev/null @@ -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; -}; diff --git a/front/src/modules/ui/object/record-table/components/RecordTableCell.tsx b/front/src/modules/ui/object/record-table/components/RecordTableCell.tsx index 375b1ca7f..1181f2870 100644 --- a/front/src/modules/ui/object/record-table/components/RecordTableCell.tsx +++ b/front/src/modules/ui/object/record-table/components/RecordTableCell.tsx @@ -63,7 +63,6 @@ export const RecordTableCell = ({ cellIndex }: { cellIndex: number }) => { isMainIdentifier: columnDefinition.fieldMetadataId === objectMetadataConfig?.mainIdentifierFieldMetadataId, - mainIdentifierMapper: objectMetadataConfig?.mainIdentifierMapper, }} > diff --git a/front/src/modules/ui/object/record-table/types/ObjectMetadataConfig.ts b/front/src/modules/ui/object/record-table/types/ObjectMetadataConfig.ts index 41b7d5047..d12811ae7 100644 --- a/front/src/modules/ui/object/record-table/types/ObjectMetadataConfig.ts +++ b/front/src/modules/ui/object/record-table/types/ObjectMetadataConfig.ts @@ -2,10 +2,9 @@ import { AvatarType } from '@/users/components/Avatar'; export type ObjectMetadataConfig = { mainIdentifierFieldMetadataId: string; - mainIdentifierMapper: (record: any) => { - name: string; - avatarUrl?: string; - avatarType: AvatarType; - }; + labelIdentifierFieldPaths: string[]; + imageIdentifierUrlField: string; + imageIdentifierUrlPrefix: string; + imageIdentifierFormat: AvatarType; basePathToShowPage: string; }; diff --git a/front/src/pages/opportunities/Opportunities.tsx b/front/src/pages/opportunities/Opportunities.tsx index 251f64efc..54e77c9c1 100644 --- a/front/src/pages/opportunities/Opportunities.tsx +++ b/front/src/pages/opportunities/Opportunities.tsx @@ -6,7 +6,7 @@ import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope- import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord'; 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 { IconTargetArrow } from '@/ui/display/icon'; import { BoardOptionsContext } from '@/ui/layout/board/contexts/BoardOptionsContext'; @@ -23,8 +23,8 @@ const StyledBoardContainer = styled.div` `; export const Opportunities = () => { - const { handlePipelineStageAdd, handlePipelineStageDelete } = - usePipelineStages(); + const { handlePipelineStepAdd, handlePipelineStepDelete } = + usePipelineSteps(); const { updateOneObject: updateOnePipelineStep } = useUpdateOneObjectRecord({ @@ -68,8 +68,8 @@ export const Opportunities = () => { diff --git a/front/src/pages/opportunities/__stories__/Opportunities.stories.tsx b/front/src/pages/opportunities/__stories__/Opportunities.stories.tsx index 3e078ac67..d4342ca54 100644 --- a/front/src/pages/opportunities/__stories__/Opportunities.stories.tsx +++ b/front/src/pages/opportunities/__stories__/Opportunities.stories.tsx @@ -43,17 +43,17 @@ export const AddCompanyFromHeader: Story = { }); await step('Change pipeline stage', async () => { - const pipelineStageDropdownHeader = await canvas.findByRole( + const pipelineStepDropdownHeader = await canvas.findByRole( 'listitem', { name: (_, element) => !!element?.textContent?.includes('New') }, { timeout: 1000 }, ); - const pipelineStageDropdownUnfoldButton = within( - pipelineStageDropdownHeader, + const pipelineStepDropdownUnfoldButton = within( + pipelineStepDropdownHeader, ).getByRole('button'); - await userEvent.click(pipelineStageDropdownUnfoldButton); + await userEvent.click(pipelineStepDropdownUnfoldButton); const menuItem1 = await canvas.findByRole( 'listitem',