Files
twenty/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx
T
Marie b84042ddbb Display and update fields from fromManyObjects relations in Show card (#5801)
In this PR, we implement the display and update of fields from
fromManyObjects (e.g update Employees for a Company).

Product requirement
- update should be triggered at each box check/uncheck, not at lose of
focus

Left to do in upcoming PRs
- add the column in the table views (e.g. column "Employees" on
"Companies" table view)
- add "Add new" possibility when there is no records (as is currently
exists for "one" side of relations:)
<img width="374" alt="Capture d’écran 2024-06-10 à 17 38 02"
src="https://github.com/twentyhq/twenty/assets/51697796/6f0cc494-e44f-4620-a762-d7b438951eec">

- update cache after an update affecting other records (e.g "Listings"
have one "Person"; if listing A belonged to Person A but then we
attribute listing A to Person B, Person A is no longer owner of Listing
A. For the moment that would not be reflected immediatly leading, to
potential false information if information is accessed from cache)
- try to get rid of the glitch - we also have it on the task page
example. (probably) due to the fact that we are using a recoil state to
read, update then re-read


https://github.com/twentyhq/twenty/assets/51697796/54f71674-237a-4946-866e-b8d96353c458
2024-06-11 15:53:17 +02:00

183 lines
6.5 KiB
TypeScript

import styled from '@emotion/styled';
import { isNonEmptyArray, isNull } from '@sniptt/guards';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { v4 } from 'uuid';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { ActivityTargetWithTargetRecord } from '@/activities/types/ActivityTargetObject';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { getActivityTargetObjectFieldName } from '@/activities/utils/getActivityTargetObjectFieldName';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateManyRecordsInCache } from '@/object-record/cache/hooks/useCreateManyRecordsInCache';
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 { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { MultipleObjectRecordSelect } from '@/object-record/relation-picker/components/MultipleObjectRecordSelect';
import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
import { prefillRecord } from '@/object-record/utils/prefillRecord';
const StyledSelectContainer = styled.div`
left: 0px;
position: absolute;
top: -8px;
`;
type ActivityTargetInlineCellEditModeProps = {
activity: Activity;
activityTargetWithTargetRecords: ActivityTargetWithTargetRecord[];
};
export const ActivityTargetInlineCellEditMode = ({
activity,
activityTargetWithTargetRecords,
}: ActivityTargetInlineCellEditModeProps) => {
const [isActivityInCreateMode] = useRecoilState(isActivityInCreateModeState);
const selectedTargetObjectIds = activityTargetWithTargetRecords.map(
(activityTarget) => ({
objectNameSingular: activityTarget.targetObjectMetadataItem.nameSingular,
id: activityTarget.targetObject.id,
}),
);
const { createManyRecords: createManyActivityTargets } =
useCreateManyRecords<ActivityTarget>({
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
});
const { deleteManyRecords: deleteManyActivityTargets } = useDeleteManyRecords(
{
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
},
);
const { closeInlineCell: closeEditableField } = useInlineCell();
const { upsertActivity } = useUpsertActivity();
const { objectMetadataItem: objectMetadataItemActivityTarget } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
});
const setActivityFromStore = useSetRecoilState(
recordStoreFamilyState(activity.id),
);
const { createManyRecordsInCache: createManyActivityTargetsInCache } =
useCreateManyRecordsInCache<ActivityTarget>({
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
});
const handleSubmit = async (selectedRecords: ObjectRecordForSelect[]) => {
closeEditableField();
const activityTargetsToDelete = activityTargetWithTargetRecords.filter(
(activityTargetObjectRecord) =>
!selectedRecords.some(
(selectedRecord) =>
selectedRecord.recordIdentifier.id ===
activityTargetObjectRecord.targetObject.id,
),
);
const selectedTargetObjectsToCreate = selectedRecords.filter(
(selectedRecord) =>
!activityTargetWithTargetRecords.some(
(activityTargetWithTargetRecord) =>
activityTargetWithTargetRecord.targetObject.id ===
selectedRecord.recordIdentifier.id,
),
);
const existingActivityTargets = activityTargetWithTargetRecords.map(
(activityTargetObjectRecord) => activityTargetObjectRecord.activityTarget,
);
let activityTargetsAfterUpdate = Array.from(existingActivityTargets);
const activityTargetsToCreate = selectedTargetObjectsToCreate.map(
(selectedRecord) => {
const emptyActivityTarget = prefillRecord<ActivityTarget>({
objectMetadataItem: objectMetadataItemActivityTarget,
input: {
id: v4(),
activityId: activity.id,
activity,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
[getActivityTargetObjectFieldName({
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
})]: selectedRecord.record,
[getActivityTargetObjectFieldIdName({
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
})]: selectedRecord.recordIdentifier.id,
},
});
return emptyActivityTarget;
},
);
activityTargetsAfterUpdate.push(...activityTargetsToCreate);
if (isNonEmptyArray(activityTargetsToDelete)) {
activityTargetsAfterUpdate = activityTargetsAfterUpdate.filter(
(activityTarget) =>
!activityTargetsToDelete.some(
(activityTargetToDelete) =>
activityTargetToDelete.activityTarget.id === activityTarget.id,
),
);
}
if (isActivityInCreateMode) {
createManyActivityTargetsInCache(activityTargetsToCreate);
upsertActivity({
activity,
input: {
activityTargets: activityTargetsAfterUpdate,
},
});
} else {
if (activityTargetsToCreate.length > 0) {
await createManyActivityTargets(activityTargetsToCreate);
}
if (activityTargetsToDelete.length > 0) {
await deleteManyActivityTargets(
activityTargetsToDelete.map(
(activityTargetObjectRecord) =>
activityTargetObjectRecord.activityTarget.id,
),
);
}
}
setActivityFromStore((currentActivity) => {
if (isNull(currentActivity)) {
return null;
}
return {
...currentActivity,
activityTargets: activityTargetsAfterUpdate,
};
});
};
return (
<StyledSelectContainer>
<MultipleObjectRecordSelect
selectedObjectRecordIds={selectedTargetObjectIds}
onSubmit={handleSubmit}
/>
</StyledSelectContainer>
);
};