Files
twenty_crm/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx
Marie 7eb69a78ef Relations many in table view (#5842)
Closes #5924.

Adding the "many" side of relations in the table view, and fixing some
issues (glitch in Multi record select, cache update after update).

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2024-06-27 11:28:03 +02:00

264 lines
9.6 KiB
TypeScript

import styled from '@emotion/styled';
import { isNull } from '@sniptt/guards';
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
import { v4 } from 'uuid';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { ActivityTargetObjectRecordEffect } from '@/activities/inline-cell/components/ActivityTargetObjectRecordEffect';
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 { activityTargetObjectRecordFamilyState } from '@/object-record/record-field/states/activityTargetObjectRecordFamilyState';
import { objectRecordMultiSelectCheckedRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectCheckedRecordsIdsComponentState';
import {
ObjectRecordAndSelected,
objectRecordMultiSelectComponentFamilyState,
} from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState';
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { ActivityTargetInlineCellEditModeMultiRecordsEffect } from '@/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect';
import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
import { prefillRecord } from '@/object-record/utils/prefillRecord';
const StyledSelectContainer = styled.div`
position: absolute;
left: 0;
top: 0;
`;
type ActivityTargetInlineCellEditModeProps = {
activity: Activity;
activityTargetWithTargetRecords: ActivityTargetWithTargetRecord[];
};
export const ActivityTargetInlineCellEditMode = ({
activity,
activityTargetWithTargetRecords,
}: ActivityTargetInlineCellEditModeProps) => {
const [isActivityInCreateMode] = useRecoilState(isActivityInCreateModeState);
const relationPickerScopeId = `relation-picker-${activity.id}`;
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 = useRecoilCallback(
({ snapshot }) =>
async () => {
const activityTargetsAfterUpdate =
activityTargetWithTargetRecords.filter((activityTarget) => {
const record = snapshot
.getLoadable(
objectRecordMultiSelectComponentFamilyState({
scopeId: relationPickerScopeId,
familyKey: activityTarget.targetObject.id,
}),
)
.getValue() as ObjectRecordAndSelected;
return record.selected;
});
setActivityFromStore((currentActivity) => {
if (isNull(currentActivity)) {
return null;
}
return {
...currentActivity,
activityTargets: activityTargetsAfterUpdate,
};
});
closeEditableField();
},
[
activityTargetWithTargetRecords,
closeEditableField,
relationPickerScopeId,
setActivityFromStore,
],
);
const handleChange = useRecoilCallback(
({ snapshot, set }) =>
async (recordId: string) => {
const existingActivityTargets = activityTargetWithTargetRecords.map(
(activityTargetObjectRecord) =>
activityTargetObjectRecord.activityTarget,
);
let activityTargetsAfterUpdate = Array.from(existingActivityTargets);
const previouslyCheckedRecordsIds = snapshot
.getLoadable(
objectRecordMultiSelectCheckedRecordsIdsComponentState({
scopeId: relationPickerScopeId,
}),
)
.getValue();
const isNewlySelected = !previouslyCheckedRecordsIds.includes(recordId);
if (isNewlySelected) {
const record = snapshot
.getLoadable(
objectRecordMultiSelectComponentFamilyState({
scopeId: relationPickerScopeId,
familyKey: recordId,
}),
)
.getValue();
if (!record) {
throw new Error(
`Could not find selected record with id ${recordId}`,
);
}
set(
objectRecordMultiSelectCheckedRecordsIdsComponentState({
scopeId: relationPickerScopeId,
}),
(prev) => [...prev, recordId],
);
const newActivityTargetId = v4();
const fieldName = getActivityTargetObjectFieldName({
nameSingular: record.objectMetadataItem.nameSingular,
});
const fieldNameWithIdSuffix = getActivityTargetObjectFieldIdName({
nameSingular: record.objectMetadataItem.nameSingular,
});
const newActivityTarget = prefillRecord<ActivityTarget>({
objectMetadataItem: objectMetadataItemActivityTarget,
input: {
id: newActivityTargetId,
activityId: activity.id,
activity,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
[fieldName]: record.record,
[fieldNameWithIdSuffix]: recordId,
},
});
activityTargetsAfterUpdate.push(newActivityTarget);
if (isActivityInCreateMode) {
createManyActivityTargetsInCache([newActivityTarget]);
upsertActivity({
activity,
input: {
activityTargets: activityTargetsAfterUpdate,
},
});
} else {
await createManyActivityTargets([newActivityTarget]);
}
set(activityTargetObjectRecordFamilyState(recordId), {
activityTargetId: newActivityTargetId,
});
} else {
const activityTargetToDeleteId = snapshot
.getLoadable(activityTargetObjectRecordFamilyState(recordId))
.getValue().activityTargetId;
if (!activityTargetToDeleteId) {
throw new Error('Could not delete this activity target.');
}
set(
objectRecordMultiSelectCheckedRecordsIdsComponentState({
scopeId: relationPickerScopeId,
}),
previouslyCheckedRecordsIds.filter((id) => id !== recordId),
);
activityTargetsAfterUpdate = activityTargetsAfterUpdate.filter(
(activityTarget) => activityTarget.id !== activityTargetToDeleteId,
);
if (isActivityInCreateMode) {
upsertActivity({
activity,
input: {
activityTargets: activityTargetsAfterUpdate,
},
});
} else {
await deleteManyActivityTargets([activityTargetToDeleteId]);
}
set(activityTargetObjectRecordFamilyState(recordId), {
activityTargetId: null,
});
}
},
[
activity,
activityTargetWithTargetRecords,
createManyActivityTargets,
createManyActivityTargetsInCache,
deleteManyActivityTargets,
isActivityInCreateMode,
objectMetadataItemActivityTarget,
relationPickerScopeId,
upsertActivity,
],
);
return (
<StyledSelectContainer>
<RelationPickerScope relationPickerScopeId={relationPickerScopeId}>
<ActivityTargetObjectRecordEffect
activityTargetWithTargetRecords={activityTargetWithTargetRecords}
/>
<ActivityTargetInlineCellEditModeMultiRecordsEffect
selectedObjectRecordIds={selectedTargetObjectIds}
/>
<MultiRecordSelect onSubmit={handleSubmit} onChange={handleChange} />
</RelationPickerScope>
</StyledSelectContainer>
);
};