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}
id={company.id}
name={company.name}
pictureUrl={getLogoUrlFromDomainName(company.domainName)}
avatarUrl={getLogoUrlFromDomainName(company.domainName)}
/>
);
}
@ -46,7 +46,7 @@ export const ActivityTargetChips = ({
key={person.id}
id={person.id}
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 { Activity } from '@/activities/types/Activity';
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 { assertNotNull } from '~/utils/assert';
@ -60,31 +56,31 @@ export const ActivityRelationEditableFieldEditMode = ({
Record<string, boolean>
>(initialSelectedEntityIds);
const personsForMultiSelect = useFilteredSearchPeopleQuery({
searchFilter,
selectedIds: initialPeopleIds,
});
// const personsForMultiSelect = useFilteredSearchPeopleQuery({
// searchFilter,
// selectedIds: initialPeopleIds,
// });
const companiesForMultiSelect = useFilteredSearchCompanyQuery({
searchFilter,
selectedIds: initialCompanyIds,
});
// const companiesForMultiSelect = useFilteredSearchCompanyQuery({
// searchFilter,
// selectedIds: initialCompanyIds,
// });
const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.selectedEntities,
companiesForMultiSelect.selectedEntities,
]);
// const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
// personsForMultiSelect.selectedEntities,
// companiesForMultiSelect.selectedEntities,
// ]);
const filteredSelectedEntities =
flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.filteredSelectedEntities,
companiesForMultiSelect.filteredSelectedEntities,
]);
// const filteredSelectedEntities =
// flatMapAndSortEntityForSelectArrayOfArrayByName([
// personsForMultiSelect.filteredSelectedEntities,
// companiesForMultiSelect.filteredSelectedEntities,
// ]);
const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.entitiesToSelect,
companiesForMultiSelect.entitiesToSelect,
]);
// const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([
// personsForMultiSelect.entitiesToSelect,
// companiesForMultiSelect.entitiesToSelect,
// ]);
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
activity,
@ -102,7 +98,7 @@ export const ActivityRelationEditableFieldEditMode = ({
return (
<StyledSelectContainer>
<MultipleEntitySelect
{/* <MultipleEntitySelect
entities={{
entitiesToSelect,
filteredSelectedEntities,
@ -115,7 +111,7 @@ export const ActivityRelationEditableFieldEditMode = ({
value={selectedEntityIds}
onCancel={handleCancel}
onSubmit={handleSubmit}
/>
/> */}
</StyledSelectContainer>
);
};

View File

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

View File

@ -17,7 +17,7 @@ export const SmallName: Story = {
args: {
id: '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: {
id: 'google',
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 { v4 } from 'uuid';
import {
PeoplePicker,
PersonForSelect,
} from '@/people/components/PeoplePicker';
import { IconPlus } from '@/ui/display/icon';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
@ -67,7 +63,7 @@ export const AddPersonToCompany = ({
} = usePreviousHotkeyScope();
const handlePersonSelected =
(companyId: string) => async (newPerson: PersonForSelect | null) => {
(companyId: string) => async (newPerson: any | null) => {
if (newPerson) {
// await updatePerson({
// variables: {
@ -146,13 +142,14 @@ export const AddPersonToCompany = ({
/>
</StyledInputContainer>
) : (
<PeoplePicker
personId={''}
onSubmit={handlePersonSelected(companyId)}
onCancel={handleClosePicker}
onCreate={() => setIsCreationDropdownOpen(true)}
excludePersonIds={peopleIds}
/>
<>todo</>
// <PeoplePicker
// personId={''}
// onSubmit={handlePersonSelected(companyId)}
// onCancel={handleClosePicker}
// onCreate={() => setIsCreationDropdownOpen(true)}
// excludePersonIds={peopleIds}
// />
)}
</div>
)}

View File

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

View File

@ -6,14 +6,14 @@ import {
type CompanyChipProps = {
id: string;
name: string;
pictureUrl?: string;
avatarUrl?: string;
variant?: EntityChipVariant;
};
export const CompanyChip = ({
id,
name,
pictureUrl,
avatarUrl,
variant = EntityChipVariant.Regular,
}: CompanyChipProps) => (
<EntityChip
@ -21,7 +21,7 @@ export const CompanyChip = ({
linkToEntity={`/objects/companies/${id}`}
name={name}
avatarType="squared"
pictureUrl={pictureUrl}
avatarUrl={avatarUrl}
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 { IconChevronDown } from '@/ui/display/icon';
import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
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 { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
export type CompanyProgressPickerProps = {
companyId: string | null;
onSubmit: (
@ -34,10 +31,10 @@ export const CompanyProgressPicker = ({
const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
const companies = useFilteredSearchCompanyQuery({
searchFilter,
selectedIds: companyId ? [companyId] : [],
});
// const companies = useFilteredSearchCompanyQuery({
// searchFilter,
// selectedIds: companyId ? [companyId] : [],
// });
const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] =
useState(false);
@ -113,13 +110,13 @@ export const CompanyProgressPicker = ({
/>
<DropdownMenuSeparator />
<RecoilScope>
<SingleEntitySelectBase
{/* <SingleEntitySelectBase
entitiesToSelect={companies.entitiesToSelect}
loading={companies.loading}
onCancel={onCancel}
onEntitySelected={handleEntitySelected}
selectedEntity={companies.selectedEntities[0]}
/>
/> */}
</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 { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
import { pipelineAvailableFieldDefinitions } from '@/pipeline/constants/pipelineAvailableFieldDefinitions';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { PipelineStep } from '@/pipeline/types/PipelineStep';
import { useBoardActionBarEntries } from '@/ui/layout/board/hooks/useBoardActionBarEntries';
@ -117,7 +116,7 @@ export const HooksCompanyBoardEffect = () => {
useEffect(() => {
setAvailableFilterDefinitions(opportunitiesBoardOptions.filterDefinitions);
setAvailableSortDefinitions?.(opportunitiesBoardOptions.sortDefinitions);
setAvailableFieldDefinitions?.(pipelineAvailableFieldDefinitions);
setAvailableFieldDefinitions?.([]);
}, [
setAvailableFieldDefinitions,
setAvailableFilterDefinitions,
@ -140,7 +139,7 @@ export const HooksCompanyBoardEffect = () => {
if (!loading && opportunities && companies) {
setActionBarEntries();
setContextMenuEntries();
setAvailableBoardCardFields(pipelineAvailableFieldDefinitions);
setAvailableBoardCardFields([]);
updateCompanyBoard(pipelineSteps, opportunities, companies);
setEntityCountInCurrentView(companies.length);
}
@ -160,10 +159,7 @@ export const HooksCompanyBoardEffect = () => {
useEffect(() => {
if (currentViewFields) {
setBoardCardFields(
mapViewFieldsToBoardFieldDefinitions(
currentViewFields,
pipelineAvailableFieldDefinitions,
),
mapViewFieldsToBoardFieldDefinitions(currentViewFields, []),
);
}
}, [currentViewFields, setBoardCardFields]);

View File

@ -1,7 +1,6 @@
import { useCallback, useContext, useState } from 'react';
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 { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
export const NewCompanyProgressButton = () => {
const [isCreatingCard, setIsCreatingCard] = useState(false);
const column = useContext(BoardColumnContext);
@ -55,22 +52,23 @@ export const NewCompanyProgressButton = () => {
relationPickerSearchFilterScopedState,
);
const companies = useFilteredSearchCompanyQuery({
searchFilter: relationPickerSearchFilter,
});
// const companies = useFilteredSearchCompanyQuery({
// searchFilter: relationPickerSearchFilter,
// });
return (
<>
{isCreatingCard ? (
<SingleEntitySelect
disableBackgroundBlur
entitiesToSelect={companies.entitiesToSelect}
loading={companies.loading}
onCancel={handleCancel}
onEntitySelected={handleEntitySelect}
selectedEntity={companies.selectedEntities[0]}
/>
<>TODO</>
) : (
// <SingleEntitySelect
// disableBackgroundBlur
// entitiesToSelect={companies.entitiesToSelect}
// loading={companies.loading}
// onCancel={handleCancel}
// onEntitySelected={handleEntitySelect}
// selectedEntity={companies.selectedEntities[0]}
// />
<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({
position: index,
field,
objectMetadataItem: objectMetadataItem,
}),
);

View File

@ -8,7 +8,7 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
const mapFieldMetadataToGraphQLQuery = (
field: FieldMetadataItem,
maxDepthForRelations: number = 1,
maxDepthForRelations: number = 2,
): any => {
if (maxDepthForRelations <= 0) {
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?:
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
toObjectMetadata: Pick<Relation['toObjectMetadata'], 'id'>;
toObjectMetadata: Pick<
Relation['toObjectMetadata'],
'id' | 'nameSingular' | 'namePlural'
>;
})
| null;
toRelationMetadata?:
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
fromObjectMetadata: Pick<Relation['fromObjectMetadata'], 'id'>;
fromObjectMetadata: Pick<
Relation['fromObjectMetadata'],
'id' | 'nameSingular' | 'namePlural'
>;
})
| null;
};

View File

@ -1,32 +1,68 @@
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 { FieldMetadataItem } from '../types/FieldMetadataItem';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
import { parseFieldType } from './parseFieldType';
export const formatFieldMetadataItemAsColumnDefinition = ({
position,
field,
objectMetadataItem,
}: {
position: number;
field: FieldMetadataItem;
objectMetadataItem: Omit<ObjectMetadataItem, 'fields'>;
}): ColumnDefinition<FieldMetadata> => ({
position,
fieldMetadataId: field.id,
label: field.label,
size: 100,
type: parseFieldType(field.type),
metadata: {
fieldName: field.name,
placeHolder: field.label,
},
iconName: field.icon ?? 'Icon123',
isVisible: true,
basePathToShowPage: `/object/${objectMetadataItem.nameSingular}/`,
relationType: parseFieldRelationType(field),
});
}): ColumnDefinition<FieldMetadata> => {
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,
};
};
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={{
entityId: object.id,
recoilScopeId: object.id + metadataField.id,
isMainIdentifier: false,
fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({
field: metadataField,
position: index,
objectMetadataItem: foundObjectMetadataItem,
}),
useUpdateEntityMutation: useUpdateOneObjectMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,

View File

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

View File

@ -1,12 +1,13 @@
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';
export const filterAvailableTableColumns = (
columnDefinition: ColumnDefinition<FieldMetadata>,
): boolean => {
if (
columnDefinition.type === 'RELATION' &&
columnDefinition.relationType !== 'TO_ONE_OBJECT'
isFieldRelation(columnDefinition) &&
columnDefinition.metadata?.relationType !== 'TO_ONE_OBJECT'
) {
return false;
}

View File

@ -15,6 +15,7 @@ export const mapPaginatedObjectsToObjects = <
pagedObjects: ObjectTypeQuery | undefined;
objectNamePlural: string;
}) => {
console.log(objectNamePlural);
const formattedObjects: ObjectType[] =
pagedObjects?.[objectNamePlural].edges.map((objectEdge: ObjectEdge) => ({
...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 = {
id: string;
name: string;
pictureUrl?: string;
avatarUrl?: string;
variant?: EntityChipVariant;
};
export const PersonChip = ({
id,
name,
pictureUrl,
avatarUrl,
variant,
}: PersonChipProps) => (
<EntityChip
@ -21,7 +21,7 @@ export const PersonChip = ({
linkToEntity={`/person/${id}`}
name={name}
avatarType="rounded"
pictureUrl={pictureUrl}
avatarUrl={avatarUrl}
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
// Filtered entities to select are
export const useFilteredSearchEntityQueryV2 = ({
export const useFilteredSearchEntityQuery = ({
queryHook,
orderByField,
filters,

View File

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

View File

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

View File

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

View File

@ -1,12 +1,9 @@
import { AvatarType } from '@/users/components/Avatar';
import { EntityTypeForSelect } from './EntityTypeForSelect';
export type EntityForSelect = {
id: string;
entityType: EntityTypeForSelect;
name: string;
avatarUrl?: string;
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 { Key } from 'ts-key-enum';
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
import {
IconArrowLeft,
IconArrowRight,
@ -10,7 +9,6 @@ import {
IconPlus,
} from '@/ui/display/icon';
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 { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
@ -98,9 +96,9 @@ export const BoardColumnMenu = ({
const [relationPickerSearchFilter] = useRecoilScopedState(
relationPickerSearchFilterScopedState,
);
const companies = useFilteredSearchCompanyQuery({
searchFilter: relationPickerSearchFilter,
});
// const companies = useFilteredSearchCompanyQuery({
// searchFilter: relationPickerSearchFilter,
// });
useListenClickOutside({
refs: [boardColumnMenuRef],
@ -172,14 +170,15 @@ export const BoardColumnMenu = ({
/>
)}
{currentMenu === 'add' && (
<SingleEntitySelect
disableBackgroundBlur
entitiesToSelect={companies.entitiesToSelect}
loading={companies.loading}
onCancel={closeMenu}
onEntitySelected={handleCompanySelected}
selectedEntity={companies.selectedEntities[0]}
/>
<div>add</div>
// <SingleEntitySelect
// disableBackgroundBlur
// entitiesToSelect={companies.entitiesToSelect}
// loading={companies.loading}
// onCancel={closeMenu}
// onEntitySelected={handleCompanySelected}
// selectedEntity={companies.selectedEntities[0]}
// />
)}
</DropdownMenu>
</StyledMenuContainer>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,6 +25,7 @@ const meta: Meta = {
<FieldContext.Provider
value={{
entityId: '',
isMainIdentifier: false,
fieldDefinition: {
fieldMetadataId: '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 = {
entityType: Entity;
displayName: string;
entityId: string | null;
avatarUrlValue?: string;
};
export const ChipDisplay = ({
entityType,
displayName,
entityId,
avatarUrlValue,
}: ChipDisplayProps) => {
switch (entityType) {
case Entity.Company: {
return (
<CompanyChip
id={entityId ?? ''}
name={displayName}
pictureUrl={getLogoUrlFromDomainName(avatarUrlValue)}
/>
);
}
case Entity.Person: {
return (
<PersonChip
id={entityId ?? ''}
name={displayName}
pictureUrl={avatarUrlValue}
/>
);
}
switch (true) {
// case Entity.Company: {
// return (
// <CompanyChip
// id={entityId ?? ''}
// name={displayName}
// avatarUrl={getLogoUrlFromDomainName(avatarUrlValue)}
// />
// );
// }
// case Entity.Person: {
// return (
// <PersonChip
// id={entityId ?? ''}
// name={displayName}
// avatarUrl={avatarUrlValue}
// />
// );
// }
default:
logError(
`Unknown relation type: "${entityType}" in DoubleTextChipDisplay`,
);
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 initialContentValue = fieldInitialValue?.isEmpty
@ -51,7 +49,6 @@ export const useChipField = () => {
avatarFieldValue,
initialAvatarValue,
setAvatarFieldValue,
entityType,
entityId,
hotkeyScope,
};

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import { expect, jest } from '@storybook/jest';
import { Decorator, Meta, StoryObj } from '@storybook/react';
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 { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
@ -52,7 +51,6 @@ const ChipFieldInputWithContext = ({
contentFieldName: 'name',
urlFieldName: 'xURL',
placeHolder: 'X URL',
relationType: Entity.Person,
},
}}
entityId={entityId}

View File

@ -3,7 +3,6 @@ import { expect, jest } from '@storybook/jest';
import { Decorator, Meta, StoryObj } from '@storybook/react';
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 { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
@ -67,7 +66,7 @@ const DoubleTextChipFieldInputWithContext = ({
secondValueFieldName: 'Second-text',
secondValuePlaceholder: 'Second-text',
avatarUrlFieldName: 'avatarUrl',
entityType: Entity.Person,
fieldName: '',
},
}}
entityId={entityId}

View File

@ -3,7 +3,6 @@ import { expect, jest } from '@storybook/jest';
import { Decorator, Meta, StoryObj } from '@storybook/react';
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 { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
@ -52,7 +51,6 @@ const RelationFieldInputWithContext = ({
iconName: 'IconLink',
metadata: {
fieldName: 'Relation',
relationType: Entity.Person,
},
}}
entityId={entityId}

View File

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

View File

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

View File

@ -1,5 +1,3 @@
import { AvatarType } from '@/users/components/Avatar';
import { FieldMetadata } from './FieldMetadata';
import { FieldType } from './FieldType';
@ -15,12 +13,5 @@ export type FieldDefinition<T extends FieldMetadata> = {
iconName: string;
type: FieldType;
metadata: T;
basePathToShowPage?: 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 { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { MainIdentifierMapper } from '@/ui/object/field/types/MainIdentifierMapper';
import { ThemeColor } from '@/ui/theme/constants/colors';
export type FieldUuidMetadata = {
@ -58,14 +58,23 @@ export type FieldEmailMetadata = {
placeHolder: string;
};
export type FieldDefinitionRelationType =
| 'FROM_MANY_OBJECTS'
| 'FROM_ONE_OBJECT'
| 'TO_MANY_OBJECTS'
| 'TO_ONE_OBJECT';
export type FieldRelationMetadata = {
relationType: Entity;
fieldName: string;
useEditButton?: boolean;
relationType?: FieldDefinitionRelationType;
mainIdentifierMapper: MainIdentifierMapper;
searchFields: string[];
objectMetadataNameSingular: string;
objectMetadataNamePlural: string;
};
export type FieldChipMetadata = {
relationType: Entity;
contentFieldName: string;
urlFieldName: string;
placeHolder: string;
@ -84,7 +93,6 @@ export type FieldDoubleTextChipMetadata = {
secondValueFieldName: string;
secondValuePlaceholder: string;
avatarUrlFieldName: string;
entityType: Entity;
};
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}
name={filter.displayValue}
avatarType="rounded"
pictureUrl={filter.displayAvatarUrl}
avatarUrl={filter.displayAvatarUrl}
LeftIcon={Icon}
/>
);

View File

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

View File

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

View File

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

View File

@ -37,7 +37,8 @@ export const useTableCell = () => {
const isEmpty = useIsFieldEmpty();
const { entityId, fieldDefinition } = useContext(FieldContext);
const { entityId, fieldDefinition, basePathToShowPage } =
useContext(FieldContext);
const [, setFieldInitialValue] = useRecoilState(
entityFieldInitialValueFamilyState({
@ -47,8 +48,8 @@ export const useTableCell = () => {
);
const openTableCell = (options?: { initialValue?: FieldInitialValue }) => {
if (isFirstColumnCell && !isEmpty && fieldDefinition.basePathToShowPage) {
navigate(`${fieldDefinition.basePathToShowPage}${entityId}`);
if (isFirstColumnCell && !isEmpty && basePathToShowPage) {
navigate(`${basePathToShowPage}${entityId}`);
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 { availableTableColumnsScopedState } from '../states/availableTableColumnsScopedState';
@ -36,6 +37,11 @@ export const getRecordTableScopedStates = ({
recordTableScopeId,
);
const objectMetadataConfigState = getScopedState(
objectMetadataConfigScopedState,
recordTableScopeId,
);
const tableColumnsByKeySelector =
tableColumnsByKeyScopedSelector(recordTableScopeId);
@ -60,6 +66,7 @@ export const getRecordTableScopedStates = ({
tableFiltersState,
tableSortsState,
tableColumnsState,
objectMetadataConfigState,
tableColumnsByKeySelector,
hiddenTableColumnsSelector,
visibleTableColumnsSelector,

View File

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

View File

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

View File

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

View File

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

View File

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