Favorite folders (#7998)

closes - #5755

---------

Co-authored-by: martmull <martmull@hotmail.fr>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
nitin
2024-11-18 19:52:19 +05:30
committed by GitHub
parent 5115022355
commit 0125d58ba8
100 changed files with 24033 additions and 21488 deletions

View File

@ -8,10 +8,12 @@ export const favoriteId = 'f088c8c9-05d2-4276-b065-b863cc7d0b33';
const favoriteTargetObjectId = 'f2d8b9e9-7932-4065-bc09-baf12388b75d';
export const favoriteTargetObjectRecord = {
id: favoriteTargetObjectId,
__typename: 'Person',
};
export const initialFavorites = [
{
__typename: 'Favorite',
id: '1',
position: 0,
key: mockId,
@ -22,8 +24,11 @@ export const initialFavorites = [
recordId: '1',
person: { id: '1', name: 'John Doe' },
company: { id: '2', name: 'ABC Corp' },
workspaceMemberId: '1',
favoriteFolderId: undefined,
},
{
__typename: 'Favorite',
id: '2',
position: 1,
key: mockId,
@ -34,8 +39,12 @@ export const initialFavorites = [
recordId: '1',
person: { id: '3', name: 'Jane Doe' },
company: { id: '4', name: 'Company Test' },
workspaceMemberId: '1',
favoriteFolderId: undefined,
},
{
__typename: 'Favorite',
id: '3',
position: 2,
key: mockId,
@ -44,27 +53,37 @@ export const initialFavorites = [
avatarType: 'squared' as AvatarType,
link: 'example.com',
recordId: '1',
workspaceMemberId: '1',
favoriteFolderId: undefined,
},
];
export const sortedFavorites = [
{
id: '1',
recordId: '2',
recordId: '1',
position: 0,
avatarType: 'squared',
avatarUrl: undefined,
labelIdentifier: 'ABC Corp',
link: '/object/company/2',
avatarType: 'rounded',
avatarUrl: '',
labelIdentifier: ' ',
link: '/object/person/1',
objectNameSingular: 'person',
workspaceMemberId: '1',
favoriteFolderId: undefined,
__typename: 'Favorite',
},
{
id: '2',
recordId: '4',
recordId: '3',
position: 1,
avatarType: 'squared',
avatarUrl: undefined,
labelIdentifier: 'Company Test',
link: '/object/company/4',
avatarType: 'rounded',
avatarUrl: '',
labelIdentifier: ' ',
link: '/object/person/3',
objectNameSingular: 'person',
workspaceMemberId: '1',
favoriteFolderId: undefined,
__typename: 'Favorite',
},
{
id: '3',
@ -72,9 +91,12 @@ export const sortedFavorites = [
key: '8f3b2121-f194-4ba4-9fbf-2d5a37126806',
labelIdentifier: 'favoriteLabel',
avatarUrl: 'example.com',
avatarType: 'squared',
link: 'example.com',
recordId: '1',
avatarType: 'squared',
favoriteFolderId: undefined,
workspaceMemberId: '1',
__typename: 'Favorite',
},
];
@ -84,288 +106,301 @@ export const mocks = [
query: gql`
mutation CreateOneFavorite($input: FavoriteCreateInput!) {
createFavorite(data: $input) {
__typename
company {
__typename
accountOwnerId
address {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
createdBy {
source
__typename
company {
__typename
accountOwnerId
address {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
domainName {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
employees
id
idealCustomerProfile
introVideo {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name
position
tagline
updatedAt
visaSponsorship
workPolicy
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
companyId
createdAt
deletedAt
favoriteFolder {
__typename
createdAt
deletedAt
id
name
position
updatedAt
}
favoriteFolderId
id
note {
__typename
body
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
position
title
updatedAt
}
noteId
opportunity {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
opportunityId
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
personId
position
rocket {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
name
position
updatedAt
}
rocketId
task {
__typename
assigneeId
body
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
dueAt
id
position
status
title
updatedAt
}
taskId
updatedAt
view {
__typename
createdAt
deletedAt
icon
id
isCompact
kanbanFieldMetadataId
key
name
objectMetadataId
position
type
updatedAt
}
viewId
workflow {
__typename
createdAt
deletedAt
id
lastPublishedVersionId
name
position
statuses
updatedAt
}
workflowId
workflowRun {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
endedAt
id
name
output
position
startedAt
status
updatedAt
workflowId
workflowVersionId
}
workflowRunId
workflowVersion {
__typename
createdAt
deletedAt
id
name
position
status
steps
trigger
updatedAt
workflowId
}
workflowVersionId
workspaceMember {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
timeFormat
timeZone
updatedAt
userEmail
userId
}
workspaceMemberId
name
}
deletedAt
domainName {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
employees
id
idealCustomerProfile
introVideo {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name
position
tagline
updatedAt
visaSponsorship
workPolicy
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
companyId
createdAt
deletedAt
id
note {
__typename
body
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
position
title
updatedAt
}
noteId
opportunity {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
opportunityId
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
personId
position
rocket {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
name
position
updatedAt
}
rocketId
task {
__typename
assigneeId
body
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
dueAt
id
position
status
title
updatedAt
}
taskId
updatedAt
view {
__typename
createdAt
deletedAt
icon
id
isCompact
kanbanFieldMetadataId
key
name
objectMetadataId
position
type
updatedAt
}
viewId
workflow {
__typename
createdAt
deletedAt
id
lastPublishedVersionId
name
position
statuses
updatedAt
}
workflowId
workflowRun {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
endedAt
id
name
output
position
startedAt
status
updatedAt
workflowId
workflowVersionId
}
workflowRunId
workflowVersion {
__typename
createdAt
deletedAt
id
name
position
status
steps
trigger
updatedAt
workflowId
}
workflowVersionId
workspaceMember {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
timeFormat
timeZone
updatedAt
userEmail
userId
}
workspaceMemberId
}
}
`,
variables: {
input: {
id: mockId,
personId: favoriteTargetObjectId,
position: 4,
position: 3,
workspaceMemberId: '1',
favoriteFolderId: undefined,
id: mockId,
},
},
},
result: jest.fn(() => ({
data: {
createFavorite: {
__typename: 'Favorite',
id: favoriteId,
position: 1,
},
},
})),
@ -386,7 +421,9 @@ export const mocks = [
result: jest.fn(() => ({
data: {
deleteFavorite: {
__typename: 'Favorite',
id: favoriteId,
deletedAt: new Date().toISOString(),
},
},
})),
@ -457,6 +494,16 @@ export const mocks = [
companyId
createdAt
deletedAt
favoriteFolder {
__typename
createdAt
deletedAt
id
name
position
updatedAt
}
favoriteFolderId
id
note {
__typename
@ -678,7 +725,9 @@ export const mocks = [
result: jest.fn(() => ({
data: {
updateFavorite: {
__typename: 'Favorite',
id: favoriteId,
position: 2,
},
},
})),

View File

@ -0,0 +1,53 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import {
favoriteTargetObjectRecord,
initialFavorites,
mockId,
mockWorkspaceMember,
mocks,
} from '../__mocks__/useFavorites';
jest.mock('uuid', () => ({
v4: () => mockId,
}));
jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
useFindManyRecords: () => ({ records: initialFavorites }),
}));
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: mocks,
});
describe('useCreateFavorite', () => {
it('should create favorite successfully', async () => {
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
return useCreateFavorite();
},
{ wrapper: Wrapper },
);
result.current(favoriteTargetObjectRecord, CoreObjectNameSingular.Person);
await waitFor(() => {
expect(mocks[0].result).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,47 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import {
favoriteId,
initialFavorites,
mockWorkspaceMember,
mocks,
} from '../__mocks__/useFavorites';
jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
useFindManyRecords: () => ({ records: initialFavorites }),
}));
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: mocks,
});
describe('useDeleteFavorite', () => {
it('should delete favorite successfully', async () => {
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
return useDeleteFavorite();
},
{ wrapper: Wrapper },
);
result.current(favoriteId);
await waitFor(() => {
expect(mocks[1].result).toHaveBeenCalled();
});
});
});

View File

@ -1,29 +1,18 @@
import { DropResult, ResponderProvided } from '@hello-pangea/dnd';
import { renderHook, waitFor } from '@testing-library/react';
import { renderHook } from '@testing-library/react';
import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { act } from 'react';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import {
favoriteId,
favoriteTargetObjectRecord,
initialFavorites,
mockId,
mocks,
mockWorkspaceMember,
sortedFavorites,
} from '../__mocks__/useFavorites';
jest.mock('uuid', () => ({
v4: jest.fn(() => mockId),
}));
jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
useFindManyRecords: () => ({ records: initialFavorites }),
}));
@ -33,7 +22,7 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
});
describe('useFavorites', () => {
it('should fetch favorites successfully', async () => {
it('should fetch and sort favorites successfully', () => {
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
@ -46,108 +35,9 @@ describe('useFavorites', () => {
return useFavorites();
},
{
wrapper: Wrapper,
},
{ wrapper: Wrapper },
);
expect(result.current.favorites).toEqual(sortedFavorites);
});
it('should createOneFavorite successfully', async () => {
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
return useFavorites();
},
{
wrapper: Wrapper,
},
);
result.current.createFavorite(
favoriteTargetObjectRecord,
CoreObjectNameSingular.Person,
);
await waitFor(() => {
expect(mocks[0].result).toHaveBeenCalled();
});
});
it('should deleteOneRecord successfully', async () => {
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
return useFavorites();
},
{
wrapper: Wrapper,
},
);
result.current.deleteFavorite(favoriteId);
await waitFor(() => {
expect(mocks[1].result).toHaveBeenCalled();
});
});
it('should handle reordering favorites successfully', async () => {
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
return useFavorites();
},
{
wrapper: Wrapper,
},
);
act(() => {
const dragAndDropResult: DropResult = {
source: { index: 0, droppableId: 'droppableId' },
destination: { index: 2, droppableId: 'droppableId' },
combine: null,
mode: 'FLUID',
draggableId: 'draggableId',
type: 'type',
reason: 'DROP',
};
const responderProvided: ResponderProvided = {
announce: () => {},
};
result.current.handleReorderFavorite(
dragAndDropResult,
responderProvided,
);
});
await waitFor(() => {
expect(mocks[2].result).toHaveBeenCalled();
});
expect(result.current).toEqual(sortedFavorites);
});
});

View File

@ -0,0 +1,64 @@
import { DropResult, ResponderProvided } from '@hello-pangea/dnd';
import { renderHook, waitFor } from '@testing-library/react';
import { act } from 'react';
import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useReorderFavorite } from '@/favorites/hooks/useReorderFavorite';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import {
initialFavorites,
mockWorkspaceMember,
mocks,
} from '../__mocks__/useFavorites';
jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
useFindManyRecords: () => ({ records: initialFavorites }),
}));
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: mocks,
});
describe('useReorderFavorite', () => {
it('should handle reordering favorites successfully', async () => {
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
return useReorderFavorite();
},
{ wrapper: Wrapper },
);
act(() => {
const dragAndDropResult: DropResult = {
source: { index: 0, droppableId: 'droppableId' },
destination: { index: 2, droppableId: 'droppableId' },
combine: null,
mode: 'FLUID',
draggableId: '1',
type: 'type',
reason: 'DROP',
};
const responderProvided: ResponderProvided = {
announce: () => {},
};
result.current(dragAndDropResult, responderProvided);
});
await waitFor(() => {
expect(mocks[2].result).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,38 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useCreateFavorite = () => {
const { favorites, currentWorkspaceMemberId } = usePrefetchedFavoritesData();
const { createOneRecord: createOneFavorite } = useCreateOneRecord({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const createFavorite = (
targetRecord: ObjectRecord,
targetObjectNameSingular: string,
favoriteFolderId?: string,
) => {
const relevantFavorites = favoriteFolderId
? favorites.filter((fav) => fav.favoriteFolderId === favoriteFolderId)
: favorites.filter(
(fav) => !fav.favoriteFolderId && fav.workspaceMemberId,
);
const maxPosition = Math.max(
...relevantFavorites.map((fav) => fav.position),
0,
);
createOneFavorite({
[targetObjectNameSingular]: targetRecord,
[`${targetObjectNameSingular}Id`]: targetRecord.id,
position: maxPosition + 1,
workspaceMemberId: currentWorkspaceMemberId,
favoriteFolderId,
});
};
return createFavorite;
};

View File

@ -0,0 +1,32 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
import { usePrefetchedFavoritesFoldersData } from './usePrefetchedFavoritesFoldersData';
export const useCreateFavoriteFolder = () => {
const { createOneRecord: createFavoriteFolder } = useCreateOneRecord({
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
});
const { currentWorkspaceMemberId } = usePrefetchedFavoritesData();
const { favoriteFolders } = usePrefetchedFavoritesFoldersData();
const createNewFavoriteFolder = async (name: string): Promise<void> => {
if (!name || !currentWorkspaceMemberId) {
return;
}
const maxPosition = Math.max(
...favoriteFolders.map((folder) => folder.position),
0,
);
await createFavoriteFolder({
workspaceMemberId: currentWorkspaceMemberId,
name,
position: maxPosition + 1,
});
};
return createNewFavoriteFolder;
};

View File

@ -0,0 +1,14 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
export const useDeleteFavorite = () => {
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const deleteFavorite = (favoriteId: string) => {
deleteOneRecord(favoriteId);
};
return deleteFavorite;
};

View File

@ -0,0 +1,26 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useDeleteFavoriteFolder = () => {
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
});
const { upsertFavorites, favorites, workspaceFavorites } =
usePrefetchedFavoritesData();
const deleteFavoriteFolder = async (folderId: string): Promise<void> => {
await deleteOneRecord(folderId);
const updatedFavorites = [
...favorites.filter((favorite) => favorite.favoriteFolderId !== folderId),
...workspaceFavorites,
];
upsertFavorites(updatedFavorites);
};
return {
deleteFavoriteFolder,
};
};

View File

@ -1,163 +1,56 @@
import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { Favorite } from '@/favorites/types/Favorite';
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 { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useFavorites = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { favorites } = usePrefetchedFavoritesData();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const { updateOneRecord: updateOneFavorite } = useUpdateOneRecord({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const { createOneRecord: createOneFavorite } = useCreateOneRecord({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const { records: favorites } = usePrefetchedData<Favorite>(
PrefetchKey.AllFavorites,
{
workspaceMemberId: {
eq: currentWorkspaceMember?.id ?? '',
},
},
);
const { records: workspaceFavorites } = usePrefetchedData<Favorite>(
PrefetchKey.AllFavorites,
{
workspaceMemberId: {
eq: undefined,
},
},
);
const getObjectRecordIdentifierByNameSingular =
useGetObjectRecordIdentifierByNameSingular();
const favoriteRelationFieldMetadataItems = useMemo(
() =>
favoriteObjectMetadataItem.fields.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type === FieldMetadataType.Relation &&
fieldMetadataItem.name !== 'workspaceMember',
fieldMetadataItem.name !== 'workspaceMember' &&
fieldMetadataItem.name !== 'favoriteFolder',
),
[favoriteObjectMetadataItem.fields],
);
const getObjectRecordIdentifierByNameSingular =
useGetObjectRecordIdentifierByNameSingular();
const favoritesSorted = useMemo(() => {
return sortFavorites(
const sortedFavorites = useMemo(
() =>
sortFavorites(
favorites,
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
true,
views,
objectMetadataItems,
),
[
favorites,
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
true,
);
}, [
favoriteRelationFieldMetadataItems,
favorites,
getObjectRecordIdentifierByNameSingular,
]);
views,
objectMetadataItems,
],
);
const workspaceFavoritesSorted = useMemo(() => {
return sortFavorites(
workspaceFavorites.filter((favorite) => favorite.viewId),
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
false,
);
}, [
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
workspaceFavorites,
]);
const createFavorite = (
targetRecord: Record<string, any>,
targetObjectNameSingular: string,
) => {
createOneFavorite({
[targetObjectNameSingular]: targetRecord,
[`${targetObjectNameSingular}Id`]: targetRecord.id,
position: favorites.length + 1,
workspaceMemberId: currentWorkspaceMember?.id,
});
};
const deleteFavorite = (favoriteId: string) => {
deleteOneRecord(favoriteId);
};
const computeNewPosition = (destIndex: number, sourceIndex: number) => {
const moveToFirstPosition = destIndex === 0;
const moveToLastPosition = destIndex === favoritesSorted.length - 1;
const moveAfterSource = destIndex > sourceIndex;
if (moveToFirstPosition) {
return favoritesSorted[0].position / 2;
} else if (moveToLastPosition) {
return favoritesSorted[destIndex - 1].position + 1;
} else if (moveAfterSource) {
return (
(favoritesSorted[destIndex + 1].position +
favoritesSorted[destIndex].position) /
2
);
} else {
return (
favoritesSorted[destIndex].position -
(favoritesSorted[destIndex].position -
favoritesSorted[destIndex - 1].position) /
2
);
}
};
const handleReorderFavorite: OnDragEndResponder = (result) => {
if (!result.destination || !favoritesSorted) {
return;
}
const newPosition = computeNewPosition(
result.destination.index,
result.source.index,
);
const updatedFavorite = favoritesSorted[result.source.index];
updateOneFavorite({
idToUpdate: updatedFavorite.id,
updateOneRecordInput: {
position: newPosition,
},
});
};
return {
favorites: favoritesSorted,
workspaceFavorites: workspaceFavoritesSorted,
createFavorite,
handleReorderFavorite,
deleteFavorite,
};
return sortedFavorites;
};

View File

@ -0,0 +1,62 @@
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 { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
import { usePrefetchedFavoritesFoldersData } from './usePrefetchedFavoritesFoldersData';
export const useFavoritesByFolder = () => {
const { favorites } = usePrefetchedFavoritesData();
const { favoriteFolders } = usePrefetchedFavoritesFoldersData();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const getObjectRecordIdentifierByNameSingular =
useGetObjectRecordIdentifierByNameSingular();
const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const favoriteRelationFields = useMemo(
() =>
favoriteObjectMetadataItem.fields.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type === FieldMetadataType.Relation &&
fieldMetadataItem.name !== 'workspaceMember' &&
fieldMetadataItem.name !== 'favoriteFolder',
),
[favoriteObjectMetadataItem.fields],
);
const favoritesByFolder = useMemo(() => {
return favoriteFolders.map((folder) => ({
folderId: folder.id,
folderName: folder.name,
favorites: sortFavorites(
favorites.filter((favorite) => favorite.favoriteFolderId === folder.id),
favoriteRelationFields,
getObjectRecordIdentifierByNameSingular,
true,
views,
objectMetadataItems,
),
}));
}, [
favoriteFolders,
favorites,
favoriteRelationFields,
getObjectRecordIdentifierByNameSingular,
views,
objectMetadataItems,
]);
return favoritesByFolder;
};

View File

@ -0,0 +1,46 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { Favorite } from '@/favorites/types/Favorite';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilValue } from 'recoil';
type PrefetchedFavoritesData = {
favorites: Favorite[];
workspaceFavorites: Favorite[];
upsertFavorites: (records: Favorite[]) => void;
currentWorkspaceMemberId: string | undefined;
};
export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMemberId = currentWorkspaceMember?.id;
const { records: _favorites } = usePrefetchedData<Favorite>(
PrefetchKey.AllFavorites,
{
workspaceMemberId: {
eq: currentWorkspaceMemberId,
},
},
);
const favorites = _favorites.filter(
(favorite) => favorite.workspaceMemberId === currentWorkspaceMemberId,
);
const workspaceFavorites = _favorites.filter(
(favorite) => favorite.workspaceMemberId === null,
);
const { upsertRecordsInCache: upsertFavorites } =
usePrefetchRunQuery<Favorite>({
prefetchKey: PrefetchKey.AllFavorites,
});
return {
favorites,
workspaceFavorites,
upsertFavorites,
currentWorkspaceMemberId,
};
};

View File

@ -0,0 +1,38 @@
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 { useRecoilValue } from 'recoil';
type PrefetchedFavoritesFoldersData = {
favoriteFolders: FavoriteFolder[];
upsertFavoriteFolders: (records: FavoriteFolder[]) => void;
currentWorkspaceMemberId: string | undefined;
};
export const usePrefetchedFavoritesFoldersData =
(): PrefetchedFavoritesFoldersData => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMemberId = currentWorkspaceMember?.id;
const { records: favoriteFolders } = usePrefetchedData<FavoriteFolder>(
PrefetchKey.AllFavoritesFolders,
{
workspaceMemberId: {
eq: currentWorkspaceMemberId,
},
},
);
const { upsertRecordsInCache: upsertFavoriteFolders } =
usePrefetchRunQuery<FavoriteFolder>({
prefetchKey: PrefetchKey.AllFavoritesFolders,
});
return {
favoriteFolders,
upsertFavoriteFolders,
currentWorkspaceMemberId,
};
};

View File

@ -0,0 +1,28 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
export const useRenameFavoriteFolder = () => {
const { updateOneRecord: updateFavoriteFolder } = useUpdateOneRecord({
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
});
const renameFavoriteFolder = async (
folderId: string,
newName: string,
): Promise<void> => {
if (!newName) {
return;
}
await updateFavoriteFolder({
idToUpdate: folderId,
updateOneRecordInput: {
name: newName,
},
});
};
return {
renameFavoriteFolder,
};
};

View File

@ -0,0 +1,41 @@
import { useSortedFavorites } from '@/favorites/hooks/useSortedFavorites';
import { calculateNewPosition } from '@/favorites/utils/calculateNewPosition';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { OnDragEndResponder } from '@hello-pangea/dnd';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useReorderFavorite = () => {
const { favorites } = usePrefetchedFavoritesData();
const { favoritesSorted } = useSortedFavorites();
const { updateOneRecord: updateOneFavorite } = useUpdateOneRecord({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const reorderFavorite: OnDragEndResponder = (result) => {
if (!result.destination) return;
const draggedFavoriteId = result.draggableId;
const draggedFavorite = favorites.find((f) => f.id === draggedFavoriteId);
if (!draggedFavorite) return;
const inSameFolderFavorites = favoritesSorted.filter(
(fav) => fav.favoriteFolderId === draggedFavorite.favoriteFolderId,
);
if (!inSameFolderFavorites.length) return;
const newPosition = calculateNewPosition({
destinationIndex: result.destination.index,
sourceIndex: result.source.index,
items: inSameFolderFavorites,
});
updateOneFavorite({
idToUpdate: draggedFavoriteId,
updateOneRecordInput: { position: newPosition },
});
};
return reorderFavorite;
};

View File

@ -0,0 +1,75 @@
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 { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useSortedFavorites = () => {
const { favorites, workspaceFavorites } = usePrefetchedFavoritesData();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const getObjectRecordIdentifierByNameSingular =
useGetObjectRecordIdentifierByNameSingular();
const favoriteRelationFieldMetadataItems = useMemo(
() =>
favoriteObjectMetadataItem.fields.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type === FieldMetadataType.Relation &&
fieldMetadataItem.name !== 'workspaceMember' &&
fieldMetadataItem.name !== 'favoriteFolder',
),
[favoriteObjectMetadataItem.fields],
);
const favoritesSorted = useMemo(() => {
return sortFavorites(
favorites,
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
true,
views,
objectMetadataItems,
);
}, [
favoriteRelationFieldMetadataItems,
favorites,
getObjectRecordIdentifierByNameSingular,
views,
objectMetadataItems,
]);
const workspaceFavoritesSorted = useMemo(() => {
return sortFavorites(
workspaceFavorites.filter((favorite) => favorite.viewId),
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
false,
views,
objectMetadataItems,
);
}, [
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
workspaceFavorites,
views,
objectMetadataItems,
]);
return {
favoritesSorted,
workspaceFavoritesSorted,
};
};

View File

@ -0,0 +1,56 @@
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 { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useWorkspaceFavorites = () => {
const { workspaceFavorites } = usePrefetchedFavoritesData();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const getObjectRecordIdentifierByNameSingular =
useGetObjectRecordIdentifierByNameSingular();
const favoriteRelationFieldMetadataItems = useMemo(
() =>
favoriteObjectMetadataItem.fields.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type === FieldMetadataType.Relation &&
fieldMetadataItem.name !== 'workspaceMember' &&
fieldMetadataItem.name !== 'favoriteFolder',
),
[favoriteObjectMetadataItem.fields],
);
const sortedWorkspaceFavorites = useMemo(
() =>
sortFavorites(
workspaceFavorites.filter((favorite) => favorite.viewId),
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
false,
views,
objectMetadataItems,
),
[
workspaceFavorites,
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
views,
objectMetadataItems,
],
);
return sortedWorkspaceFavorites;
};