Feat/activities custom objects (#3213)

* WIP

* WIP - MultiObjectSearch

* WIP

* WIP

* Finished working version

* Fix

* Fixed and cleaned

* Fix

* Disabled files and emails for custom objects

* Cleaned console.log

* Fixed attachment

* Fixed

* fix lint

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2024-01-05 09:08:33 +01:00
committed by GitHub
parent c15e138d72
commit b112b74022
72 changed files with 1611 additions and 551 deletions

View File

@ -1,22 +1,15 @@
import { useCallback, useMemo, useState } from 'react';
import { useQuery } from '@apollo/client';
import styled from '@emotion/styled';
import { v4 } from 'uuid';
import { useHandleCheckableActivityTargetChange } from '@/activities/hooks/useHandleCheckableActivityTargetChange';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '@/activities/utils/flatMapAndSortEntityForSelectArrayByName';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ActivityTargetObjectRecord } from '@/activities/types/ActivityTargetObject';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
import { MultipleEntitySelect } from '@/object-record/relation-picker/components/MultipleEntitySelect';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { assertNotNull } from '~/utils/assert';
type ActivityTargetInlineCellEditModeProps = {
activityId: string;
activityTargets: Array<Pick<ActivityTarget, 'id' | 'personId' | 'companyId'>>;
};
import { MultipleObjectRecordSelect } from '@/object-record/relation-picker/components/MultipleObjectRecordSelect';
import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
const StyledSelectContainer = styled.div`
left: 0px;
@ -24,125 +17,77 @@ const StyledSelectContainer = styled.div`
top: -8px;
`;
type ActivityTargetInlineCellEditModeProps = {
activityId: string;
activityTargetObjectRecords: ActivityTargetObjectRecord[];
};
export const ActivityTargetInlineCellEditMode = ({
activityId,
activityTargets,
activityTargetObjectRecords,
}: ActivityTargetInlineCellEditModeProps) => {
const [searchFilter, setSearchFilter] = useState('');
const initialPeopleIds = useMemo(
() =>
activityTargets
?.filter(({ personId }) => personId !== null)
.map(({ personId }) => personId)
.filter(assertNotNull) ?? [],
[activityTargets],
const selectedObjectRecordIds = activityTargetObjectRecords.map(
(activityTarget) => ({
objectNameSingular: activityTarget.targetObjectNameSingular,
id: activityTarget.targetObjectRecord.id,
}),
);
const initialCompanyIds = useMemo(
() =>
activityTargets
?.filter(({ companyId }) => companyId !== null)
.map(({ companyId }) => companyId)
.filter(assertNotNull) ?? [],
[activityTargets],
);
const initialSelectedEntityIds = useMemo(
() =>
[...initialPeopleIds, ...initialCompanyIds].reduce<
Record<string, boolean>
>((result, entityId) => ({ ...result, [entityId]: true }), {}),
[initialPeopleIds, initialCompanyIds],
);
const { findManyRecordsQuery: findManyPeopleQuery } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Person,
});
const { findManyRecordsQuery: findManyCompaniesQuery } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Company,
const { createManyRecords: createManyActivityTargets } =
useCreateManyRecords<ActivityTarget>({
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
});
const useFindManyPeopleQuery = (options: any) =>
useQuery(findManyPeopleQuery, options);
const { deleteManyRecords: deleteManyActivityTargets } = useDeleteManyRecords(
{
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
},
);
const useFindManyCompaniesQuery = (options: any) =>
useQuery(findManyCompaniesQuery, options);
const [selectedEntityIds, setSelectedEntityIds] = useState<
Record<string, boolean>
>(initialSelectedEntityIds);
const { identifiersMapper, searchQuery } = useRelationPicker();
const people = useFilteredSearchEntityQuery({
queryHook: useFindManyPeopleQuery,
filters: [
{
fieldNames: searchQuery?.computeFilterFields?.('person') ?? [],
filter: searchFilter,
},
],
orderByField: 'createdAt',
mappingFunction: (record: any) => identifiersMapper?.(record, 'person'),
selectedIds: initialPeopleIds,
objectNameSingular: CoreObjectNameSingular.Person,
limit: 3,
});
const companies = useFilteredSearchEntityQuery({
queryHook: useFindManyCompaniesQuery,
filters: [
{
fieldNames: searchQuery?.computeFilterFields?.('company') ?? [],
filter: searchFilter,
},
],
orderByField: 'createdAt',
mappingFunction: (record: any) => identifiersMapper?.(record, 'company'),
selectedIds: initialCompanyIds,
objectNameSingular: CoreObjectNameSingular.Company,
limit: 3,
});
const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
people.selectedEntities,
companies.selectedEntities,
]);
const filteredSelectedEntities =
flatMapAndSortEntityForSelectArrayOfArrayByName([
people.filteredSelectedEntities,
companies.filteredSelectedEntities,
]);
const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([
people.entitiesToSelect,
companies.entitiesToSelect,
]);
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
activityId,
currentActivityTargets: activityTargets,
});
const { closeInlineCell: closeEditableField } = useInlineCell();
const handleSubmit = useCallback(() => {
handleCheckItemsChange(
selectedEntityIds,
entitiesToSelect,
selectedEntities,
);
const handleSubmit = async (selectedRecords: ObjectRecordForSelect[]) => {
closeEditableField();
}, [
closeEditableField,
entitiesToSelect,
handleCheckItemsChange,
selectedEntities,
selectedEntityIds,
]);
const activityTargetRecordsToDelete = activityTargetObjectRecords.filter(
(activityTargetObjectRecord) =>
!selectedRecords.some(
(selectedRecord) =>
selectedRecord.recordIdentifier.id ===
activityTargetObjectRecord.targetObjectRecord.id,
),
);
const activityTargetRecordsToCreate = selectedRecords.filter(
(selectedRecord) =>
!activityTargetObjectRecords.some(
(activityTargetObjectRecord) =>
activityTargetObjectRecord.targetObjectRecord.id ===
selectedRecord.recordIdentifier.id,
),
);
if (activityTargetRecordsToCreate.length > 0) {
await createManyActivityTargets(
activityTargetRecordsToCreate.map((selectedRecord) => ({
id: v4(),
activityId,
[getActivityTargetObjectFieldIdName({
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
})]: selectedRecord.recordIdentifier.id,
})),
);
}
if (activityTargetRecordsToDelete.length > 0) {
await deleteManyActivityTargets(
activityTargetRecordsToDelete.map(
(activityTargetObjectRecord) =>
activityTargetObjectRecord.activityTargetRecord.id,
),
);
}
};
const handleCancel = () => {
closeEditableField();
@ -150,17 +95,8 @@ export const ActivityTargetInlineCellEditMode = ({
return (
<StyledSelectContainer>
<MultipleEntitySelect
entities={{
entitiesToSelect,
filteredSelectedEntities,
selectedEntities,
loading: false,
}}
onChange={setSelectedEntityIds}
onSearchFilterChange={setSearchFilter}
searchFilter={searchFilter}
value={selectedEntityIds}
<MultipleObjectRecordSelect
selectedObjectRecordIds={selectedObjectRecordIds}
onCancel={handleCancel}
onSubmit={handleSubmit}
/>

View File

@ -1,9 +1,8 @@
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
import { ActivityTargetInlineCellEditMode } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditMode';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { RecordInlineCellContainer } from '@/object-record/record-inline-cell/components/RecordInlineCellContainer';
import { FieldRecoilScopeContext } from '@/object-record/record-inline-cell/states/recoil-scope-contexts/FieldRecoilScopeContext';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
@ -23,14 +22,8 @@ type ActivityTargetsInlineCellProps = {
export const ActivityTargetsInlineCell = ({
activity,
}: ActivityTargetsInlineCellProps) => {
const activityTargetIds =
activity?.activityTargets?.edges?.map(
(activityTarget) => activityTarget.node.id,
) ?? [];
const { records: activityTargets } = useFindManyRecords<ActivityTarget>({
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
filter: { id: { in: activityTargetIds } },
const { activityTargetObjectRecords } = useActivityTargetObjectRecords({
activityId: activity?.id ?? '',
});
return (
@ -44,11 +37,15 @@ export const ActivityTargetsInlineCell = ({
editModeContent={
<ActivityTargetInlineCellEditMode
activityId={activity?.id ?? ''}
activityTargets={activityTargets as any}
activityTargetObjectRecords={activityTargetObjectRecords as any}
/>
}
label="Relations"
displayModeContent={<ActivityTargetChips targets={activityTargets} />}
displayModeContent={
<ActivityTargetChips
activityTargetObjectRecords={activityTargetObjectRecords}
/>
}
isDisplayModeContentEmpty={
activity?.activityTargets?.edges?.length === 0
}