Introduce main identifier to power RelationFieldDisplay (#2577)

* Introduce main identifier to power RelationFieldDisplay, FilterDrodown, TableFirstColumn

* Apply to RelationPicker
This commit is contained in:
Charles Bochet
2023-11-20 10:33:36 +01:00
committed by GitHub
parent 18ee95179e
commit 25950ab82a
75 changed files with 412 additions and 717 deletions

View File

@ -36,7 +36,7 @@ export const ActivityTargetChips = ({
key={company.id} key={company.id}
id={company.id} id={company.id}
name={company.name} name={company.name}
pictureUrl={getLogoUrlFromDomainName(company.domainName)} avatarUrl={getLogoUrlFromDomainName(company.domainName)}
/> />
); );
} }
@ -46,7 +46,7 @@ export const ActivityTargetChips = ({
key={person.id} key={person.id}
id={person.id} id={person.id}
name={person.name.firstName + ' ' + person.name.lastName} name={person.name.firstName + ' ' + person.name.lastName}
pictureUrl={person.avatarUrl ?? undefined} avatarUrl={person.avatarUrl ?? undefined}
/> />
); );
} }

View File

@ -4,10 +4,6 @@ import styled from '@emotion/styled';
import { useHandleCheckableActivityTargetChange } from '@/activities/hooks/useHandleCheckableActivityTargetChange'; import { useHandleCheckableActivityTargetChange } from '@/activities/hooks/useHandleCheckableActivityTargetChange';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '@/activities/utils/flatMapAndSortEntityForSelectArrayByName';
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
import { useFilteredSearchPeopleQuery } from '@/people/hooks/useFilteredSearchPeopleQuery';
import { MultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
import { useInlineCell } from '@/ui/object/record-inline-cell/hooks/useInlineCell'; import { useInlineCell } from '@/ui/object/record-inline-cell/hooks/useInlineCell';
import { assertNotNull } from '~/utils/assert'; import { assertNotNull } from '~/utils/assert';
@ -60,31 +56,31 @@ export const ActivityRelationEditableFieldEditMode = ({
Record<string, boolean> Record<string, boolean>
>(initialSelectedEntityIds); >(initialSelectedEntityIds);
const personsForMultiSelect = useFilteredSearchPeopleQuery({ // const personsForMultiSelect = useFilteredSearchPeopleQuery({
searchFilter, // searchFilter,
selectedIds: initialPeopleIds, // selectedIds: initialPeopleIds,
}); // });
const companiesForMultiSelect = useFilteredSearchCompanyQuery({ // const companiesForMultiSelect = useFilteredSearchCompanyQuery({
searchFilter, // searchFilter,
selectedIds: initialCompanyIds, // selectedIds: initialCompanyIds,
}); // });
const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([ // const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.selectedEntities, // personsForMultiSelect.selectedEntities,
companiesForMultiSelect.selectedEntities, // companiesForMultiSelect.selectedEntities,
]); // ]);
const filteredSelectedEntities = // const filteredSelectedEntities =
flatMapAndSortEntityForSelectArrayOfArrayByName([ // flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.filteredSelectedEntities, // personsForMultiSelect.filteredSelectedEntities,
companiesForMultiSelect.filteredSelectedEntities, // companiesForMultiSelect.filteredSelectedEntities,
]); // ]);
const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([ // const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.entitiesToSelect, // personsForMultiSelect.entitiesToSelect,
companiesForMultiSelect.entitiesToSelect, // companiesForMultiSelect.entitiesToSelect,
]); // ]);
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({ const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
activity, activity,
@ -102,7 +98,7 @@ export const ActivityRelationEditableFieldEditMode = ({
return ( return (
<StyledSelectContainer> <StyledSelectContainer>
<MultipleEntitySelect {/* <MultipleEntitySelect
entities={{ entities={{
entitiesToSelect, entitiesToSelect,
filteredSelectedEntities, filteredSelectedEntities,
@ -115,7 +111,7 @@ export const ActivityRelationEditableFieldEditMode = ({
value={selectedEntityIds} value={selectedEntityIds}
onCancel={handleCancel} onCancel={handleCancel}
onSubmit={handleSubmit} onSubmit={handleSubmit}
/> /> */}
</StyledSelectContainer> </StyledSelectContainer>
); );
}; };

View File

@ -49,7 +49,7 @@ export const TimelineActivityCardFooter = ({
' ' + ' ' +
activity.assignee.name.lastName ?? '' activity.assignee.name.lastName ?? ''
} }
pictureUrl={activity.assignee.avatarUrl ?? ''} avatarUrl={activity.assignee.avatarUrl ?? ''}
/> />
)} )}

View File

@ -17,7 +17,7 @@ export const SmallName: Story = {
args: { args: {
id: 'airbnb', id: 'airbnb',
name: 'Airbnb', name: 'Airbnb',
pictureUrl: 'https://api.faviconkit.com/airbnb.com/144', avatarUrl: 'https://api.faviconkit.com/airbnb.com/144',
}, },
}; };
@ -25,6 +25,6 @@ export const BigName: Story = {
args: { args: {
id: 'google', id: 'google',
name: 'Google with a real big name to overflow the cell', name: 'Google with a real big name to overflow the cell',
pictureUrl: 'https://api.faviconkit.com/google.com/144', avatarUrl: 'https://api.faviconkit.com/google.com/144',
}, },
}; };

View File

@ -3,10 +3,6 @@ import styled from '@emotion/styled';
import { flip, offset, useFloating } from '@floating-ui/react'; import { flip, offset, useFloating } from '@floating-ui/react';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import {
PeoplePicker,
PersonForSelect,
} from '@/people/components/PeoplePicker';
import { IconPlus } from '@/ui/display/icon'; import { IconPlus } from '@/ui/display/icon';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
@ -67,7 +63,7 @@ export const AddPersonToCompany = ({
} = usePreviousHotkeyScope(); } = usePreviousHotkeyScope();
const handlePersonSelected = const handlePersonSelected =
(companyId: string) => async (newPerson: PersonForSelect | null) => { (companyId: string) => async (newPerson: any | null) => {
if (newPerson) { if (newPerson) {
// await updatePerson({ // await updatePerson({
// variables: { // variables: {
@ -146,13 +142,14 @@ export const AddPersonToCompany = ({
/> />
</StyledInputContainer> </StyledInputContainer>
) : ( ) : (
<PeoplePicker <>todo</>
personId={''} // <PeoplePicker
onSubmit={handlePersonSelected(companyId)} // personId={''}
onCancel={handleClosePicker} // onSubmit={handlePersonSelected(companyId)}
onCreate={() => setIsCreationDropdownOpen(true)} // onCancel={handleClosePicker}
excludePersonIds={peopleIds} // onCreate={() => setIsCreationDropdownOpen(true)}
/> // excludePersonIds={peopleIds}
// />
)} )}
</div> </div>
)} )}

View File

@ -208,7 +208,7 @@ export const CompanyBoardCard = () => {
<CompanyChip <CompanyChip
id={company.id} id={company.id}
name={company.name} name={company.name}
pictureUrl={getLogoUrlFromDomainName(company.domainName)} avatarUrl={getLogoUrlFromDomainName(company.domainName)}
variant={EntityChipVariant.Transparent} variant={EntityChipVariant.Transparent}
/> />
{showCompactView && ( {showCompactView && (
@ -239,14 +239,13 @@ export const CompanyBoardCard = () => {
value={{ value={{
entityId: boardCardId, entityId: boardCardId,
recoilScopeId: boardCardId + viewField.fieldMetadataId, recoilScopeId: boardCardId + viewField.fieldMetadataId,
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: viewField.fieldMetadataId, fieldMetadataId: viewField.fieldMetadataId,
label: viewField.label, label: viewField.label,
iconName: viewField.iconName, iconName: viewField.iconName,
type: viewField.type, type: viewField.type,
metadata: viewField.metadata, metadata: viewField.metadata,
entityChipDisplayMapper:
viewField.entityChipDisplayMapper,
}, },
useUpdateEntityMutation: useUpdateOneObjectMutation, useUpdateEntityMutation: useUpdateOneObjectMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell, hotkeyScope: InlineCellHotkeyScope.InlineCell,

View File

@ -6,14 +6,14 @@ import {
type CompanyChipProps = { type CompanyChipProps = {
id: string; id: string;
name: string; name: string;
pictureUrl?: string; avatarUrl?: string;
variant?: EntityChipVariant; variant?: EntityChipVariant;
}; };
export const CompanyChip = ({ export const CompanyChip = ({
id, id,
name, name,
pictureUrl, avatarUrl,
variant = EntityChipVariant.Regular, variant = EntityChipVariant.Regular,
}: CompanyChipProps) => ( }: CompanyChipProps) => (
<EntityChip <EntityChip
@ -21,7 +21,7 @@ export const CompanyChip = ({
linkToEntity={`/objects/companies/${id}`} linkToEntity={`/objects/companies/${id}`}
name={name} name={name}
avatarType="squared" avatarType="squared"
pictureUrl={pictureUrl} avatarUrl={avatarUrl}
variant={variant} variant={variant}
/> />
); );

View File

@ -1,78 +0,0 @@
import { useEffect } from 'react';
import { useQuery } from '@apollo/client';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2';
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 { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { getLogoUrlFromDomainName } from '~/utils';
export type CompanyPickerProps = {
companyId: string | null;
onSubmit: (newCompanyId: EntityForSelect | null) => void;
onCancel?: () => void;
initialSearchFilter?: string | null;
};
export const CompanyPicker = ({
companyId,
onSubmit,
onCancel,
initialSearchFilter,
}: CompanyPickerProps) => {
const [relationPickerSearchFilter, setRelationPickerSearchFilter] =
useRecoilScopedState(relationPickerSearchFilterScopedState);
useEffect(() => {
if (initialSearchFilter) {
setRelationPickerSearchFilter(initialSearchFilter);
}
}, [initialSearchFilter, setRelationPickerSearchFilter]);
const { findManyQuery } = useFindOneObjectMetadataItem({
objectNamePlural: 'companies',
});
const useFindManyCompanies = (options: any) =>
useQuery(findManyQuery, options);
const companies = useFilteredSearchEntityQueryV2({
queryHook: useFindManyCompanies,
filters: [
{
fieldNames: ['name'],
filter: relationPickerSearchFilter,
},
],
orderByField: 'name',
mappingFunction: (company) => ({
entityType: Entity.Company,
id: company.id,
name: company.name,
avatarType: 'squared',
avatarUrl: getLogoUrlFromDomainName(company.domainName),
originalEntity: company,
}),
selectedIds: companyId ? [companyId] : [],
objectNamePlural: 'companies',
});
const handleEntitySelected = async (
selectedCompany: EntityForSelect | null | undefined,
) => {
onSubmit(selectedCompany ?? null);
};
return (
<SingleEntitySelect
entitiesToSelect={companies.entitiesToSelect}
loading={companies.loading}
onCancel={onCancel}
onEntitySelected={handleEntitySelected}
selectedEntity={companies.selectedEntities[0]}
/>
);
};

View File

@ -3,7 +3,6 @@ import { useRecoilState } from 'recoil';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { IconChevronDown } from '@/ui/display/icon'; import { IconChevronDown } from '@/ui/display/icon';
import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch'; import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@ -14,8 +13,6 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
export type CompanyProgressPickerProps = { export type CompanyProgressPickerProps = {
companyId: string | null; companyId: string | null;
onSubmit: ( onSubmit: (
@ -34,10 +31,10 @@ export const CompanyProgressPicker = ({
const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch(); const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
const companies = useFilteredSearchCompanyQuery({ // const companies = useFilteredSearchCompanyQuery({
searchFilter, // searchFilter,
selectedIds: companyId ? [companyId] : [], // selectedIds: companyId ? [companyId] : [],
}); // });
const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] = const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] =
useState(false); useState(false);
@ -113,13 +110,13 @@ export const CompanyProgressPicker = ({
/> />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<RecoilScope> <RecoilScope>
<SingleEntitySelectBase {/* <SingleEntitySelectBase
entitiesToSelect={companies.entitiesToSelect} entitiesToSelect={companies.entitiesToSelect}
loading={companies.loading} loading={companies.loading}
onCancel={onCancel} onCancel={onCancel}
onEntitySelected={handleEntitySelected} onEntitySelected={handleEntitySelected}
selectedEntity={companies.selectedEntities[0]} selectedEntity={companies.selectedEntities[0]}
/> /> */}
</RecoilScope> </RecoilScope>
</> </>
)} )}

View File

@ -1,24 +0,0 @@
import { ObjectFilterDropdownEntitySearchSelect } from '@/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect';
import { useFilter } from '@/ui/object/object-filter-dropdown/hooks/useFilter';
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
export const FilterDropdownCompanySearchSelect = () => {
const {
objectFilterDropdownSearchInput,
objectFilterDropdownSelectedEntityId,
} = useFilter();
const usersForSelect = useFilteredSearchCompanyQuery({
searchFilter: objectFilterDropdownSearchInput,
selectedIds: objectFilterDropdownSelectedEntityId
? [objectFilterDropdownSelectedEntityId]
: [],
});
return (
<ObjectFilterDropdownEntitySearchSelect
entitiesForSelect={usersForSelect}
/>
);
};

View File

@ -5,7 +5,6 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { Company } from '@/companies/types/Company'; import { Company } from '@/companies/types/Company';
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 { pipelineAvailableFieldDefinitions } from '@/pipeline/constants/pipelineAvailableFieldDefinitions';
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';
@ -117,7 +116,7 @@ export const HooksCompanyBoardEffect = () => {
useEffect(() => { useEffect(() => {
setAvailableFilterDefinitions(opportunitiesBoardOptions.filterDefinitions); setAvailableFilterDefinitions(opportunitiesBoardOptions.filterDefinitions);
setAvailableSortDefinitions?.(opportunitiesBoardOptions.sortDefinitions); setAvailableSortDefinitions?.(opportunitiesBoardOptions.sortDefinitions);
setAvailableFieldDefinitions?.(pipelineAvailableFieldDefinitions); setAvailableFieldDefinitions?.([]);
}, [ }, [
setAvailableFieldDefinitions, setAvailableFieldDefinitions,
setAvailableFilterDefinitions, setAvailableFilterDefinitions,
@ -140,7 +139,7 @@ export const HooksCompanyBoardEffect = () => {
if (!loading && opportunities && companies) { if (!loading && opportunities && companies) {
setActionBarEntries(); setActionBarEntries();
setContextMenuEntries(); setContextMenuEntries();
setAvailableBoardCardFields(pipelineAvailableFieldDefinitions); setAvailableBoardCardFields([]);
updateCompanyBoard(pipelineSteps, opportunities, companies); updateCompanyBoard(pipelineSteps, opportunities, companies);
setEntityCountInCurrentView(companies.length); setEntityCountInCurrentView(companies.length);
} }
@ -160,10 +159,7 @@ export const HooksCompanyBoardEffect = () => {
useEffect(() => { useEffect(() => {
if (currentViewFields) { if (currentViewFields) {
setBoardCardFields( setBoardCardFields(
mapViewFieldsToBoardFieldDefinitions( mapViewFieldsToBoardFieldDefinitions(currentViewFields, []),
currentViewFields,
pipelineAvailableFieldDefinitions,
),
); );
} }
}, [currentViewFields, setBoardCardFields]); }, [currentViewFields, setBoardCardFields]);

View File

@ -1,7 +1,6 @@
import { useCallback, useContext, useState } from 'react'; import { useCallback, useContext, useState } from 'react';
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
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 { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { NewButton } from '@/ui/layout/board/components/NewButton'; import { NewButton } from '@/ui/layout/board/components/NewButton';
@ -9,8 +8,6 @@ import { BoardColumnContext } from '@/ui/layout/board/contexts/BoardColumnContex
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
export const NewCompanyProgressButton = () => { export const NewCompanyProgressButton = () => {
const [isCreatingCard, setIsCreatingCard] = useState(false); const [isCreatingCard, setIsCreatingCard] = useState(false);
const column = useContext(BoardColumnContext); const column = useContext(BoardColumnContext);
@ -55,22 +52,23 @@ export const NewCompanyProgressButton = () => {
relationPickerSearchFilterScopedState, relationPickerSearchFilterScopedState,
); );
const companies = useFilteredSearchCompanyQuery({ // const companies = useFilteredSearchCompanyQuery({
searchFilter: relationPickerSearchFilter, // searchFilter: relationPickerSearchFilter,
}); // });
return ( return (
<> <>
{isCreatingCard ? ( {isCreatingCard ? (
<SingleEntitySelect <>TODO</>
disableBackgroundBlur
entitiesToSelect={companies.entitiesToSelect}
loading={companies.loading}
onCancel={handleCancel}
onEntitySelected={handleEntitySelect}
selectedEntity={companies.selectedEntities[0]}
/>
) : ( ) : (
// <SingleEntitySelect
// disableBackgroundBlur
// entitiesToSelect={companies.entitiesToSelect}
// loading={companies.loading}
// onCancel={handleCancel}
// onEntitySelected={handleEntitySelect}
// selectedEntity={companies.selectedEntities[0]}
// />
<NewButton onClick={handleNewClick} /> <NewButton onClick={handleNewClick} />
)} )}
</> </>

View File

@ -1,44 +0,0 @@
import { useQuery } from '@apollo/client';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
export const useFilteredSearchCompanyQuery = ({
searchFilter,
selectedIds = [],
limit,
}: {
searchFilter: string;
selectedIds?: string[];
limit?: number;
}) => {
const { findManyQuery } = useFindOneObjectMetadataItem({
objectNameSingular: 'company',
});
const useFindManyCompanies = (options: any) =>
useQuery(findManyQuery, options);
return useFilteredSearchEntityQueryV2({
queryHook: useFindManyCompanies,
filters: [
{
fieldNames: ['name.firstName', 'name.lastName'],
filter: searchFilter,
},
],
orderByField: 'createdAt',
mappingFunction: (company) => ({
entityType: Entity.Company,
id: company.id,
name: company.name,
avatarType: 'squared',
avatarUrl: '',
originalEntity: company,
}),
selectedIds: selectedIds,
objectNamePlural: 'workspaceMembers',
limit,
});
};

View File

@ -26,7 +26,6 @@ export const useComputeDefinitionsFromFieldMetadata = (
formatFieldMetadataItemAsColumnDefinition({ formatFieldMetadataItemAsColumnDefinition({
position: index, position: index,
field, field,
objectMetadataItem: objectMetadataItem,
}), }),
); );

View File

@ -8,7 +8,7 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
const mapFieldMetadataToGraphQLQuery = ( const mapFieldMetadataToGraphQLQuery = (
field: FieldMetadataItem, field: FieldMetadataItem,
maxDepthForRelations: number = 1, maxDepthForRelations: number = 2,
): any => { ): any => {
if (maxDepthForRelations <= 0) { if (maxDepthForRelations <= 0) {
return ''; return '';

View File

@ -0,0 +1,56 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper';
export const useObjectMainIdentifier = (
objectMetadataItem?: ObjectMetadataItem,
) => {
if (!objectMetadataItem) {
return {
mainIdentifierMapper: undefined,
mainIdentifierFieldMetadataId: undefined,
basePathToShowPage: undefined,
};
}
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 mainIdentifierFieldMetadataId = objectMetadataItem.fields.find(
({ name }) => name === 'name',
)?.id;
const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
return {
mainIdentifierMapper,
mainIdentifierFieldMetadataId,
basePathToShowPage,
};
};

View File

@ -6,12 +6,18 @@ export type FieldMetadataItem = Omit<
> & { > & {
fromRelationMetadata?: fromRelationMetadata?:
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & { | (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
toObjectMetadata: Pick<Relation['toObjectMetadata'], 'id'>; toObjectMetadata: Pick<
Relation['toObjectMetadata'],
'id' | 'nameSingular' | 'namePlural'
>;
}) })
| null; | null;
toRelationMetadata?: toRelationMetadata?:
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & { | (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
fromObjectMetadata: Pick<Relation['fromObjectMetadata'], 'id'>; fromObjectMetadata: Pick<
Relation['fromObjectMetadata'],
'id' | 'nameSingular' | 'namePlural'
>;
}) })
| null; | null;
}; };

View File

@ -1,32 +1,68 @@
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 { FieldMetadataItem } from '../types/FieldMetadataItem'; import { FieldMetadataItem } from '../types/FieldMetadataItem';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
import { parseFieldType } from './parseFieldType'; import { parseFieldType } from './parseFieldType';
export const formatFieldMetadataItemAsColumnDefinition = ({ export const formatFieldMetadataItemAsColumnDefinition = ({
position, position,
field, field,
objectMetadataItem,
}: { }: {
position: number; position: number;
field: FieldMetadataItem; field: FieldMetadataItem;
objectMetadataItem: Omit<ObjectMetadataItem, 'fields'>; }): ColumnDefinition<FieldMetadata> => {
}): ColumnDefinition<FieldMetadata> => ({ const relationObjectMetadataItem =
position, field.toRelationMetadata?.fromObjectMetadata;
fieldMetadataId: field.id,
label: field.label, const mainIdentifierMapper: MainIdentifierMapper = (record: any) => {
size: 100, if (relationObjectMetadataItem?.nameSingular === 'company') {
type: parseFieldType(field.type), return {
metadata: { id: record.id,
fieldName: field.name, name: record.name,
placeHolder: field.label, avatarUrl: getLogoUrlFromDomainName(record.domainName),
}, avatarType: 'squared',
iconName: field.icon ?? 'Icon123', record: record,
isVisible: true, };
basePathToShowPage: `/object/${objectMetadataItem.nameSingular}/`, }
relationType: parseFieldRelationType(field), 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,
};
};
return {
position,
fieldMetadataId: field.id,
label: field.label,
size: 100,
type: parseFieldType(field.type),
metadata: {
fieldName: field.name,
placeHolder: field.label,
mainIdentifierMapper: mainIdentifierMapper,
relationType: parseFieldRelationType(field),
searchFields: ['name'],
objectMetadataNamePlural: relationObjectMetadataItem?.namePlural,
objectMetadataNameSingular: relationObjectMetadataItem?.nameSingular,
},
iconName: field.icon ?? 'Icon123',
isVisible: true,
};
};

View File

@ -165,11 +165,11 @@ export const RecordShowPage = () => {
value={{ value={{
entityId: object.id, entityId: object.id,
recoilScopeId: object.id + metadataField.id, recoilScopeId: object.id + metadataField.id,
isMainIdentifier: false,
fieldDefinition: fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({ formatFieldMetadataItemAsColumnDefinition({
field: metadataField, field: metadataField,
position: index, position: index,
objectMetadataItem: foundObjectMetadataItem,
}), }),
useUpdateEntityMutation: useUpdateOneObjectMutation, useUpdateEntityMutation: useUpdateOneObjectMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell, hotkeyScope: InlineCellHotkeyScope.InlineCell,

View File

@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { useComputeDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useComputeDefinitionsFromFieldMetadata'; import { useComputeDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useComputeDefinitionsFromFieldMetadata';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useObjectMainIdentifier } from '@/object-metadata/hooks/useObjectMainIdentifier';
import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries'; import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
@ -13,12 +14,19 @@ export const RecordTableEffect = () => {
scopeId: objectNamePlural, scopeId: objectNamePlural,
setAvailableTableColumns, setAvailableTableColumns,
setOnEntityCountChange, setOnEntityCountChange,
setObjectMetadataConfig,
} = useRecordTable(); } = useRecordTable();
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({ const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
objectNamePlural, objectNamePlural,
}); });
const {
mainIdentifierMapper,
basePathToShowPage,
mainIdentifierFieldMetadataId,
} = useObjectMainIdentifier(foundObjectMetadataItem);
const { columnDefinitions, filterDefinitions, sortDefinitions } = const { columnDefinitions, filterDefinitions, sortDefinitions } =
useComputeDefinitionsFromFieldMetadata(foundObjectMetadataItem); useComputeDefinitionsFromFieldMetadata(foundObjectMetadataItem);
@ -31,6 +39,26 @@ export const RecordTableEffect = () => {
setEntityCountInCurrentView, setEntityCountInCurrentView,
} = useView(); } = useView();
useEffect(() => {
if (
mainIdentifierMapper &&
basePathToShowPage &&
mainIdentifierFieldMetadataId
) {
setObjectMetadataConfig?.({
mainIdentifierMapper,
basePathToShowPage,
mainIdentifierFieldMetadataId,
});
}
}, [
basePathToShowPage,
foundObjectMetadataItem,
mainIdentifierFieldMetadataId,
mainIdentifierMapper,
setObjectMetadataConfig,
]);
useEffect(() => { useEffect(() => {
if (!foundObjectMetadataItem) { if (!foundObjectMetadataItem) {
return; return;

View File

@ -1,12 +1,13 @@
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata'; import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { isFieldRelation } from '@/ui/object/field/types/guards/isFieldRelation';
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
export const filterAvailableTableColumns = ( export const filterAvailableTableColumns = (
columnDefinition: ColumnDefinition<FieldMetadata>, columnDefinition: ColumnDefinition<FieldMetadata>,
): boolean => { ): boolean => {
if ( if (
columnDefinition.type === 'RELATION' && isFieldRelation(columnDefinition) &&
columnDefinition.relationType !== 'TO_ONE_OBJECT' columnDefinition.metadata?.relationType !== 'TO_ONE_OBJECT'
) { ) {
return false; return false;
} }

View File

@ -15,6 +15,7 @@ export const mapPaginatedObjectsToObjects = <
pagedObjects: ObjectTypeQuery | undefined; pagedObjects: ObjectTypeQuery | undefined;
objectNamePlural: string; objectNamePlural: string;
}) => { }) => {
console.log(objectNamePlural);
const formattedObjects: ObjectType[] = const formattedObjects: ObjectType[] =
pagedObjects?.[objectNamePlural].edges.map((objectEdge: ObjectEdge) => ({ pagedObjects?.[objectNamePlural].edges.map((objectEdge: ObjectEdge) => ({
...objectEdge.node, ...objectEdge.node,

View File

@ -1,23 +0,0 @@
import { useFilteredSearchPeopleQuery } from '@/people/hooks/useFilteredSearchPeopleQuery';
import { ObjectFilterDropdownEntitySearchSelect } from '@/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect';
import { useFilter } from '@/ui/object/object-filter-dropdown/hooks/useFilter';
export const FilterDropdownPeopleSearchSelect = () => {
const {
objectFilterDropdownSearchInput,
objectFilterDropdownSelectedEntityId,
} = useFilter();
const peopleForSelect = useFilteredSearchPeopleQuery({
searchFilter: objectFilterDropdownSearchInput,
selectedIds: objectFilterDropdownSelectedEntityId
? [objectFilterDropdownSelectedEntityId]
: [],
});
return (
<ObjectFilterDropdownEntitySearchSelect
entitiesForSelect={peopleForSelect}
/>
);
};

View File

@ -1,96 +0,0 @@
import { useEffect } from 'react';
import { useQuery } from '@apollo/client';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2';
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 { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
export type PeoplePickerProps = {
personId: string | null;
companyId?: string;
onSubmit: (newPersonId: PersonForSelect | null) => void;
onCancel?: () => void;
onCreate?: () => void;
excludePersonIds?: string[];
initialSearchFilter?: string | null;
};
export type PersonForSelect = EntityForSelect & {
entityType: Entity.Person;
};
export const PeoplePicker = ({
personId,
companyId,
onSubmit,
onCancel,
onCreate,
excludePersonIds,
initialSearchFilter,
}: PeoplePickerProps) => {
const [relationPickerSearchFilter, setRelationPickerSearchFilter] =
useRecoilScopedState(relationPickerSearchFilterScopedState);
useEffect(() => {
setRelationPickerSearchFilter(initialSearchFilter ?? '');
}, [initialSearchFilter, setRelationPickerSearchFilter]);
const queryFilters = [
{
fieldNames: ['name.firstName', 'name.lastName'],
filter: relationPickerSearchFilter,
},
];
if (companyId) {
queryFilters.push({
fieldNames: ['companyId'],
filter: companyId,
});
}
const { findManyQuery } = useFindOneObjectMetadataItem({
objectNameSingular: 'person',
});
const useFindManyPeople = (options: any) => useQuery(findManyQuery, options);
const people = useFilteredSearchEntityQueryV2({
queryHook: useFindManyPeople,
filters: queryFilters,
orderByField: 'createdAt',
mappingFunction: (workspaceMember) => ({
entityType: Entity.WorkspaceMember,
id: workspaceMember.id,
name:
workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName,
avatarType: 'rounded',
avatarUrl: '',
originalEntity: workspaceMember,
}),
selectedIds: [personId ?? ''],
excludeEntityIds: excludePersonIds,
objectNamePlural: 'people',
});
const handleEntitySelected = async (
selectedPerson: any | null | undefined,
) => {
onSubmit(selectedPerson ?? null);
};
return (
<SingleEntitySelect
entitiesToSelect={people.entitiesToSelect}
loading={people.loading}
onCancel={onCancel}
onCreate={onCreate}
onEntitySelected={handleEntitySelected}
selectedEntity={people.selectedEntities[0]}
/>
);
};

View File

@ -6,14 +6,14 @@ import {
export type PersonChipProps = { export type PersonChipProps = {
id: string; id: string;
name: string; name: string;
pictureUrl?: string; avatarUrl?: string;
variant?: EntityChipVariant; variant?: EntityChipVariant;
}; };
export const PersonChip = ({ export const PersonChip = ({
id, id,
name, name,
pictureUrl, avatarUrl,
variant, variant,
}: PersonChipProps) => ( }: PersonChipProps) => (
<EntityChip <EntityChip
@ -21,7 +21,7 @@ export const PersonChip = ({
linkToEntity={`/person/${id}`} linkToEntity={`/person/${id}`}
name={name} name={name}
avatarType="rounded" avatarType="rounded"
pictureUrl={pictureUrl} avatarUrl={avatarUrl}
variant={variant} variant={variant}
/> />
); );

View File

@ -1,43 +0,0 @@
import { useQuery } from '@apollo/client';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
export const useFilteredSearchPeopleQuery = ({
searchFilter,
selectedIds = [],
limit,
}: {
searchFilter: string;
selectedIds?: string[];
limit?: number;
}) => {
const { findManyQuery } = useFindOneObjectMetadataItem({
objectNameSingular: 'person',
});
const useFindManyPeople = (options: any) => useQuery(findManyQuery, options);
return useFilteredSearchEntityQueryV2({
queryHook: useFindManyPeople,
filters: [
{
fieldNames: ['name.firstName', 'name.lastName'],
filter: searchFilter,
},
],
orderByField: 'createdAt',
mappingFunction: (person) => ({
entityType: Entity.Person,
id: person.id,
name: person.name.firstName + ' ' + person.name.lastName,
avatarType: 'rounded',
avatarUrl: '',
originalEntity: person,
}),
selectedIds: selectedIds,
objectNamePlural: 'workspaceMembers',
limit,
});
};

View File

@ -1,78 +0,0 @@
import { Person } from '@/people/types/Person';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import {
FieldDateMetadata,
FieldMetadata,
FieldNumberMetadata,
FieldProbabilityMetadata,
FieldRelationMetadata,
} from '@/ui/object/field/types/FieldMetadata';
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
export const pipelineAvailableFieldDefinitions: ColumnDefinition<FieldMetadata>[] =
[
{
fieldMetadataId: 'closeDate',
label: 'Close Date',
iconName: 'IconCalendarEvent',
position: 0,
type: 'DATE',
metadata: {
fieldName: 'closeDate',
},
size: 0,
isVisible: true,
infoTooltipContent:
'Specified date by which an opportunity must be completed.',
} satisfies ColumnDefinition<FieldDateMetadata>,
{
fieldMetadataId: 'amount',
label: 'Amount',
iconName: 'IconCurrencyDollar',
position: 1,
type: 'NUMBER',
metadata: {
fieldName: 'amount',
placeHolder: '0',
},
size: 0,
isVisible: true,
infoTooltipContent: 'Potential monetary value of a business opportunity.',
} satisfies ColumnDefinition<FieldNumberMetadata>,
{
fieldMetadataId: 'probability',
label: 'Probability',
iconName: 'IconProgressCheck',
position: 2,
type: 'PROBABILITY',
metadata: {
fieldName: 'probability',
},
size: 0,
isVisible: true,
infoTooltipContent:
"Level of certainty in the lead's potential to convert into a success.",
} satisfies ColumnDefinition<FieldProbabilityMetadata>,
{
fieldMetadataId: 'pointOfContact',
label: 'Point of Contact',
iconName: 'IconUser',
position: 3,
type: 'RELATION',
metadata: {
fieldName: 'pointOfContact',
relationType: Entity.Person,
useEditButton: true,
},
size: 0,
isVisible: true,
infoTooltipContent: 'Primary contact within the company.',
entityChipDisplayMapper: (dataObject: Person) => {
return {
name: dataObject?.name.firstName + ' ' + dataObject?.name.lastName,
pictureUrl: dataObject?.avatarUrl ?? undefined,
avatarType: 'rounded',
};
},
} satisfies ColumnDefinition<FieldRelationMetadata>,
];

View File

@ -18,7 +18,7 @@ const DEFAULT_SEARCH_REQUEST_LIMIT = 30;
// TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search // TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search
// Filtered entities to select are // Filtered entities to select are
export const useFilteredSearchEntityQueryV2 = ({ export const useFilteredSearchEntityQuery = ({
queryHook, queryHook,
orderByField, orderByField,
filters, filters,

View File

@ -90,12 +90,11 @@ export const SettingsObjectFieldPreview = ({
objectMetadataId, objectMetadataId,
}); });
const { defaultValue: relationDefaultValue, entityChipDisplayMapper } = const { defaultValue: relationDefaultValue } = useRelationFieldPreview({
useRelationFieldPreview({ relationObjectMetadataId,
relationObjectMetadataId, skipDefaultValue:
skipDefaultValue: fieldMetadata.type !== FieldMetadataType.Relation || hasValue,
fieldMetadata.type !== FieldMetadataType.Relation || hasValue, });
});
const defaultValue = const defaultValue =
fieldMetadata.type === FieldMetadataType.Relation fieldMetadata.type === FieldMetadataType.Relation
@ -138,16 +137,13 @@ export const SettingsObjectFieldPreview = ({
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId, entityId,
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
type: parseFieldType(fieldMetadata.type), type: parseFieldType(fieldMetadata.type),
iconName: 'FieldIcon', iconName: 'FieldIcon',
fieldMetadataId: fieldMetadata.id || '', fieldMetadataId: fieldMetadata.id || '',
label: fieldMetadata.label, label: fieldMetadata.label,
metadata: { fieldName }, metadata: { fieldName },
entityChipDisplayMapper:
fieldMetadata.type === FieldMetadataType.Relation
? entityChipDisplayMapper
: undefined,
}, },
hotkeyScope: 'field-preview', hotkeyScope: 'field-preview',
}} }}

View File

@ -12,7 +12,7 @@ export type EntityChipProps = {
linkToEntity?: string; linkToEntity?: string;
entityId: string; entityId: string;
name: string; name: string;
pictureUrl?: string; avatarUrl?: string;
avatarType?: AvatarType; avatarType?: AvatarType;
variant?: EntityChipVariant; variant?: EntityChipVariant;
LeftIcon?: IconComponent; LeftIcon?: IconComponent;
@ -27,7 +27,7 @@ export const EntityChip = ({
linkToEntity, linkToEntity,
entityId, entityId,
name, name,
pictureUrl, avatarUrl,
avatarType = 'rounded', avatarType = 'rounded',
variant = EntityChipVariant.Regular, variant = EntityChipVariant.Regular,
LeftIcon, LeftIcon,
@ -59,7 +59,7 @@ export const EntityChip = ({
<LeftIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} /> <LeftIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
) : ( ) : (
<Avatar <Avatar
avatarUrl={pictureUrl} avatarUrl={avatarUrl}
colorId={entityId} colorId={entityId}
placeholder={name} placeholder={name}
size="sm" size="sm"

View File

@ -11,14 +11,12 @@ import { sleep } from '~/testing/sleep';
import { relationPickerSearchFilterScopedState } from '../../states/relationPickerSearchFilterScopedState'; import { relationPickerSearchFilterScopedState } from '../../states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '../../types/EntityForSelect'; import { EntityForSelect } from '../../types/EntityForSelect';
import { Entity } from '../../types/EntityTypeForSelect';
import { SingleEntitySelect } from '../SingleEntitySelect'; import { SingleEntitySelect } from '../SingleEntitySelect';
const entities = mockedPeopleData.map<EntityForSelect>((person) => ({ const entities = mockedPeopleData.map<EntityForSelect>((person) => ({
id: person.id, id: person.id,
entityType: Entity.Person,
name: person.name.firstName + ' ' + person.name.lastName, name: person.name.firstName + ' ' + person.name.lastName,
originalEntity: person, record: person,
})); }));
const meta: Meta<typeof SingleEntitySelect> = { const meta: Meta<typeof SingleEntitySelect> = {

View File

@ -1,12 +1,9 @@
import { AvatarType } from '@/users/components/Avatar'; import { AvatarType } from '@/users/components/Avatar';
import { EntityTypeForSelect } from './EntityTypeForSelect';
export type EntityForSelect = { export type EntityForSelect = {
id: string; id: string;
entityType: EntityTypeForSelect;
name: string; name: string;
avatarUrl?: string; avatarUrl?: string;
avatarType?: AvatarType; avatarType?: AvatarType;
originalEntity: any; record: any;
}; };

View File

@ -1,10 +0,0 @@
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
export enum Entity {
Company = 'Company',
Person = 'Person',
User = 'User',
WorkspaceMember = 'WorkspaceMember',
}
export type EntityTypeForSelect = ActivityTargetableEntityType | Entity;

View File

@ -2,7 +2,6 @@ import { useCallback, useContext, useRef, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
import { import {
IconArrowLeft, IconArrowLeft,
IconArrowRight, IconArrowRight,
@ -10,7 +9,6 @@ import {
IconPlus, IconPlus,
} from '@/ui/display/icon'; } from '@/ui/display/icon';
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
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 { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
@ -98,9 +96,9 @@ export const BoardColumnMenu = ({
const [relationPickerSearchFilter] = useRecoilScopedState( const [relationPickerSearchFilter] = useRecoilScopedState(
relationPickerSearchFilterScopedState, relationPickerSearchFilterScopedState,
); );
const companies = useFilteredSearchCompanyQuery({ // const companies = useFilteredSearchCompanyQuery({
searchFilter: relationPickerSearchFilter, // searchFilter: relationPickerSearchFilter,
}); // });
useListenClickOutside({ useListenClickOutside({
refs: [boardColumnMenuRef], refs: [boardColumnMenuRef],
@ -172,14 +170,15 @@ export const BoardColumnMenu = ({
/> />
)} )}
{currentMenu === 'add' && ( {currentMenu === 'add' && (
<SingleEntitySelect <div>add</div>
disableBackgroundBlur // <SingleEntitySelect
entitiesToSelect={companies.entitiesToSelect} // disableBackgroundBlur
loading={companies.loading} // entitiesToSelect={companies.entitiesToSelect}
onCancel={closeMenu} // loading={companies.loading}
onEntitySelected={handleCompanySelected} // onCancel={closeMenu}
selectedEntity={companies.selectedEntities[0]} // onEntitySelected={handleCompanySelected}
/> // selectedEntity={companies.selectedEntities[0]}
// />
)} )}
</DropdownMenu> </DropdownMenu>
</StyledMenuContainer> </StyledMenuContainer>

View File

@ -10,6 +10,13 @@ export type GenericFieldContextType = {
entityId: string; entityId: string;
recoilScopeId?: string; recoilScopeId?: string;
hotkeyScope: string; hotkeyScope: string;
isMainIdentifier: boolean;
mainIdentifierMapper?: (record: any) => {
name: string;
avatarUrl?: string;
avatarType: string;
};
basePathToShowPage?: string;
}; };
export const FieldContext = createContext<GenericFieldContextType>( export const FieldContext = createContext<GenericFieldContextType>(

View File

@ -10,10 +10,8 @@ export const useIsFieldEmpty = () => {
const isFieldEmpty = useRecoilValue( const isFieldEmpty = useRecoilValue(
isEntityFieldEmptyFamilySelector({ isEntityFieldEmptyFamilySelector({
fieldDefinition: { fieldDefinition: {
fieldMetadataId: fieldDefinition.fieldMetadataId,
label: fieldDefinition.label,
type: fieldDefinition.type, type: fieldDefinition.type,
metadata: fieldDefinition.metadata, metadata: { ...fieldDefinition.metadata, mainIdentifierMapper: null },
}, },
entityId, entityId,
}), }),

View File

@ -18,6 +18,7 @@ export const FieldContextProvider = ({
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: entityId ?? '1', entityId: entityId ?? '1',
isMainIdentifier: false,
recoilScopeId: '1', recoilScopeId: '1',
hotkeyScope: 'hotkey-scope', hotkeyScope: 'hotkey-scope',
fieldDefinition, fieldDefinition,

View File

@ -2,14 +2,12 @@ import { useChipField } from '../../hooks/useChipField';
import { ChipDisplay } from '../content-display/components/ChipDisplay'; import { ChipDisplay } from '../content-display/components/ChipDisplay';
export const ChipFieldDisplay = () => { export const ChipFieldDisplay = () => {
const { avatarFieldValue, contentFieldValue, entityType, entityId } = const { avatarFieldValue, contentFieldValue, entityId } = useChipField();
useChipField();
return ( return (
<ChipDisplay <ChipDisplay
displayName={contentFieldValue} displayName={contentFieldValue}
avatarUrlValue={avatarFieldValue} avatarUrlValue={avatarFieldValue}
entityType={entityType}
entityId={entityId} entityId={entityId}
/> />
); );

View File

@ -2,7 +2,7 @@ import { useDoubleTextChipField } from '../../hooks/useDoubleTextChipField';
import { ChipDisplay } from '../content-display/components/ChipDisplay'; import { ChipDisplay } from '../content-display/components/ChipDisplay';
export const DoubleTextChipFieldDisplay = () => { export const DoubleTextChipFieldDisplay = () => {
const { avatarUrl, firstValue, secondValue, entityType, entityId } = const { avatarUrl, firstValue, secondValue, entityId } =
useDoubleTextChipField(); useDoubleTextChipField();
const content = [firstValue, secondValue].filter(Boolean).join(' '); const content = [firstValue, secondValue].filter(Boolean).join(' ');
@ -11,7 +11,6 @@ export const DoubleTextChipFieldDisplay = () => {
<ChipDisplay <ChipDisplay
displayName={content} displayName={content}
avatarUrlValue={avatarUrl} avatarUrlValue={avatarUrl}
entityType={entityType}
entityId={entityId} entityId={entityId}
/> />
); );

View File

@ -1,22 +1,23 @@
import { EntityChip } from '@/ui/display/chip/components/EntityChip'; import { EntityChip } from '@/ui/display/chip/components/EntityChip';
import { getEntityChipFromFieldMetadata } from '@/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata';
import { useRelationField } from '../../hooks/useRelationField'; import { useRelationField } from '../../hooks/useRelationField';
export const RelationFieldDisplay = () => { export const RelationFieldDisplay = () => {
const { fieldValue, fieldDefinition } = useRelationField(); const { fieldValue, fieldDefinition } = useRelationField();
const entityChipProps = getEntityChipFromFieldMetadata( if (!fieldValue || !fieldDefinition) {
fieldDefinition, return <></>;
fieldValue, }
);
const mainIdentifierMapped =
fieldDefinition.metadata.mainIdentifierMapper(fieldValue);
return ( return (
<EntityChip <EntityChip
entityId={entityChipProps.entityId} entityId={fieldValue.id}
name={entityChipProps.name} name={mainIdentifierMapped.name}
pictureUrl={entityChipProps.pictureUrl} avatarUrl={mainIdentifierMapped.avatarUrl}
avatarType={entityChipProps.avatarType} avatarType={mainIdentifierMapped.avatarType}
/> />
); );
}; };

View File

@ -26,6 +26,7 @@ const meta: Meta = {
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: '', entityId: '',
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: 'date', fieldMetadataId: 'date',
label: 'Date', label: 'Date',

View File

@ -25,6 +25,7 @@ const meta: Meta = {
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: '', entityId: '',
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: 'email', fieldMetadataId: 'email',
label: 'Email', label: 'Email',

View File

@ -25,6 +25,7 @@ const meta: Meta = {
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: '', entityId: '',
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: 'enum', fieldMetadataId: 'enum',
label: 'Enum', label: 'Enum',

View File

@ -24,6 +24,7 @@ const meta: Meta = {
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: '', entityId: '',
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: 'money', fieldMetadataId: 'money',
label: 'Money', label: 'Money',

View File

@ -24,6 +24,7 @@ const meta: Meta = {
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: '', entityId: '',
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: 'number', fieldMetadataId: 'number',
label: 'Number', label: 'Number',

View File

@ -25,6 +25,7 @@ const meta: Meta = {
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: '', entityId: '',
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: 'phone', fieldMetadataId: 'phone',
label: 'Phone', label: 'Phone',

View File

@ -24,6 +24,7 @@ const meta: Meta = {
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: '', entityId: '',
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: 'text', fieldMetadataId: 'text',
label: 'Text', label: 'Text',

View File

@ -25,6 +25,7 @@ const meta: Meta = {
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
entityId: '', entityId: '',
isMainIdentifier: false,
fieldDefinition: { fieldDefinition: {
fieldMetadataId: 'URL', fieldMetadataId: 'URL',
label: 'URL', label: 'URL',

View File

@ -1,45 +1,34 @@
import { CompanyChip } from '@/companies/components/CompanyChip';
import { PersonChip } from '@/people/components/PersonChip';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { getLogoUrlFromDomainName } from '~/utils';
import { logError } from '~/utils/logError';
type ChipDisplayProps = { type ChipDisplayProps = {
entityType: Entity;
displayName: string; displayName: string;
entityId: string | null; entityId: string | null;
avatarUrlValue?: string; avatarUrlValue?: string;
}; };
export const ChipDisplay = ({ export const ChipDisplay = ({
entityType,
displayName, displayName,
entityId, entityId,
avatarUrlValue, avatarUrlValue,
}: ChipDisplayProps) => { }: ChipDisplayProps) => {
switch (entityType) { switch (true) {
case Entity.Company: { // case Entity.Company: {
return ( // return (
<CompanyChip // <CompanyChip
id={entityId ?? ''} // id={entityId ?? ''}
name={displayName} // name={displayName}
pictureUrl={getLogoUrlFromDomainName(avatarUrlValue)} // avatarUrl={getLogoUrlFromDomainName(avatarUrlValue)}
/> // />
); // );
} // }
case Entity.Person: { // case Entity.Person: {
return ( // return (
<PersonChip // <PersonChip
id={entityId ?? ''} // id={entityId ?? ''}
name={displayName} // name={displayName}
pictureUrl={avatarUrlValue} // avatarUrl={avatarUrlValue}
/> // />
); // );
} // }
default: default:
logError(
`Unknown relation type: "${entityType}" in DoubleTextChipDisplay`,
);
return <> </>; return <> </>;
} }
}; };

View File

@ -1,42 +0,0 @@
import { EntityChipProps } from '@/ui/display/chip/components/EntityChip';
import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition';
import { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata';
import { getLogoUrlFromDomainName } from '~/utils';
export const getEntityChipFromFieldMetadata = (
fieldDefinition: FieldDefinition<FieldRelationMetadata>,
fieldValue: any,
) => {
const { entityChipDisplayMapper } = fieldDefinition;
const { fieldName } = fieldDefinition.metadata;
const defaultChipValue: Pick<
EntityChipProps,
'name' | 'pictureUrl' | 'avatarType' | 'entityId'
> = {
name: '',
pictureUrl: '',
avatarType: 'rounded',
entityId: fieldValue?.id,
};
if (['accountOwner', 'person'].includes(fieldName) && fieldValue) {
return {
...defaultChipValue,
name: `${fieldValue.firstName} ${fieldValue.lastName}`,
};
}
if (fieldName === 'company' && fieldValue) {
return {
...defaultChipValue,
name: fieldValue.name,
pictureUrl: getLogoUrlFromDomainName(fieldValue.domainName),
};
}
return {
...defaultChipValue,
...entityChipDisplayMapper?.(fieldValue),
};
};

View File

@ -29,8 +29,6 @@ export const useChipField = () => {
}), }),
); );
const entityType = fieldDefinition.metadata.relationType;
const fieldInitialValue = useFieldInitialValue(); const fieldInitialValue = useFieldInitialValue();
const initialContentValue = fieldInitialValue?.isEmpty const initialContentValue = fieldInitialValue?.isEmpty
@ -51,7 +49,6 @@ export const useChipField = () => {
avatarFieldValue, avatarFieldValue,
initialAvatarValue, initialAvatarValue,
setAvatarFieldValue, setAvatarFieldValue,
entityType,
entityId, entityId,
hotkeyScope, hotkeyScope,
}; };

View File

@ -39,8 +39,6 @@ export const useDoubleTextChipField = () => {
const fullValue = [firstValue, secondValue].filter(Boolean).join(' '); const fullValue = [firstValue, secondValue].filter(Boolean).join(' ');
const entityType = fieldDefinition.metadata.entityType;
const fieldInitialValue = useFieldInitialValue(); const fieldInitialValue = useFieldInitialValue();
const initialFirstValue = fieldInitialValue?.isEmpty const initialFirstValue = fieldInitialValue?.isEmpty
@ -68,7 +66,6 @@ export const useDoubleTextChipField = () => {
firstValue, firstValue,
setFirstValue, setFirstValue,
fullValue, fullValue,
entityType,
entityId, entityId,
hotkeyScope, hotkeyScope,
initialAvatarUrl, initialAvatarUrl,

View File

@ -1,10 +1,8 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { CompanyPicker } from '@/companies/components/CompanyPicker';
import { PeoplePicker } from '@/people/components/PeoplePicker';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { WorkspaceMemberPicker } from '@/workspace-member/components/WorkspaceMemberPicker'; import { RelationPicker } from '@/ui/object/field/meta-types/input/components/internal/RelationPicker';
import { usePersistField } from '../../../hooks/usePersistField'; import { usePersistField } from '../../../hooks/usePersistField';
import { useRelationField } from '../../hooks/useRelationField'; import { useRelationField } from '../../hooks/useRelationField';
@ -32,14 +30,21 @@ export const RelationFieldInput = ({
const persistField = usePersistField(); const persistField = usePersistField();
const handleSubmit = (newEntity: EntityForSelect | null) => { const handleSubmit = (newEntity: EntityForSelect | null) => {
onSubmit?.(() => persistField(newEntity?.originalEntity ?? null)); onSubmit?.(() => persistField(newEntity?.record ?? null));
}; };
useEffect(() => {}, [initialSearchValue]); useEffect(() => {}, [initialSearchValue]);
return ( return (
<StyledRelationPickerContainer> <StyledRelationPickerContainer>
{fieldDefinition.metadata.fieldName === 'person' ? ( <RelationPicker
fieldDefinition={fieldDefinition}
recordId={initialValue?.id ?? ''}
onSubmit={handleSubmit}
onCancel={onCancel}
initialSearchFilter={initialSearchValue}
/>
{/* {fieldDefinition.metadata.fieldName === 'person' ? (
<PeoplePicker <PeoplePicker
personId={initialValue?.id ?? ''} personId={initialValue?.id ?? ''}
companyId={initialValue?.companyId ?? ''} companyId={initialValue?.companyId ?? ''}
@ -47,13 +52,6 @@ export const RelationFieldInput = ({
onCancel={onCancel} onCancel={onCancel}
initialSearchFilter={initialSearchValue} initialSearchFilter={initialSearchValue}
/> />
) : fieldDefinition.metadata.fieldName === 'accountOwner' ? (
<WorkspaceMemberPicker
userId={initialValue?.id ?? ''}
onSubmit={handleSubmit}
onCancel={onCancel}
initialSearchFilter={initialSearchValue}
/>
) : fieldDefinition.metadata.fieldName === 'company' ? ( ) : fieldDefinition.metadata.fieldName === 'company' ? (
<CompanyPicker <CompanyPicker
companyId={initialValue?.id ?? ''} companyId={initialValue?.id ?? ''}
@ -61,7 +59,7 @@ export const RelationFieldInput = ({
onCancel={onCancel} onCancel={onCancel}
initialSearchFilter={initialSearchValue} initialSearchFilter={initialSearchValue}
/> />
) : null} ) : null} */}
</StyledRelationPickerContainer> </StyledRelationPickerContainer>
); );
}; };

View File

@ -3,7 +3,6 @@ import { expect, jest } from '@storybook/jest';
import { Decorator, Meta, StoryObj } from '@storybook/react'; import { Decorator, Meta, StoryObj } from '@storybook/react';
import { userEvent, waitFor, within } from '@storybook/testing-library'; import { userEvent, waitFor, within } from '@storybook/testing-library';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider'; import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
@ -52,7 +51,6 @@ const ChipFieldInputWithContext = ({
contentFieldName: 'name', contentFieldName: 'name',
urlFieldName: 'xURL', urlFieldName: 'xURL',
placeHolder: 'X URL', placeHolder: 'X URL',
relationType: Entity.Person,
}, },
}} }}
entityId={entityId} entityId={entityId}

View File

@ -3,7 +3,6 @@ import { expect, jest } from '@storybook/jest';
import { Decorator, Meta, StoryObj } from '@storybook/react'; import { Decorator, Meta, StoryObj } from '@storybook/react';
import { userEvent, waitFor, within } from '@storybook/testing-library'; import { userEvent, waitFor, within } from '@storybook/testing-library';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider'; import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
@ -67,7 +66,7 @@ const DoubleTextChipFieldInputWithContext = ({
secondValueFieldName: 'Second-text', secondValueFieldName: 'Second-text',
secondValuePlaceholder: 'Second-text', secondValuePlaceholder: 'Second-text',
avatarUrlFieldName: 'avatarUrl', avatarUrlFieldName: 'avatarUrl',
entityType: Entity.Person, fieldName: '',
}, },
}} }}
entityId={entityId} entityId={entityId}

View File

@ -3,7 +3,6 @@ import { expect, jest } from '@storybook/jest';
import { Decorator, Meta, StoryObj } from '@storybook/react'; import { Decorator, Meta, StoryObj } from '@storybook/react';
import { userEvent, waitFor, within } from '@storybook/testing-library'; import { userEvent, waitFor, within } from '@storybook/testing-library';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
@ -52,7 +51,6 @@ const RelationFieldInputWithContext = ({
iconName: 'IconLink', iconName: 'IconLink',
metadata: { metadata: {
fieldName: 'Relation', fieldName: 'Relation',
relationType: Entity.Person,
}, },
}} }}
entityId={entityId} entityId={entityId}

View File

@ -2,29 +2,32 @@ import { useEffect } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { IconUserCircle } from '@/ui/display/icon'; 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 { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; 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'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
export type WorkspaceMemberPickerProps = { export type RelationPickerProps = {
userId: string; recordId: string;
onSubmit: (newUser: EntityForSelect | null) => void; onSubmit: (newUser: EntityForSelect | null) => void;
onCancel?: () => void; onCancel?: () => void;
width?: number; width?: number;
initialSearchFilter?: string | null; initialSearchFilter?: string | null;
fieldDefinition: FieldDefinition<FieldRelationMetadata>;
}; };
export const WorkspaceMemberPicker = ({ export const RelationPicker = ({
userId, recordId,
onSubmit, onSubmit,
onCancel, onCancel,
width, width,
initialSearchFilter, initialSearchFilter,
}: WorkspaceMemberPickerProps) => { fieldDefinition,
}: RelationPickerProps) => {
const [relationPickerSearchFilter, setRelationPickerSearchFilter] = const [relationPickerSearchFilter, setRelationPickerSearchFilter] =
useRecoilScopedState(relationPickerSearchFilterScopedState); useRecoilScopedState(relationPickerSearchFilterScopedState);
@ -33,32 +36,23 @@ export const WorkspaceMemberPicker = ({
}, [initialSearchFilter, setRelationPickerSearchFilter]); }, [initialSearchFilter, setRelationPickerSearchFilter]);
const { findManyQuery } = useFindOneObjectMetadataItem({ const { findManyQuery } = useFindOneObjectMetadataItem({
objectNameSingular: 'workspaceMember', objectNameSingular: fieldDefinition.metadata.objectMetadataNameSingular,
}); });
const useFindManyWorkspaceMembers = (options: any) => const useFindManyQuery = (options: any) => useQuery(findManyQuery, options);
useQuery(findManyQuery, options);
const workspaceMembers = useFilteredSearchEntityQueryV2({ const workspaceMembers = useFilteredSearchEntityQuery({
queryHook: useFindManyWorkspaceMembers, queryHook: useFindManyQuery,
filters: [ filters: [
{ {
fieldNames: ['name.firstName', 'name.lastName'], fieldNames: fieldDefinition.metadata.searchFields,
filter: relationPickerSearchFilter, filter: relationPickerSearchFilter,
}, },
], ],
orderByField: 'createdAt', orderByField: 'createdAt',
mappingFunction: (workspaceMember) => ({ mappingFunction: fieldDefinition.metadata.mainIdentifierMapper,
entityType: Entity.WorkspaceMember, selectedIds: recordId ? [recordId] : [],
id: workspaceMember.id, objectNamePlural: fieldDefinition.metadata.objectMetadataNamePlural,
name:
workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName,
avatarType: 'rounded',
avatarUrl: '',
originalEntity: workspaceMember,
}),
selectedIds: userId ? [userId] : [],
objectNamePlural: 'workspaceMembers',
}); });
const handleEntitySelected = async (selectedUser: any | null | undefined) => { const handleEntitySelected = async (selectedUser: any | null | undefined) => {

View File

@ -34,10 +34,9 @@ export const isEntityFieldEmptyFamilySelector = selectorFamily({
fieldDefinition, fieldDefinition,
entityId, entityId,
}: { }: {
fieldDefinition: Pick< fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type'> & {
FieldDefinition<FieldMetadata>, metadata: Omit<FieldMetadata, 'mainIdentifierMapper'>;
'type' | 'metadata' | 'fieldMetadataId' | 'label' };
>;
entityId: string; entityId: string;
}) => { }) => {
return ({ get }) => { return ({ get }) => {

View File

@ -1,5 +1,3 @@
import { AvatarType } from '@/users/components/Avatar';
import { FieldMetadata } from './FieldMetadata'; import { FieldMetadata } from './FieldMetadata';
import { FieldType } from './FieldType'; import { FieldType } from './FieldType';
@ -15,12 +13,5 @@ export type FieldDefinition<T extends FieldMetadata> = {
iconName: string; iconName: string;
type: FieldType; type: FieldType;
metadata: T; metadata: T;
basePathToShowPage?: string;
infoTooltipContent?: string; infoTooltipContent?: string;
entityChipDisplayMapper?: (dataObject: any) => {
name: string;
pictureUrl?: string;
avatarType: AvatarType;
};
relationType?: FieldDefinitionRelationType;
}; };

View File

@ -1,5 +1,5 @@
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper';
import { ThemeColor } from '@/ui/theme/constants/colors'; import { ThemeColor } from '@/ui/theme/constants/colors';
export type FieldUuidMetadata = { export type FieldUuidMetadata = {
@ -58,14 +58,23 @@ export type FieldEmailMetadata = {
placeHolder: string; placeHolder: string;
}; };
export type FieldDefinitionRelationType =
| 'FROM_MANY_OBJECTS'
| 'FROM_ONE_OBJECT'
| 'TO_MANY_OBJECTS'
| 'TO_ONE_OBJECT';
export type FieldRelationMetadata = { export type FieldRelationMetadata = {
relationType: Entity;
fieldName: string; fieldName: string;
useEditButton?: boolean; useEditButton?: boolean;
relationType?: FieldDefinitionRelationType;
mainIdentifierMapper: MainIdentifierMapper;
searchFields: string[];
objectMetadataNameSingular: string;
objectMetadataNamePlural: string;
}; };
export type FieldChipMetadata = { export type FieldChipMetadata = {
relationType: Entity;
contentFieldName: string; contentFieldName: string;
urlFieldName: string; urlFieldName: string;
placeHolder: string; placeHolder: string;
@ -84,7 +93,6 @@ export type FieldDoubleTextChipMetadata = {
secondValueFieldName: string; secondValueFieldName: string;
secondValuePlaceholder: string; secondValuePlaceholder: string;
avatarUrlFieldName: string; avatarUrlFieldName: string;
entityType: Entity;
}; };
export type FieldProbabilityMetadata = { export type FieldProbabilityMetadata = {

View File

@ -0,0 +1,9 @@
import { AvatarType } from '@/users/components/Avatar';
export type MainIdentifierMapper = (record: any) => {
id: string;
name: string;
avatarUrl?: string;
avatarType: AvatarType;
record: any;
};

View File

@ -0,0 +1,14 @@
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;
};

View File

@ -16,7 +16,7 @@ export const GenericEntityFilterChip = ({
entityId={filter.value} entityId={filter.value}
name={filter.displayValue} name={filter.displayValue}
avatarType="rounded" avatarType="rounded"
pictureUrl={filter.displayAvatarUrl} avatarUrl={filter.displayAvatarUrl}
LeftIcon={Icon} LeftIcon={Icon}
/> />
); );

View File

@ -1,9 +1,10 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { FieldContext } from '../../field/contexts/FieldContext'; import { FieldContext } from '../../field/contexts/FieldContext';
@ -20,6 +21,9 @@ export const RecordTableCell = ({ cellIndex }: { cellIndex: number }) => {
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
const currentRowId = useContext(RowIdContext); const currentRowId = useContext(RowIdContext);
const { objectMetadataConfigState } = useRecordTableScopedStates();
const objectMetadataConfig = useRecoilValue(objectMetadataConfigState);
const { setCurrentRowSelected } = useCurrentRowSelected(); const { setCurrentRowSelected } = useCurrentRowSelected();
@ -56,6 +60,10 @@ export const RecordTableCell = ({ cellIndex }: { cellIndex: number }) => {
fieldDefinition: columnDefinition, fieldDefinition: columnDefinition,
useUpdateEntityMutation: () => [updateEntityMutation, {}], useUpdateEntityMutation: () => [updateEntityMutation, {}],
hotkeyScope: customHotkeyScope, hotkeyScope: customHotkeyScope,
isMainIdentifier:
columnDefinition.fieldMetadataId ===
objectMetadataConfig?.mainIdentifierFieldMetadataId,
mainIdentifierMapper: objectMetadataConfig?.mainIdentifierMapper,
}} }}
> >
<TableCell customHotkeyScope={{ scope: customHotkeyScope }} /> <TableCell customHotkeyScope={{ scope: customHotkeyScope }} />

View File

@ -18,6 +18,7 @@ export const useRecordTableScopedStates = (args?: {
tableFiltersState, tableFiltersState,
tableSortsState, tableSortsState,
tableColumnsState, tableColumnsState,
objectMetadataConfigState,
tableColumnsByKeySelector, tableColumnsByKeySelector,
hiddenTableColumnsSelector, hiddenTableColumnsSelector,
visibleTableColumnsSelector, visibleTableColumnsSelector,
@ -33,6 +34,7 @@ export const useRecordTableScopedStates = (args?: {
tableFiltersState, tableFiltersState,
tableSortsState, tableSortsState,
tableColumnsState, tableColumnsState,
objectMetadataConfigState,
tableColumnsByKeySelector, tableColumnsByKeySelector,
hiddenTableColumnsSelector, hiddenTableColumnsSelector,
visibleTableColumnsSelector, visibleTableColumnsSelector,

View File

@ -43,6 +43,7 @@ export const useRecordTable = (props?: useRecordTableProps) => {
tableFiltersState, tableFiltersState,
tableSortsState, tableSortsState,
tableColumnsState, tableColumnsState,
objectMetadataConfigState,
onEntityCountChangeState, onEntityCountChangeState,
} = useRecordTableScopedStates({ } = useRecordTableScopedStates({
customRecordTableScopeId: scopeId, customRecordTableScopeId: scopeId,
@ -54,6 +55,7 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const setOnEntityCountChange = useSetRecoilState(onEntityCountChangeState); const setOnEntityCountChange = useSetRecoilState(onEntityCountChangeState);
const setTableFilters = useSetRecoilState(tableFiltersState); const setTableFilters = useSetRecoilState(tableFiltersState);
const setObjectMetadataConfig = useSetRecoilState(objectMetadataConfigState);
const setTableSorts = useSetRecoilState(tableSortsState); const setTableSorts = useSetRecoilState(tableSortsState);
@ -301,6 +303,7 @@ export const useRecordTable = (props?: useRecordTableProps) => {
setAvailableTableColumns, setAvailableTableColumns,
setTableFilters, setTableFilters,
setTableSorts, setTableSorts,
setObjectMetadataConfig,
setOnEntityCountChange, setOnEntityCountChange,
setRecordTableData, setRecordTableData,
setTableColumns, setTableColumns,

View File

@ -37,7 +37,8 @@ export const useTableCell = () => {
const isEmpty = useIsFieldEmpty(); const isEmpty = useIsFieldEmpty();
const { entityId, fieldDefinition } = useContext(FieldContext); const { entityId, fieldDefinition, basePathToShowPage } =
useContext(FieldContext);
const [, setFieldInitialValue] = useRecoilState( const [, setFieldInitialValue] = useRecoilState(
entityFieldInitialValueFamilyState({ entityFieldInitialValueFamilyState({
@ -47,8 +48,8 @@ export const useTableCell = () => {
); );
const openTableCell = (options?: { initialValue?: FieldInitialValue }) => { const openTableCell = (options?: { initialValue?: FieldInitialValue }) => {
if (isFirstColumnCell && !isEmpty && fieldDefinition.basePathToShowPage) { if (isFirstColumnCell && !isEmpty && basePathToShowPage) {
navigate(`${fieldDefinition.basePathToShowPage}${entityId}`); navigate(`${basePathToShowPage}${entityId}`);
return; return;
} }

View File

@ -0,0 +1,8 @@
import { ObjectMetadataConfig } from '@/ui/object/record-table/types/ObjectMetadataConfig';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const objectMetadataConfigScopedState =
createScopedState<ObjectMetadataConfig | null>({
key: 'objectMetadataConfigScopedState',
defaultValue: null,
});

View File

@ -0,0 +1,11 @@
import { AvatarType } from '@/users/components/Avatar';
export type ObjectMetadataConfig = {
mainIdentifierFieldMetadataId: string;
mainIdentifierMapper: (record: any) => {
name: string;
avatarUrl?: string;
avatarType: AvatarType;
};
basePathToShowPage: string;
};

View File

@ -1,3 +1,4 @@
import { objectMetadataConfigScopedState } from '@/ui/object/record-table/states/objectMetadataConfigScopedState';
import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState'; import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState';
import { availableTableColumnsScopedState } from '../states/availableTableColumnsScopedState'; import { availableTableColumnsScopedState } from '../states/availableTableColumnsScopedState';
@ -36,6 +37,11 @@ export const getRecordTableScopedStates = ({
recordTableScopeId, recordTableScopeId,
); );
const objectMetadataConfigState = getScopedState(
objectMetadataConfigScopedState,
recordTableScopeId,
);
const tableColumnsByKeySelector = const tableColumnsByKeySelector =
tableColumnsByKeyScopedSelector(recordTableScopeId); tableColumnsByKeyScopedSelector(recordTableScopeId);
@ -60,6 +66,7 @@ export const getRecordTableScopedStates = ({
tableFiltersState, tableFiltersState,
tableSortsState, tableSortsState,
tableColumnsState, tableColumnsState,
objectMetadataConfigState,
tableColumnsByKeySelector, tableColumnsByKeySelector,
hiddenTableColumnsSelector, hiddenTableColumnsSelector,
visibleTableColumnsSelector, visibleTableColumnsSelector,

View File

@ -1,8 +1,7 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { ObjectFilterDropdownEntitySearchSelect } from '@/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect'; import { ObjectFilterDropdownEntitySearchSelect } from '@/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect';
import { useFilter } from '@/ui/object/object-filter-dropdown/hooks/useFilter'; import { useFilter } from '@/ui/object/object-filter-dropdown/hooks/useFilter';
@ -19,7 +18,7 @@ export const FilterDropdownUserSearchSelect = () => {
const useFindManyWorkspaceMembers = (options: any) => const useFindManyWorkspaceMembers = (options: any) =>
useQuery(findManyQuery, options); useQuery(findManyQuery, options);
const workspaceMembers = useFilteredSearchEntityQueryV2({ const workspaceMembers = useFilteredSearchEntityQuery({
queryHook: useFindManyWorkspaceMembers, queryHook: useFindManyWorkspaceMembers,
filters: [ filters: [
{ {
@ -29,13 +28,13 @@ export const FilterDropdownUserSearchSelect = () => {
], ],
orderByField: 'createdAt', orderByField: 'createdAt',
mappingFunction: (workspaceMember) => ({ mappingFunction: (workspaceMember) => ({
entityType: Entity.WorkspaceMember, entityType: 'WorkspaceMember',
id: workspaceMember.id, id: workspaceMember.id,
name: name:
workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName, workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName,
avatarType: 'rounded', avatarType: 'rounded',
avatarUrl: '', avatarUrl: '',
originalEntity: workspaceMember, record: workspaceMember,
}), }),
selectedIds: objectFilterDropdownSelectedEntityId selectedIds: objectFilterDropdownSelectedEntityId
? [objectFilterDropdownSelectedEntityId] ? [objectFilterDropdownSelectedEntityId]

View File

@ -3,14 +3,14 @@ import { EntityChip } from '@/ui/display/chip/components/EntityChip';
export type UserChipProps = { export type UserChipProps = {
id: string; id: string;
name: string; name: string;
pictureUrl?: string; avatarUrl?: string;
}; };
export const UserChip = ({ id, name, pictureUrl }: UserChipProps) => ( export const UserChip = ({ id, name, avatarUrl }: UserChipProps) => (
<EntityChip <EntityChip
entityId={id} entityId={id}
name={name} name={name}
avatarType="rounded" avatarType="rounded"
pictureUrl={pictureUrl} avatarUrl={avatarUrl}
/> />
); );

View File

@ -19,10 +19,7 @@ export const mapViewFieldsToBoardFieldDefinitions = (
fieldMetadataId: viewField.fieldMetadataId, fieldMetadataId: viewField.fieldMetadataId,
label: correspondingFieldMetadata.label, label: correspondingFieldMetadata.label,
metadata: correspondingFieldMetadata.metadata, metadata: correspondingFieldMetadata.metadata,
entityChipDisplayMapper:
correspondingFieldMetadata.entityChipDisplayMapper,
infoTooltipContent: correspondingFieldMetadata.infoTooltipContent, infoTooltipContent: correspondingFieldMetadata.infoTooltipContent,
basePathToShowPage: correspondingFieldMetadata.basePathToShowPage,
iconName: correspondingFieldMetadata.iconName, iconName: correspondingFieldMetadata.iconName,
type: correspondingFieldMetadata.type, type: correspondingFieldMetadata.type,
position: viewField.position, position: viewField.position,

View File

@ -19,10 +19,7 @@ export const mapViewFieldsToColumnDefinitions = (
fieldMetadataId: viewField.fieldMetadataId, fieldMetadataId: viewField.fieldMetadataId,
label: correspondingFieldMetadata.label, label: correspondingFieldMetadata.label,
metadata: correspondingFieldMetadata.metadata, metadata: correspondingFieldMetadata.metadata,
entityChipDisplayMapper:
correspondingFieldMetadata.entityChipDisplayMapper,
infoTooltipContent: correspondingFieldMetadata.infoTooltipContent, infoTooltipContent: correspondingFieldMetadata.infoTooltipContent,
basePathToShowPage: correspondingFieldMetadata.basePathToShowPage,
iconName: correspondingFieldMetadata.iconName, iconName: correspondingFieldMetadata.iconName,
type: correspondingFieldMetadata.type, type: correspondingFieldMetadata.type,
position: viewField.position, position: viewField.position,

View File

@ -1,9 +1,6 @@
import { FilterDropdownCompanySearchSelect } from '@/companies/components/FilterDropdownCompanySearchSelect';
import { Opportunity } from '@/pipeline/types/Opportunity'; import { Opportunity } from '@/pipeline/types/Opportunity';
import { FilterDefinitionByEntity } from '@/ui/object/object-filter-dropdown/types/FilterDefinitionByEntity'; import { FilterDefinitionByEntity } from '@/ui/object/object-filter-dropdown/types/FilterDefinitionByEntity';
import { FilterDropdownPeopleSearchSelect } from '../../../modules/people/components/FilterDropdownPeopleSearchSelect';
export const opportunityBoardFilterDefinitions: FilterDefinitionByEntity<Opportunity>[] = export const opportunityBoardFilterDefinitions: FilterDefinitionByEntity<Opportunity>[] =
[ [
{ {
@ -23,13 +20,13 @@ export const opportunityBoardFilterDefinitions: FilterDefinitionByEntity<Opportu
label: 'Company', label: 'Company',
iconName: 'IconBuildingSkyscraper', iconName: 'IconBuildingSkyscraper',
type: 'ENTITY', type: 'ENTITY',
entitySelectComponent: <FilterDropdownCompanySearchSelect />, // entitySelectComponent: <FilterDropdownCompanySearchSelect />,
}, },
{ {
fieldMetadataId: 'pointOfContactId', fieldMetadataId: 'pointOfContactId',
label: 'Point of contact', label: 'Point of contact',
iconName: 'IconUser', iconName: 'IconUser',
type: 'ENTITY', type: 'ENTITY',
entitySelectComponent: <FilterDropdownPeopleSearchSelect />, //entitySelectComponent: <FilterDropdownPeopleSearchSelect />,
}, },
]; ];