diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx index b4c7e1bf357c..a4961ccf78d2 100644 --- a/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx @@ -14,11 +14,13 @@ export const RecordShowActionMenu = ({ record, objectMetadataItem, objectNameSingular, + handleFavoriteButtonClick, }: { isFavorite: boolean; record: ObjectRecord | undefined; objectMetadataItem: ObjectMetadataItem; objectNameSingular: string; + handleFavoriteButtonClick: () => void; }) => { const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( contextStoreCurrentObjectMetadataIdComponentState, @@ -41,6 +43,7 @@ export const RecordShowActionMenu = ({ record, objectMetadataItem, objectNameSingular, + handleFavoriteButtonClick, }} /> diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx index 70019c3ce175..a32cb4ebc755 100644 --- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx +++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx @@ -44,7 +44,7 @@ export const CurrentWorkspaceMemberFavorites = ({ onToggle, }: CurrentWorkspaceMemberFavoritesProps) => { const currentPath = useLocation().pathname; - const currentPathView = useLocation().pathname + useLocation().search; + const currentViewPath = useLocation().pathname + useLocation().search; const theme = useTheme(); const [isRenaming, setIsRenaming] = useState(false); @@ -57,7 +57,7 @@ export const CurrentWorkspaceMemberFavorites = ({ ); const selectedFavoriteIndex = folder.favorites.findIndex((favorite) => favorite.objectNameSingular === 'view' - ? favorite.link === currentPathView + ? favorite.link === currentViewPath : favorite.link === currentPath, ); const { deleteFavorite, handleReorderFavorite } = useFavorites(); @@ -189,7 +189,7 @@ export const CurrentWorkspaceMemberFavorites = ({ to={favorite.link} active={ favorite.objectNameSingular === 'view' - ? favorite.link === currentPathView + ? favorite.link === currentViewPath : favorite.link === currentPath } subItemState={getNavigationSubItemState({ diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolder.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolder.tsx index 1d0b9f7e86b1..a3b8e6fc5429 100644 --- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolder.tsx +++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolder.tsx @@ -1,26 +1,24 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useLocation } from 'react-router-dom'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { IconFolderPlus, IconHeartOff, isDefined } from 'twenty-ui'; + import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { CurrentWorkspaceMemberFavorites } from '@/favorites/components/CurrentWorkspaceMemberFavorites'; import { FavoriteIcon } from '@/favorites/components/FavoriteIcon'; +import { FavoriteFolders } from '@/favorites/components/FavoritesFolders'; import { FavoritesSkeletonLoader } from '@/favorites/components/FavoritesSkeletonLoader'; -import { FavoriteFolderHotkeyScope } from '@/favorites/constants/FavoriteFolderRightIconDropdownHotkeyScope'; -import { useFavoriteFolders } from '@/favorites/hooks/useFavoriteFolders'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { isFavoriteFolderCreatingState } from '@/favorites/states/isFavoriteFolderCreatingState'; import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList'; import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; -import { NavigationDrawerInput } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerInput'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { useRecoilState, useRecoilValue } from 'recoil'; -import { IconFolder, IconFolderPlus, IconHeartOff, isDefined } from 'twenty-ui'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; const StyledContainer = styled(NavigationDrawerSection)` width: 100%; @@ -37,58 +35,25 @@ const StyledNavigationDrawerItem = styled(NavigationDrawerItem)` `; export const CurrentWorkspaceMemberFavoritesFolders = () => { - const [activeFolderId, setActiveFolderId] = useState(null); - const { createFolder, favoriteFolder } = useFavoriteFolders(); - const { deleteFavorite } = useFavorites(); - const [folderName, setFolderName] = useState(''); const currentPath = useLocation().pathname; - const currentPathView = useLocation().pathname + useLocation().search; - const [isFavoriteFolderCreating, setIsFavoriteFolderCreating] = - useRecoilState(isFavoriteFolderCreatingState); + const currentViewPath = useLocation().pathname + useLocation().search; const theme = useTheme(); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); - const { favorites, handleReorderFavorite, favoritesByFolder } = - useFavorites(); + const { favorites, handleReorderFavorite, deleteFavorite } = useFavorites(); + const [isFavoriteFolderCreating, setIsFavoriteFolderCreating] = + useRecoilState(isFavoriteFolderCreatingState); + + const isFavoriteFolderEnabled = useIsFeatureEnabled( + 'IS_FAVORITE_FOLDER_ENABLED', + ); const loading = useIsPrefetchLoading(); + const { toggleNavigationSection, isNavigationSectionOpenState } = useNavigationSection('Favorites'); const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState); - const handleFolderNameChange = (value: string) => { - setFolderName(value); - }; - - const handleSubmitFolder = async (value: string) => { - if (!value) return false; - - setIsFavoriteFolderCreating(false); - setFolderName(''); - await createFolder(value); - return true; - }; - - const handleClickOutside = async ( - event: MouseEvent | TouchEvent, - value: string, - ) => { - if (!value) { - setIsFavoriteFolderCreating(false); - return; - } - - setIsFavoriteFolderCreating(false); - setFolderName(''); - await createFolder(value); - }; - - const handleCancel = () => { - setFolderName(''); - setIsFavoriteFolderCreating(false); - }; - const toggleNewFolder = () => { setIsFavoriteFolderCreating((current) => !current); - setFolderName(''); }; if (loading && isDefined(currentWorkspaceMember)) { @@ -99,59 +64,42 @@ export const CurrentWorkspaceMemberFavoritesFolders = () => { (favorite) => favorite.workspaceMemberId === currentWorkspaceMember?.id, ); + const unorganisedFavorites = currentWorkspaceMemberFavorites.filter( + (favorite) => !favorite.favoriteFolderId, + ); + if ( (!currentWorkspaceMemberFavorites || currentWorkspaceMemberFavorites.length === 0) && - (!favoriteFolder || favoriteFolder.length === 0) && !isFavoriteFolderCreating ) { - return <>; + return null; } - const unorganisedFavorites = currentWorkspaceMemberFavorites.filter( - (favorite) => !favorite.favoriteFolderId, - ); - const isGroup = - currentWorkspaceMemberFavorites.length > 1 || favoritesByFolder.length > 1; - return ( toggleNavigationSection()} - rightIcon={} - onRightIconClick={toggleNewFolder} + onClick={toggleNavigationSection} + rightIcon={ + isFavoriteFolderEnabled ? ( + + ) : undefined + } + onRightIconClick={ + isFavoriteFolderEnabled ? toggleNewFolder : undefined + } /> + {isNavigationSectionOpen && ( <> - {isFavoriteFolderCreating && ( - )} - {favoritesByFolder.map((folder) => ( - { - setActiveFolderId((currentId) => - currentId === folderId ? null : folderId, - ); - }} - /> - ))} {unorganisedFavorites.length > 0 && ( { Icon={() => } active={ favorite.objectNameSingular === 'view' - ? favorite.link === currentPathView + ? favorite.link === currentViewPath : favorite.link === currentPath } to={favorite.link} diff --git a/packages/twenty-front/src/modules/favorites/components/FavoritesFolders.tsx b/packages/twenty-front/src/modules/favorites/components/FavoritesFolders.tsx new file mode 100644 index 000000000000..a385ebe55c55 --- /dev/null +++ b/packages/twenty-front/src/modules/favorites/components/FavoritesFolders.tsx @@ -0,0 +1,89 @@ +import { useState } from 'react'; +import { useRecoilState } from 'recoil'; +import { IconFolder } from 'twenty-ui'; + +import { CurrentWorkspaceMemberFavorites } from '@/favorites/components/CurrentWorkspaceMemberFavorites'; +import { FavoriteFolderHotkeyScope } from '@/favorites/constants/FavoriteFolderRightIconDropdownHotkeyScope'; +import { useFavoriteFolders } from '@/favorites/hooks/useFavoriteFolders'; +import { isFavoriteFolderCreatingState } from '@/favorites/states/isFavoriteFolderCreatingState'; +import { NavigationDrawerInput } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerInput'; + +type FavoriteFoldersProps = { + isNavigationSectionOpen: boolean; +}; + +export const FavoriteFolders = ({ + isNavigationSectionOpen, +}: FavoriteFoldersProps) => { + const [activeFolderId, setActiveFolderId] = useState(null); + const [folderName, setFolderName] = useState(''); + + const { createFolder, favoritesByFolder } = useFavoriteFolders(); + const [isFavoriteFolderCreating, setIsFavoriteFolderCreating] = + useRecoilState(isFavoriteFolderCreatingState); + + const handleFolderNameChange = (value: string) => { + setFolderName(value); + }; + + const handleSubmitFolder = async (value: string) => { + if (!value) return false; + + setIsFavoriteFolderCreating(false); + setFolderName(''); + await createFolder(value); + return true; + }; + + const handleClickOutside = async ( + event: MouseEvent | TouchEvent, + value: string, + ) => { + if (!value) { + setIsFavoriteFolderCreating(false); + return; + } + + setIsFavoriteFolderCreating(false); + setFolderName(''); + await createFolder(value); + }; + + const handleCancel = () => { + setFolderName(''); + setIsFavoriteFolderCreating(false); + }; + + if (!isNavigationSectionOpen) { + return null; + } + + return ( + <> + {isFavoriteFolderCreating && ( + + )} + {favoritesByFolder.map((folder) => ( + 1} + isOpen={activeFolderId === folder.folderId} + onToggle={(folderId) => { + setActiveFolderId((currentId) => + currentId === folderId ? null : folderId, + ); + }} + /> + ))} + + ); +}; diff --git a/packages/twenty-front/src/modules/favorites/hooks/useFavoriteFolders.ts b/packages/twenty-front/src/modules/favorites/hooks/useFavoriteFolders.ts index 9e80c02d1c6b..85db0a82ccbf 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useFavoriteFolders.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useFavoriteFolders.ts @@ -1,14 +1,33 @@ +import { usePrefetchedFavoritesFoldersData } from '@/favorites/hooks/usePrefetchedfavoritesFoldersData'; import { calculateNewPosition } from '@/favorites/utils/calculateNewPosition'; +import { sortFavorites } from '@/favorites/utils/sortFavorites'; +import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { View } from '@/views/types/View'; import { OnDragEndResponder } from '@hello-pangea/dnd'; +import { useMemo } from 'react'; +import { useRecoilValue } from 'recoil'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; export const useFavoriteFolders = () => { - const { folders, favorites, upsertFavorites, currentWorkspaceMemberId } = + const { favorites, upsertFavorites, currentWorkspaceMemberId } = usePrefetchedFavoritesData(); + const { records: views } = usePrefetchedData(PrefetchKey.AllViews); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const { folders } = usePrefetchedFavoritesFoldersData(); + const { objectMetadataItem: favoriteObjectMetadataItem } = + useObjectMetadataItem({ + objectNameSingular: CoreObjectNameSingular.Favorite, + }); const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular: CoreObjectNameSingular.FavoriteFolder, @@ -21,6 +40,16 @@ export const useFavoriteFolders = () => { const { createOneRecord: createFavoriteFolder } = useCreateOneRecord({ objectNameSingular: CoreObjectNameSingular.FavoriteFolder, }); + const favoriteRelationFieldMetadataItems = useMemo( + () => + favoriteObjectMetadataItem.fields.filter( + (fieldMetadataItem) => + fieldMetadataItem.type === FieldMetadataType.Relation && + fieldMetadataItem.name !== 'workspaceMember' && + fieldMetadataItem.name !== 'favoriteFolder', + ), + [favoriteObjectMetadataItem.fields], + ); const createFolder = async (name: string): Promise => { if (!name || !currentWorkspaceMemberId) { @@ -85,9 +114,32 @@ export const useFavoriteFolders = () => { ); upsertFavorites(updatedFavorites); }; - + const getObjectRecordIdentifierByNameSingular = + useGetObjectRecordIdentifierByNameSingular(); + const favoritesByFolder = useMemo(() => { + return folders.map((folder) => ({ + folderId: folder.id, + folderName: folder.name, + favorites: sortFavorites( + favorites.filter((favorite) => favorite.favoriteFolderId === folder.id), + favoriteRelationFieldMetadataItems, + getObjectRecordIdentifierByNameSingular, + true, + views, + objectMetadataItems, + ), + })); + }, [ + folders, + favorites, + favoriteRelationFieldMetadataItems, + getObjectRecordIdentifierByNameSingular, + views, + objectMetadataItems, + ]); return { favoriteFolder: folders, + favoritesByFolder, createFolder, renameFolder, deleteFolder, diff --git a/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts index 33d2a41537f7..5c4ead57b5b0 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts @@ -18,7 +18,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; export const useFavorites = () => { - const { favorites, workspaceFavorites, folders, currentWorkspaceMemberId } = + const { favorites, workspaceFavorites, currentWorkspaceMemberId } = usePrefetchedFavoritesData(); const { records: views } = usePrefetchedData(PrefetchKey.AllViews); @@ -89,28 +89,6 @@ export const useFavorites = () => { objectMetadataItems, ]); - const favoritesByFolder = useMemo(() => { - return folders.map((folder) => ({ - folderId: folder.id, - folderName: folder.name, - favorites: sortFavorites( - favorites.filter((favorite) => favorite.favoriteFolderId === folder.id), - favoriteRelationFieldMetadataItems, - getObjectRecordIdentifierByNameSingular, - true, - views, - objectMetadataItems, - ), - })); - }, [ - folders, - favorites, - favoriteRelationFieldMetadataItems, - getObjectRecordIdentifierByNameSingular, - views, - objectMetadataItems, - ]); - const createFavorite = ( targetRecord: ObjectRecord, targetObjectNameSingular: string, @@ -168,7 +146,6 @@ export const useFavorites = () => { return { favorites: favoritesSorted, workspaceFavorites: workspaceFavoritesSorted, - favoritesByFolder, createFavorite, handleReorderFavorite, deleteFavorite, diff --git a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts index 553dbdedd0e5..1c851d60508e 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts @@ -1,27 +1,19 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { Favorite } from '@/favorites/types/Favorite'; -import { FavoriteFolder } from '@/favorites/types/FavoriteFolder'; import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; type PrefetchedFavoritesData = { favorites: Favorite[]; workspaceFavorites: Favorite[]; - folders: FavoriteFolder[]; upsertFavorites: (records: Favorite[]) => void; - upsertFolders: (records: FavoriteFolder[]) => void; currentWorkspaceMemberId: string | undefined; }; export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => { - const isFavoriteFolderEnabled = useIsFeatureEnabled( - 'IS_FAVORITE_FOLDER_ENABLED', - ); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMemberId = currentWorkspaceMember?.id; const { records: _favorites } = usePrefetchedData( @@ -33,15 +25,6 @@ export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => { }, ); - const { records: _folders } = usePrefetchedData( - PrefetchKey.AllFavoritesFolders, - { - workspaceMemberId: { - eq: currentWorkspaceMemberId, - }, - }, - ); - const favorites = useMemo( () => _favorites.filter( @@ -53,34 +36,19 @@ export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => { () => _favorites.filter((favorite) => favorite.workspaceMemberId === null), [_favorites], ); - const folders = useMemo(() => _folders, [_folders]); const { upsertRecordsInCache: upsertFavorites } = usePrefetchRunQuery({ prefetchKey: PrefetchKey.AllFavorites, }); - const { upsertRecordsInCache: upsertFolders } = - usePrefetchRunQuery({ - prefetchKey: PrefetchKey.AllFavoritesFolders, - }); - return useMemo( () => ({ favorites, workspaceFavorites, - folders, upsertFavorites, - upsertFolders, currentWorkspaceMemberId, }), - [ - favorites, - workspaceFavorites, - folders, - upsertFavorites, - upsertFolders, - currentWorkspaceMemberId, - ], + [favorites, workspaceFavorites, upsertFavorites, currentWorkspaceMemberId], ); }; diff --git a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts new file mode 100644 index 000000000000..40316b78162c --- /dev/null +++ b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts @@ -0,0 +1,44 @@ +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { FavoriteFolder } from '@/favorites/types/FavoriteFolder'; +import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useMemo } from 'react'; +import { useRecoilValue } from 'recoil'; + +type PrefetchedFavoritesFoldersData = { + folders: FavoriteFolder[]; + upsertFolders: (records: FavoriteFolder[]) => void; + currentWorkspaceMemberId: string | undefined; +}; + +export const usePrefetchedFavoritesFoldersData = + (): PrefetchedFavoritesFoldersData => { + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMemberId = currentWorkspaceMember?.id; + + const { records: _folders } = usePrefetchedData( + PrefetchKey.AllFavoritesFolders, + { + workspaceMemberId: { + eq: currentWorkspaceMemberId, + }, + }, + ); + + const folders = useMemo(() => _folders, [_folders]); + + const { upsertRecordsInCache: upsertFolders } = + usePrefetchRunQuery({ + prefetchKey: PrefetchKey.AllFavoritesFolders, + }); + + return useMemo( + () => ({ + folders, + upsertFolders, + currentWorkspaceMemberId, + }), + [folders, upsertFolders, currentWorkspaceMemberId], + ); + }; diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx index 9c1ac1653ba4..90c862c01bfa 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx @@ -35,6 +35,7 @@ export const MainNavigationDrawerItems = () => { const isWorkspaceFavoriteEnabled = useIsFeatureEnabled( 'IS_WORKSPACE_FAVORITE_ENABLED', ); + const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = useRecoilState(isNavigationDrawerExpandedState); const setNavigationDrawerExpandedMemorized = useSetRecoilState( diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx index 91d934b3b1fd..5a8314835e93 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx @@ -1,5 +1,5 @@ import { useRecoilValue } from 'recoil'; -import { useIcons } from 'twenty-ui'; +import { isDefined, useIcons } from 'twenty-ui'; import { FAVORITE_FOLDERS_DROPDOWN_ID } from '@/favorites/constants/FavoriteFoldersDropdownId'; import { useFavorites } from '@/favorites/hooks/useFavorites'; @@ -11,6 +11,7 @@ import { recordIndexViewTypeState } from '@/object-record/record-index/states/re import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PageAddButton } from '@/ui/layout/page/components/PageAddButton'; +import { PageFavoriteButton } from '@/ui/layout/page/components/PageFavoriteButton'; import { PageFavoriteFoldersDropdown } from '@/ui/layout/page/components/PageFavoriteFolderDropdown'; import { PageHeader } from '@/ui/layout/page/components/PageHeader'; import { PageHotkeysEffect } from '@/ui/layout/page/components/PageHotkeysEffect'; @@ -18,12 +19,16 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { View } from '@/views/types/View'; import { ViewType } from '@/views/types/ViewType'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useContext } from 'react'; import { capitalize } from '~/utils/string/capitalize'; export const RecordIndexPageHeader = () => { const { findObjectMetadataItemByNamePlural } = useFilteredObjectMetadataItems(); + const isFavoriteFolderEnabled = useIsFeatureEnabled( + 'IS_FAVORITE_FOLDER_ENABLED', + ); const { objectNamePlural, onCreateRecord, recordIndexId } = useContext( RecordIndexRootPropsContext, @@ -36,12 +41,27 @@ export const RecordIndexPageHeader = () => { const view = views.find((view) => view.id === currentViewId); - const { favorites } = useFavorites(); + const { favorites, createFavorite, deleteFavorite } = useFavorites(); const isFavorite = favorites.some( (favorite) => favorite.recordId === currentViewId && favorite.workspaceMemberId, ); + + const handleFavoriteButtonClick = async () => { + if (!view) return; + + const correspondingFavorite = favorites.find( + (favorite) => + favorite.recordId === currentViewId && favorite.workspaceMemberId, + ); + + if (isFavorite && isDefined(correspondingFavorite)) { + deleteFavorite(correspondingFavorite.id); + } else { + createFavorite(view, 'view'); + } + }; const objectMetadataItem = findObjectMetadataItemByNamePlural(objectNamePlural); @@ -68,12 +88,19 @@ export const RecordIndexPageHeader = () => { return ( - + {isFavoriteFolderEnabled ? ( + + ) : ( + + )} {shouldDisplayAddButton && (isTable ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts index 7e779d443591..ad76ba9d2087 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts @@ -34,7 +34,7 @@ export const useRecordShowPage = ( const { objectMetadataItems } = useObjectMetadataItems(); const { labelIdentifierFieldMetadataItem } = useLabelIdentifierFieldMetadataItem({ objectNameSingular }); - const { favorites } = useFavorites(); + const { favorites, createFavorite, deleteFavorite } = useFavorites(); const setEntityFields = useSetRecoilState( recordStoreFamilyState(objectRecordId), ); @@ -64,6 +64,16 @@ export const useRecordShowPage = ( ); const isFavorite = isDefined(correspondingFavorite); + const handleFavoriteButtonClick = async () => { + if (!objectNameSingular || !record) return; + + if (isFavorite) { + deleteFavorite(correspondingFavorite.id); + } else { + createFavorite(record, objectNameSingular); + } + }; + const labelIdentifierFieldValue = record?.[labelIdentifierFieldMetadataItem?.name ?? '']; const pageName = @@ -90,5 +100,6 @@ export const useRecordShowPage = ( isFavorite, record, objectMetadataItem, + handleFavoriteButtonClick, }; }; diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchDataProvider.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchDataProvider.tsx index 837d2498e576..87f5228daf72 100644 --- a/packages/twenty-front/src/modules/prefetch/components/PrefetchDataProvider.tsx +++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchDataProvider.tsx @@ -1,11 +1,17 @@ import React from 'react'; +import { PrefetchFavoriteFoldersRunQueriesEffect } from '@/prefetch/components/PrefetchFavortiteFoldersRunQueriesEffect'; import { PrefetchRunQueriesEffect } from '@/prefetch/components/PrefetchRunQueriesEffect'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; export const PrefetchDataProvider = ({ children }: React.PropsWithChildren) => { + const isFavoriteFolderEnabled = useIsFeatureEnabled( + 'IS_FAVORITE_FOLDER_ENABLED', + ); return ( <> + {isFavoriteFolderEnabled && } {children} ); diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchFavortiteFoldersRunQueriesEffect.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchFavortiteFoldersRunQueriesEffect.tsx new file mode 100644 index 000000000000..376c1e46942b --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchFavortiteFoldersRunQueriesEffect.tsx @@ -0,0 +1,45 @@ +import { currentUserState } from '@/auth/states/currentUserState'; +import { FavoriteFolder } from '@/favorites/types/FavoriteFolder'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords'; +import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig'; +import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from '~/utils/isDefined'; + +export const PrefetchFavoriteFoldersRunQueriesEffect = () => { + const currentUser = useRecoilValue(currentUserState); + + const { upsertRecordsInCache: upsertFavoritesFoldersInCache } = + usePrefetchRunQuery({ + prefetchKey: PrefetchKey.AllFavoritesFolders, + }); + + const { objectMetadataItems } = useObjectMetadataItems(); + + // Only include favorite folders operation + const operationSignatures = Object.values(PREFETCH_CONFIG) + .filter(({ objectNameSingular }) => objectNameSingular === 'favoriteFolder') + .map(({ objectNameSingular, operationSignatureFactory }) => { + const objectMetadataItem = objectMetadataItems.find( + (item) => item.nameSingular === objectNameSingular, + ); + + return operationSignatureFactory({ objectMetadataItem }); + }); + + const { result } = useCombinedFindManyRecords({ + operationSignatures, + skip: !currentUser, + }); + + useEffect(() => { + if (isDefined(result.favoriteFolders)) { + upsertFavoritesFoldersInCache(result.favoriteFolders as FavoriteFolder[]); + } + }, [result, upsertFavoritesFoldersInCache]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx index 17106a10f83b..bad5c0b5aead 100644 --- a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx +++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx @@ -3,17 +3,20 @@ import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; import { Favorite } from '@/favorites/types/Favorite'; -import { FavoriteFolder } from '@/favorites/types/FavoriteFolder'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords'; import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig'; import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { View } from '@/views/types/View'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { isDefined } from '~/utils/isDefined'; export const PrefetchRunQueriesEffect = () => { const currentUser = useRecoilValue(currentUserState); + const isFavoriteFolderEnabled = useIsFeatureEnabled( + 'IS_FAVORITE_FOLDER_ENABLED', + ); const { upsertRecordsInCache: upsertViewsInCache } = usePrefetchRunQuery({ @@ -25,22 +28,21 @@ export const PrefetchRunQueriesEffect = () => { prefetchKey: PrefetchKey.AllFavorites, }); - const { upsertRecordsInCache: upsertFavoritesFoldersInCache } = - usePrefetchRunQuery({ - prefetchKey: PrefetchKey.AllFavoritesFolders, - }); - const { objectMetadataItems } = useObjectMetadataItems(); - const operationSignatures = Object.values(PREFETCH_CONFIG).map( - ({ objectNameSingular, operationSignatureFactory }) => { + const operationSignatures = Object.values(PREFETCH_CONFIG) + .filter( + ({ objectNameSingular }) => + // Exclude favorite folders as they're handled separately + objectNameSingular !== 'favoriteFolder', + ) + .map(({ objectNameSingular, operationSignatureFactory }) => { const objectMetadataItem = objectMetadataItems.find( (item) => item.nameSingular === objectNameSingular, ); return operationSignatureFactory({ objectMetadataItem }); - }, - ); + }); const { result } = useCombinedFindManyRecords({ operationSignatures, @@ -55,15 +57,11 @@ export const PrefetchRunQueriesEffect = () => { if (isDefined(result.favorites)) { upsertFavoritesInCache(result.favorites as Favorite[]); } - - if (isDefined(result.favoriteFolders)) { - upsertFavoritesFoldersInCache(result.favoriteFolders as FavoriteFolder[]); - } }, [ result, upsertViewsInCache, upsertFavoritesInCache, - upsertFavoritesFoldersInCache, + isFavoriteFolderEnabled, ]); return <>; diff --git a/packages/twenty-front/src/modules/prefetch/hooks/useIsFavoriteFoldersPrefetchLoading.ts b/packages/twenty-front/src/modules/prefetch/hooks/useIsFavoriteFoldersPrefetchLoading.ts new file mode 100644 index 000000000000..a3ffaa608e09 --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/hooks/useIsFavoriteFoldersPrefetchLoading.ts @@ -0,0 +1,11 @@ +import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useRecoilValue } from 'recoil'; + +export const useIsFavoriteFoldersPrefetchLoading = () => { + const areFavoritesFolderPrefetched = useRecoilValue( + prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders), + ); + + return !areFavoritesFolderPrefetched; +}; diff --git a/packages/twenty-front/src/modules/prefetch/hooks/useIsPrefetchLoading.ts b/packages/twenty-front/src/modules/prefetch/hooks/useIsPrefetchLoading.ts index ea3d4b31d7cb..ca3e275b5bf8 100644 --- a/packages/twenty-front/src/modules/prefetch/hooks/useIsPrefetchLoading.ts +++ b/packages/twenty-front/src/modules/prefetch/hooks/useIsPrefetchLoading.ts @@ -1,22 +1,25 @@ -import { useRecoilValue } from 'recoil'; - import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { useRecoilValue } from 'recoil'; +import { useIsFavoriteFoldersPrefetchLoading } from './useIsFavoriteFoldersPrefetchLoading'; export const useIsPrefetchLoading = () => { + const isFavoriteFolderEnabled = useIsFeatureEnabled( + 'IS_FAVORITE_FOLDER_ENABLED', + ); + const isFavoriteFoldersLoading = useIsFavoriteFoldersPrefetchLoading(); + const areViewsPrefetched = useRecoilValue( prefetchIsLoadedFamilyState(PrefetchKey.AllViews), ); const areFavoritesPrefetched = useRecoilValue( prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites), ); - const areFavoritesFolderPrefetched = useRecoilValue( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders), - ); return ( !areViewsPrefetched || !areFavoritesPrefetched || - !areFavoritesFolderPrefetched + (isFavoriteFolderEnabled && isFavoriteFoldersLoading) ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/PageFavoriteButton.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/PageFavoriteButton.tsx index 21e5cfe874b2..2891c48f46f4 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/PageFavoriteButton.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/PageFavoriteButton.tsx @@ -2,14 +2,19 @@ import { IconButton, IconHeart } from 'twenty-ui'; type PageFavoriteButtonProps = { isFavorite: boolean; + onClick?: () => void; }; -export const PageFavoriteButton = ({ isFavorite }: PageFavoriteButtonProps) => ( +export const PageFavoriteButton = ({ + isFavorite, + onClick, +}: PageFavoriteButtonProps) => ( ); diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx index 456ea5d35913..37bf95a6defe 100644 --- a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx @@ -32,6 +32,7 @@ export const RecordShowPage = () => { isFavorite, record, objectMetadataItem, + handleFavoriteButtonClick, } = useRecordShowPage( parameters.objectNameSingular ?? '', parameters.objectRecordId ?? '', @@ -69,6 +70,7 @@ export const RecordShowPage = () => { {...{ isFavorite, record, + handleFavoriteButtonClick, objectMetadataItem, objectNameSingular, }} diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPageBaseHeader.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPageBaseHeader.tsx index acdd4b271bf1..fdcb4909d01d 100644 --- a/packages/twenty-front/src/pages/object-record/RecordShowPageBaseHeader.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordShowPageBaseHeader.tsx @@ -1,15 +1,18 @@ import { FAVORITE_FOLDERS_DROPDOWN_ID } from '@/favorites/constants/FavoriteFoldersDropdownId'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { PageFavoriteButton } from '@/ui/layout/page/components/PageFavoriteButton'; import { PageFavoriteFoldersDropdown } from '@/ui/layout/page/components/PageFavoriteFolderDropdown'; import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton'; import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; type RecordShowPageBaseHeaderProps = { isFavorite: boolean; record: ObjectRecord | undefined; objectMetadataItem: ObjectMetadataItem; objectNameSingular: string; + handleFavoriteButtonClick: () => void; }; export const RecordShowPageBaseHeader = ({ @@ -17,16 +20,28 @@ export const RecordShowPageBaseHeader = ({ record, objectMetadataItem, objectNameSingular, + handleFavoriteButtonClick, }: RecordShowPageBaseHeaderProps) => { + const isFavoriteFolderEnabled = useIsFeatureEnabled( + 'IS_FAVORITE_FOLDER_ENABLED', + ); + return ( <> - + {isFavoriteFolderEnabled ? ( + + ) : ( + + )}