From a6d8cdb11695b3ab998ac3727601e0532a9b8ad4 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Fri, 17 Nov 2023 16:24:43 +0100 Subject: [PATCH] Fix context menu and favorites (#2564) --- .../useCompanyTableContextMenuEntries.tsx | 4 +- .../favorites/components/Favorites.tsx | 5 +- .../modules/favorites/hooks/useFavorites.ts | 40 ++++-- .../components/RecordShowPage.tsx | 12 +- .../components/RecordTableEffect.tsx | 9 ++ .../useRecordTableContextMenuEntries.tsx | 133 ++++++++++++++++++ .../usePersonTableContextMenuEntries.tsx | 4 +- front/src/modules/users/components/Avatar.tsx | 6 +- front/src/pages/companies/CompanyShow.tsx | 4 +- front/src/pages/people/PersonShow.tsx | 4 +- .../metadata/field-metadata/company.ts | 3 +- .../metadata/field-metadata/favorite.ts | 8 +- 12 files changed, 197 insertions(+), 35 deletions(-) create mode 100644 front/src/modules/object-record/hooks/useRecordTableContextMenuEntries.tsx diff --git a/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx b/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx index f1a899e50..18ff56966 100644 --- a/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx +++ b/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx @@ -36,7 +36,9 @@ export const useCompanyTableContextMenuEntries = () => { const { data } = useGetFavoritesQuery(); const favorites = data?.findFavorites; - const { createFavorite, deleteFavorite } = useFavorites(); + const { createFavorite, deleteFavorite } = useFavorites({ + objectNamePlural: 'companies', + }); const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => { const selectedRowIds = snapshot diff --git a/front/src/modules/favorites/components/Favorites.tsx b/front/src/modules/favorites/components/Favorites.tsx index 5cce73c0f..d179bef83 100644 --- a/front/src/modules/favorites/components/Favorites.tsx +++ b/front/src/modules/favorites/components/Favorites.tsx @@ -24,7 +24,10 @@ const StyledContainer = styled.div` `; export const Favorites = () => { - const { favorites, handleReorderFavorite } = useFavorites(); + // This is only temporary and will be refactored once we have main identifiers + const { favorites, handleReorderFavorite } = useFavorites({ + objectNamePlural: 'companiesV2', + }); const [allCompanies, setAllCompanies] = useState< Record >({}); diff --git a/front/src/modules/favorites/hooks/useFavorites.ts b/front/src/modules/favorites/hooks/useFavorites.ts index c70f2f16e..72d1269cc 100644 --- a/front/src/modules/favorites/hooks/useFavorites.ts +++ b/front/src/modules/favorites/hooks/useFavorites.ts @@ -8,38 +8,45 @@ import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOne import { favoritesState } from '../states/favoritesState'; -export const useFavorites = () => { +export const useFavorites = ({ + objectNamePlural, +}: { + objectNamePlural: string | undefined; +}) => { const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const [favorites, setFavorites] = useRecoilState(favoritesState); const { updateOneMutation, createOneMutation, deleteOneMutation } = useFindOneObjectMetadataItem({ - objectNameSingular: 'favoriteV2', + objectNamePlural: 'favoritesV2', + }); + + const { foundObjectMetadataItem: favoriteTargetObjectMetadataItem } = + useFindOneObjectMetadataItem({ + objectNamePlural, }); const apolloClient = useApolloClient(); const createFavorite = useRecoilCallback( ({ snapshot, set }) => - async ( - favoriteNameToCreate: string, - favoriteIdToCreate: string, - additionalData?: any, - ) => { + async (favoriteTargetObjectId: string, additionalData?: any) => { const favoritesStateFromSnapshot = snapshot.getLoadable(favoritesState); const favorites = favoritesStateFromSnapshot.getValue(); - if (!favoriteNameToCreate || !favoriteIdToCreate) { - return; - } + + const targetObjectName = + favoriteTargetObjectMetadataItem?.nameSingular.replace('V2', '') ?? + ''; const result = await apolloClient.mutate({ mutation: createOneMutation, variables: { input: { - [favoriteNameToCreate]: favoriteIdToCreate, + [`${targetObjectName}Id`]: favoriteTargetObjectId, position: favorites.length + 1, - workspaceMember: currentWorkspaceMember?.id, + // workspaceMember: currentWorkspaceMember?.id, + workspaceMemberId: currentWorkspaceMember?.id, }, }, }); @@ -48,14 +55,19 @@ export const useFavorites = () => { const newFavorite = { ...additionalData, - ...createdFavorite[favoriteNameToCreate], + ...createdFavorite[targetObjectName], }; if (createdFavorite) { set(favoritesState, [...favorites, newFavorite]); } }, - [apolloClient, createOneMutation, currentWorkspaceMember?.id], + [ + apolloClient, + createOneMutation, + currentWorkspaceMember?.id, + favoriteTargetObjectMetadataItem, + ], ); const _updateFavoritePosition = useRecoilCallback( diff --git a/front/src/modules/object-record/components/RecordShowPage.tsx b/front/src/modules/object-record/components/RecordShowPage.tsx index 29cd65d75..5c914ee58 100644 --- a/front/src/modules/object-record/components/RecordShowPage.tsx +++ b/front/src/modules/object-record/components/RecordShowPage.tsx @@ -36,14 +36,16 @@ export const RecordShowPage = () => { objectMetadataId: string; }>(); - const { favorites, createFavorite, deleteFavorite } = useFavorites(); - const { icons } = useLazyLoadIcons(); const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({ objectNameSingular, }); + const { favorites, createFavorite, deleteFavorite } = useFavorites({ + objectNamePlural: foundObjectMetadataItem?.namePlural, + }); + const [, setEntityFields] = useRecoilState( entityFieldsFamilyState(objectMetadataId ?? ''), ); @@ -106,11 +108,7 @@ export const RecordShowPage = () => { recordId: object.id, } : {}; - createFavorite( - objectNameSingular.replace('V2', ''), - object.id, - additionalData, - ); + createFavorite(object.id, additionalData); } }; diff --git a/front/src/modules/object-record/components/RecordTableEffect.tsx b/front/src/modules/object-record/components/RecordTableEffect.tsx index 3c3f921f6..2d99f1b5b 100644 --- a/front/src/modules/object-record/components/RecordTableEffect.tsx +++ b/front/src/modules/object-record/components/RecordTableEffect.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react'; import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; +import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries'; import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable'; import { useView } from '@/views/hooks/useView'; @@ -56,5 +57,13 @@ export const RecordTableEffect = () => { setAvailableTableColumns, ]); + const { setActionBarEntries, setContextMenuEntries } = + useRecordTableContextMenuEntries(); + + useEffect(() => { + setActionBarEntries?.(); + setContextMenuEntries?.(); + }, [setActionBarEntries, setContextMenuEntries]); + return <>; }; diff --git a/front/src/modules/object-record/hooks/useRecordTableContextMenuEntries.tsx b/front/src/modules/object-record/hooks/useRecordTableContextMenuEntries.tsx new file mode 100644 index 000000000..e26961f00 --- /dev/null +++ b/front/src/modules/object-record/hooks/useRecordTableContextMenuEntries.tsx @@ -0,0 +1,133 @@ +import { useRecoilCallback, useSetRecoilState } from 'recoil'; + +import { useFavorites } from '@/favorites/hooks/useFavorites'; +import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord'; +import { + IconCheckbox, + IconHeart, + IconHeartOff, + IconNotes, + IconTrash, +} from '@/ui/display/icon'; +import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; +import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState'; +import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable'; +import { selectedRowIdsSelector } from '@/ui/object/record-table/states/selectors/selectedRowIdsSelector'; +import { tableRowIdsState } from '@/ui/object/record-table/states/tableRowIdsState'; +import { useGetFavoritesQuery } from '~/generated/graphql'; + +export const useRecordTableContextMenuEntries = () => { + const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState); + const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState); + + const setTableRowIds = useSetRecoilState(tableRowIdsState); + + const { scopeId: objectNamePlural, resetTableRowSelection } = + useRecordTable(); + + const { data } = useGetFavoritesQuery(); + const favorites = data?.findFavorites; + + const { createFavorite, deleteFavorite } = useFavorites({ objectNamePlural }); + + const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => { + const selectedRowIds = snapshot + .getLoadable(selectedRowIdsSelector) + .getValue(); + + const selectedRowId = selectedRowIds.length === 1 ? selectedRowIds[0] : ''; + + const isFavorite = + !!selectedRowId && + !!favorites?.find((favorite) => favorite.company?.id === selectedRowId); + + resetTableRowSelection(); + + if (isFavorite) { + deleteFavorite(selectedRowId); + } else { + createFavorite(selectedRowId); + } + }); + + const { deleteOneObject } = useDeleteOneObjectRecord({ + objectNamePlural, + }); + + const handleDeleteClick = useRecoilCallback(({ snapshot }) => async () => { + const rowIdsToDelete = snapshot + .getLoadable(selectedRowIdsSelector) + .getValue(); + + resetTableRowSelection(); + + if (deleteOneObject) { + for (const rowId of rowIdsToDelete) { + await deleteOneObject(rowId); + } + + setTableRowIds((tableRowIds) => + tableRowIds.filter((id) => !rowIdsToDelete.includes(id)), + ); + } + }); + + return { + setContextMenuEntries: useRecoilCallback(({ snapshot }) => () => { + const selectedRowIds = snapshot + .getLoadable(selectedRowIdsSelector) + .getValue(); + + const selectedRowId = + selectedRowIds.length === 1 ? selectedRowIds[0] : ''; + + const isFavorite = + !!selectedRowId && + !!favorites?.find((favorite) => favorite.company?.id === selectedRowId); + + setContextMenuEntries([ + { + label: 'New task', + Icon: IconCheckbox, + onClick: () => {}, + }, + { + label: 'New note', + Icon: IconNotes, + onClick: () => {}, + }, + { + label: isFavorite ? 'Remove from favorites' : 'Add to favorites', + Icon: isFavorite ? IconHeartOff : IconHeart, + onClick: () => handleFavoriteButtonClick(), + }, + { + label: 'Delete', + Icon: IconTrash, + accent: 'danger', + onClick: () => handleDeleteClick(), + }, + ]); + }), + setActionBarEntries: useRecoilCallback(() => () => { + setActionBarEntriesState([ + { + label: 'Task', + Icon: IconCheckbox, + onClick: () => {}, + }, + { + label: 'Note', + Icon: IconNotes, + onClick: () => {}, + }, + { + label: 'Delete', + Icon: IconTrash, + accent: 'danger', + onClick: () => handleDeleteClick(), + }, + ]); + }), + }; +}; diff --git a/front/src/modules/people/hooks/usePersonTableContextMenuEntries.tsx b/front/src/modules/people/hooks/usePersonTableContextMenuEntries.tsx index ba4a3f268..78841d6a1 100644 --- a/front/src/modules/people/hooks/usePersonTableContextMenuEntries.tsx +++ b/front/src/modules/people/hooks/usePersonTableContextMenuEntries.tsx @@ -36,7 +36,9 @@ export const usePersonTableContextMenuEntries = () => { const { data } = useGetFavoritesQuery(); const favorites = data?.findFavorites; - const { createFavorite, deleteFavorite } = useFavorites(); + const { createFavorite, deleteFavorite } = useFavorites({ + objectNamePlural: 'people', + }); const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => { const selectedRowIds = snapshot diff --git a/front/src/modules/users/components/Avatar.tsx b/front/src/modules/users/components/Avatar.tsx index 96861ecb5..bd26e4d01 100644 --- a/front/src/modules/users/components/Avatar.tsx +++ b/front/src/modules/users/components/Avatar.tsx @@ -13,7 +13,7 @@ export type AvatarSize = 'xl' | 'lg' | 'md' | 'sm' | 'xs'; export type AvatarProps = { avatarUrl: string | null | undefined; size?: AvatarSize; - placeholder: string; + placeholder: string | undefined; colorId?: string; type?: AvatarType; onClick?: () => void; @@ -118,11 +118,11 @@ export const Avatar = ({ placeholder={placeholder} size={size} type={type} - colorId={colorId} + colorId={colorId ?? ''} onClick={onClick} > {(noAvatarUrl || isInvalidAvatarUrl) && - placeholder[0]?.toLocaleUpperCase()} + placeholder?.[0]?.toLocaleUpperCase()} ); }; diff --git a/front/src/pages/companies/CompanyShow.tsx b/front/src/pages/companies/CompanyShow.tsx index ef0df3472..36eb03d9c 100644 --- a/front/src/pages/companies/CompanyShow.tsx +++ b/front/src/pages/companies/CompanyShow.tsx @@ -31,7 +31,9 @@ import { companyShowFieldDefinitions } from './constants/companyShowFieldDefinit export const CompanyShow = () => { const companyId = useParams().companyId ?? ''; - const { createFavorite, deleteFavorite } = useFavorites(); + const { createFavorite, deleteFavorite } = useFavorites({ + objectNamePlural: 'companies', + }); const navigate = useNavigate(); const { data, loading } = useCompanyQuery(companyId); const company = data?.findUniqueCompany; diff --git a/front/src/pages/people/PersonShow.tsx b/front/src/pages/people/PersonShow.tsx index b666db52c..777652bef 100644 --- a/front/src/pages/people/PersonShow.tsx +++ b/front/src/pages/people/PersonShow.tsx @@ -34,7 +34,9 @@ import { personShowFieldDefinition } from './constants/personShowFieldDefinition export const PersonShow = () => { const personId = useParams().personId ?? ''; - const { createFavorite, deleteFavorite } = useFavorites(); + const { createFavorite, deleteFavorite } = useFavorites({ + objectNamePlural: 'peopleV2', + }); const navigate = useNavigate(); const { data, loading } = usePersonQuery(personId); diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/company.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/company.ts index e32195ef3..589634714 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/company.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/company.ts @@ -125,11 +125,10 @@ export const seedCompanyFieldMetadata = async ( }, description: 'The company name', icon: 'IconBuildingSkyscraper', - isNullable: false, + isNullable: true, isSystem: false, defaultValue: { value: '' }, }, - // Scalar Fields { id: SeedCompanyFieldMetadataIds.DomainName, diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/favorite.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/favorite.ts index bcd49fd51..e490d8658 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/favorite.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/favorite.ts @@ -135,7 +135,7 @@ export const seedFavoriteFieldMetadata = async ( targetColumnMap: {}, description: 'Favorite workspace member', icon: 'IconCircleUser', - isNullable: false, + isNullable: true, isSystem: false, defaultValue: undefined, }, @@ -151,7 +151,7 @@ export const seedFavoriteFieldMetadata = async ( targetColumnMap: {}, description: 'Foreign key for workspace member', icon: undefined, - isNullable: false, + isNullable: true, isSystem: true, defaultValue: undefined, }, @@ -185,7 +185,7 @@ export const seedFavoriteFieldMetadata = async ( targetColumnMap: {}, description: 'Foreign key for person', icon: undefined, - isNullable: false, + isNullable: true, isSystem: true, defaultValue: undefined, }, @@ -217,7 +217,7 @@ export const seedFavoriteFieldMetadata = async ( targetColumnMap: {}, description: 'Foreign key for company', icon: undefined, - isNullable: false, + isNullable: true, isSystem: true, defaultValue: undefined, },