Fix favorites (#3138)

* WIP

* Finished cleaning favorites create, update, delete on record show page

* Fixed context menu favorite

* Fixed relation field bug

* Fix from review

* Review

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2024-01-03 12:30:24 +01:00
committed by GitHub
parent 41f3a74bf4
commit 6797f013c9
18 changed files with 317 additions and 299 deletions

View File

@ -15,9 +15,7 @@ const StyledContainer = styled(NavigationDrawerSection)`
`;
export const Favorites = () => {
const { favorites, handleReorderFavorite } = useFavorites({
objectNamePlural: 'companies',
});
const { favorites, handleReorderFavorite } = useFavorites();
if (!favorites || favorites.length === 0) return <></>;

View File

@ -1,215 +1,160 @@
import { useApolloClient } from '@apollo/client';
import { useMemo } from 'react';
import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilValue } from 'recoil';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { useOptimisticEvict } from '@/apollo/optimistic-effect/hooks/useOptimisticEvict';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { Favorite } from '@/favorites/types/Favorite';
import { mapFavorites } from '@/favorites/utils/mapFavorites';
import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
import { favoritesState } from '../states/favoritesState';
export const useFavorites = ({
objectNamePlural,
}: {
objectNamePlural: string;
}) => {
export const useFavorites = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const [favorites, setFavorites] = useRecoilState(favoritesState);
const favoriteObjectNameSingular = 'favorite';
const {
updateOneRecordMutation: updateOneFavoriteMutation,
createOneRecordMutation: createOneFavoriteMutation,
deleteOneRecordMutation: deleteOneFavoriteMutation,
} = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const { triggerOptimisticEffects } = useOptimisticEffect({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const { performOptimisticEvict } = useOptimisticEvict();
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem: favoriteTargetObjectMetadataItem } =
const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular,
objectNameSingular: favoriteObjectNameSingular,
});
const apolloClient = useApolloClient();
useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.Favorite,
onCompleted: useRecoilCallback(
({ snapshot, set }) =>
async (data: PaginatedRecordTypeResults<Required<Favorite>>) => {
const favorites = snapshot.getLoadable(favoritesState).getValue();
const queriedFavorites = mapFavorites(
data.edges.map((edge) => edge.node),
);
if (!isDeeplyEqual(favorites, queriedFavorites)) {
set(favoritesState, queriedFavorites);
}
},
[],
),
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: favoriteObjectNameSingular,
});
const createFavorite = useRecoilCallback(
({ snapshot, set }) =>
async (favoriteTargetObjectId: string, additionalData?: any) => {
const favorites = snapshot.getLoadable(favoritesState).getValue();
const { updateOneRecord: updateOneFavorite } = useUpdateOneRecord({
objectNameSingular: favoriteObjectNameSingular,
});
if (!favoriteTargetObjectMetadataItem) {
return;
}
const targetObjectName = favoriteTargetObjectMetadataItem.nameSingular;
const { createOneRecord: createOneFavorite } = useCreateOneRecord({
objectNameSingular: favoriteObjectNameSingular,
});
const result = await apolloClient.mutate({
mutation: createOneFavoriteMutation,
variables: {
input: {
[`${targetObjectName}Id`]: favoriteTargetObjectId,
position: favorites.length + 1,
workspaceMemberId: currentWorkspaceMember?.id,
},
},
});
const { records: favorites } = useFindManyRecords<Favorite>({
objectNameSingular: favoriteObjectNameSingular,
});
triggerOptimisticEffects({
typename: `FavoriteEdge`,
createdRecords: [result.data[`createFavorite`]],
});
const createdFavorite = result?.data?.createFavorite;
const newFavorite = {
...additionalData,
...createdFavorite,
};
const newFavoritesMapped = mapFavorites([newFavorite]);
if (createdFavorite) {
set(favoritesState, [...favorites, ...newFavoritesMapped]);
}
},
[
apolloClient,
createOneFavoriteMutation,
currentWorkspaceMember?.id,
favoriteTargetObjectMetadataItem,
triggerOptimisticEffects,
],
const favoriteRelationFieldMetadataItems = useMemo(
() =>
favoriteObjectMetadataItem.fields.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type === FieldMetadataType.Relation &&
fieldMetadataItem.name !== 'workspaceMember',
),
[favoriteObjectMetadataItem.fields],
);
const _updateFavoritePosition = useRecoilCallback(
({ snapshot, set }) =>
async (favoriteToUpdate: Favorite) => {
const favoritesStateFromSnapshot = snapshot.getLoadable(favoritesState);
const favorites = favoritesStateFromSnapshot.getValue();
const result = await apolloClient.mutate({
mutation: updateOneFavoriteMutation,
variables: {
input: {
position: favoriteToUpdate?.position,
},
idToUpdate: favoriteToUpdate?.id,
},
});
const getObjectRecordIdentifierByNameSingular =
useGetObjectRecordIdentifierByNameSingular();
const updatedFavorite = result?.data?.updateFavoriteV2;
if (updatedFavorite) {
set(
favoritesState,
favorites.map((favorite: Favorite) =>
favorite.id === updatedFavorite.id ? favoriteToUpdate : favorite,
),
);
const favoritesSorted = useMemo(() => {
return favorites
.map((favorite) => {
for (const relationField of favoriteRelationFieldMetadataItems) {
if (isDefined(favorite[relationField.name])) {
const relationObject = favorite[relationField.name];
const relationObjectNameSingular =
relationField.toRelationMetadata?.fromObjectMetadata
.nameSingular ?? '';
const objectRecordIdentifier =
getObjectRecordIdentifierByNameSingular(
relationObject,
relationObjectNameSingular,
);
return {
id: favorite.id,
recordId: objectRecordIdentifier.id,
position: favorite.position,
avatarType: objectRecordIdentifier.avatarType,
avatarUrl: objectRecordIdentifier.avatarUrl,
labelIdentifier: objectRecordIdentifier.name,
link: objectRecordIdentifier.linkToShowPage,
} as Favorite;
}
}
},
[apolloClient, updateOneFavoriteMutation],
);
const deleteFavorite = useRecoilCallback(
({ snapshot, set }) =>
async (favoriteIdToDelete: string) => {
const favoritesStateFromSnapshot = snapshot.getLoadable(favoritesState);
const favorites = favoritesStateFromSnapshot.getValue();
const idToDelete = favorites.find(
(favorite: Favorite) => favorite.recordId === favoriteIdToDelete,
)?.id;
return favorite;
})
.toSorted((a, b) => a.position - b.position);
}, [
favoriteRelationFieldMetadataItems,
favorites,
getObjectRecordIdentifierByNameSingular,
]);
await apolloClient.mutate({
mutation: deleteOneFavoriteMutation,
variables: {
idToDelete: idToDelete,
},
});
const createFavorite = (
targetObject: Record<string, any>,
targetObjectNameSingular: string,
) => {
createOneFavorite({
[`${targetObjectNameSingular}Id`]: targetObject.id,
[`${targetObjectNameSingular}`]: targetObject,
position: favorites.length + 1,
workspaceMemberId: currentWorkspaceMember?.id,
});
};
performOptimisticEvict('Favorite', 'id', idToDelete ?? '');
set(
favoritesState,
favorites.filter((favorite: Favorite) => favorite.id !== idToDelete),
);
},
[apolloClient, deleteOneFavoriteMutation, performOptimisticEvict],
);
const deleteFavorite = (favoriteId: string) => {
deleteOneRecord(favoriteId);
};
const computeNewPosition = (destIndex: number, sourceIndex: number) => {
if (destIndex === 0) {
return favorites[destIndex].position / 2;
}
const moveToFirstPosition = destIndex === 0;
const moveToLastPosition = destIndex === favoritesSorted.length - 1;
const moveAfterSource = destIndex > sourceIndex;
if (destIndex === favorites.length - 1) {
return favorites[destIndex - 1].position + 1;
}
if (sourceIndex < destIndex) {
if (moveToFirstPosition) {
return favoritesSorted[0].position / 2;
} else if (moveToLastPosition) {
return favoritesSorted[destIndex - 1].position + 1;
} else if (moveAfterSource) {
return (
(favorites[destIndex + 1].position + favorites[destIndex].position) / 2
(favoritesSorted[destIndex + 1].position +
favoritesSorted[destIndex].position) /
2
);
} else {
return (
favoritesSorted[destIndex].position -
(favoritesSorted[destIndex].position -
favoritesSorted[destIndex - 1].position) /
2
);
}
return (
(favorites[destIndex - 1].position + favorites[destIndex].position) / 2
);
};
const handleReorderFavorite: OnDragEndResponder = (result) => {
if (!result.destination || !favorites) {
if (!result.destination || !favoritesSorted) {
return;
}
const newPosition = computeNewPosition(
result.destination.index,
result.source.index,
);
const reorderFavorites = Array.from(favorites);
const [removed] = reorderFavorites.splice(result.source.index, 1);
const removedFav = { ...removed, position: newPosition };
reorderFavorites.splice(result.destination.index, 0, removedFav);
setFavorites(reorderFavorites);
_updateFavoritePosition(removedFav);
const updatedFavorite = favoritesSorted[result.source.index];
updateOneFavorite({
idToUpdate: updatedFavorite.id,
updateOneRecordInput: {
position: newPosition,
},
});
};
return {
favorites,
favorites: favoritesSorted,
createFavorite,
deleteFavorite,
handleReorderFavorite,
deleteFavorite,
};
};