Skip to content

Commit 4f43851

Browse files
committed
Reuse filterReadableActiveObjectMetadataItems utility and extract dropdown content
- Replace custom useReadableObjectMetadataItems hook with the filterReadableActiveObjectMetadataItems utility from #18747 - Extract SidePanelObjectFilterDropdownContent into its own file - Update all consumers (search hooks, mention search, default home page) Made-with: Cursor
1 parent 1027992 commit 4f43851

File tree

7 files changed

+148
-126
lines changed

7 files changed

+148
-126
lines changed

packages/twenty-front/src/modules/mention/hooks/useMentionSearch.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { SEARCH_QUERY } from '@/command-menu/graphql/queries/search';
22
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
3-
import { useReadableObjectMetadataItems } from '@/object-metadata/hooks/useReadableObjectMetadataItems';
3+
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
4+
import { filterReadableActiveObjectMetadataItems } from '@/object-metadata/utils/filterReadableActiveObjectMetadataItems';
5+
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
46
import { useCallback, useMemo } from 'react';
57
import {
68
type SearchQuery,
@@ -11,15 +13,17 @@ import type { MentionSearchResult } from '@/mention/types/MentionSearchResult';
1113
const MENTION_SEARCH_LIMIT = 50;
1214

1315
export const useMentionSearch = () => {
14-
const { readableObjectMetadataItems } = useReadableObjectMetadataItems();
16+
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
1517
const apolloCoreClient = useApolloCoreClient();
18+
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
1619

1720
const searchableObjectMetadataItems = useMemo(
1821
() =>
19-
readableObjectMetadataItems.filter(
20-
(item) => !item.isSystem && item.isSearchable,
21-
),
22-
[readableObjectMetadataItems],
22+
filterReadableActiveObjectMetadataItems(
23+
activeObjectMetadataItems,
24+
objectPermissionsByObjectMetadataId,
25+
).filter((item) => !item.isSystem && item.isSearchable),
26+
[activeObjectMetadataItems, objectPermissionsByObjectMetadataId],
2327
);
2428

2529
const objectsToSearch = useMemo(

packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordSubPage.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import { isDefined } from 'twenty-shared/utils';
44
import { useDebounce } from 'use-debounce';
55

66
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
7-
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/edit/hooks/useDraftNavigationMenuItems';
87
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/common/states/addMenuItemInsertionContextState';
8+
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/edit/hooks/useDraftNavigationMenuItems';
99
import { SidePanelNewSidebarItemRecordItem } from '@/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordItem';
1010
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
11-
import { useReadableObjectMetadataItems } from '@/object-metadata/hooks/useReadableObjectMetadataItems';
11+
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
12+
import { filterReadableActiveObjectMetadataItems } from '@/object-metadata/utils/filterReadableActiveObjectMetadataItems';
13+
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
1214
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
1315
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
1416
import { SidePanelList } from '@/side-panel/components/SidePanelList';
@@ -35,17 +37,21 @@ export const SidePanelNewSidebarItemRecordSubPage = () => {
3537
const [recordSearchInput, setRecordSearchInput] = useState('');
3638
const [deferredRecordSearchInput] = useDebounce(recordSearchInput, 300);
3739
const coreClient = useApolloCoreClient();
38-
const { readableObjectMetadataItems } = useReadableObjectMetadataItems();
40+
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
41+
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
3942
const [selectedObjectNameSingular, setSelectedObjectNameSingular] = useState<
4043
string | null
4144
>(null);
4245

4346
const searchableObjectNameSingulars = useMemo(
4447
() =>
45-
readableObjectMetadataItems
48+
filterReadableActiveObjectMetadataItems(
49+
activeObjectMetadataItems,
50+
objectPermissionsByObjectMetadataId,
51+
)
4652
.filter((item) => item.isSearchable)
4753
.map((item) => item.nameSingular),
48-
[readableObjectMetadataItems],
54+
[activeObjectMetadataItems, objectPermissionsByObjectMetadataId],
4955
);
5056

5157
const includedObjectNameSingulars = isDefined(selectedObjectNameSingular)

packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { currentUserState } from '@/auth/states/currentUserState';
22
import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState';
33
import { type ObjectPathInfo } from '@/navigation/types/ObjectPathInfo';
4-
import { useReadableObjectMetadataItems } from '@/object-metadata/hooks/useReadableObjectMetadataItems';
4+
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
5+
import { filterReadableActiveObjectMetadataItems } from '@/object-metadata/utils/filterReadableActiveObjectMetadataItems';
6+
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
57
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
68
import { viewsSelector } from '@/views/states/selectors/viewsSelector';
79
import isEmpty from 'lodash.isempty';
@@ -13,15 +15,19 @@ import { useStore } from 'jotai';
1315
export const useDefaultHomePagePath = () => {
1416
const store = useStore();
1517
const currentUser = useAtomStateValue(currentUserState);
18+
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
1619

17-
const { readableObjectMetadataItems } = useReadableObjectMetadataItems();
20+
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
1821

1922
const readableNonSystemObjectMetadataItems = useMemo(
2023
() =>
21-
readableObjectMetadataItems
24+
filterReadableActiveObjectMetadataItems(
25+
activeObjectMetadataItems,
26+
objectPermissionsByObjectMetadataId,
27+
)
2228
.filter((item) => !item.isSystem)
2329
.sort((a, b) => a.nameSingular.localeCompare(b.nameSingular)),
24-
[readableObjectMetadataItems],
30+
[activeObjectMetadataItems, objectPermissionsByObjectMetadataId],
2531
);
2632

2733
const getActiveObjectMetadataItemMatchingId = useCallback(

packages/twenty-front/src/modules/object-metadata/hooks/useReadableObjectMetadataItems.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

packages/twenty-front/src/modules/side-panel/components/SidePanelObjectFilterDropdown.tsx

Lines changed: 4 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,12 @@
11
import { useLingui } from '@lingui/react/macro';
2-
import { useMemo, useState } from 'react';
32
import { isDefined } from 'twenty-shared/utils';
4-
import { IconCube, IconFilter, useIcons } from 'twenty-ui/display';
3+
import { IconFilter } from 'twenty-ui/display';
54
import { IconButton } from 'twenty-ui/input';
6-
import { MenuItemSelectAvatar } from 'twenty-ui/navigation';
75

8-
import { getStandardObjectIconColor } from '@/navigation-menu-item/common/utils/getStandardObjectIconColor';
9-
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/display/components/NavigationMenuItemStyleIcon';
10-
import { useReadableObjectMetadataItems } from '@/object-metadata/hooks/useReadableObjectMetadataItems';
6+
import { SidePanelObjectFilterDropdownContent } from '@/side-panel/components/SidePanelObjectFilterDropdownContent';
117
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
12-
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
13-
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
14-
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
15-
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
16-
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
17-
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
188

19-
const OBJECT_FILTER_DROPDOWN_ID = 'side-panel-object-filter-dropdown';
20-
21-
const ObjectFilterDropdownContent = ({
22-
selectedObjectNameSingular,
23-
onSelectObject,
24-
}: {
25-
selectedObjectNameSingular: string | null;
26-
onSelectObject: (objectNameSingular: string | null) => void;
27-
}) => {
28-
const { t } = useLingui();
29-
const { getIcon } = useIcons();
30-
const [filterSearch, setFilterSearch] = useState('');
31-
const { readableObjectMetadataItems } = useReadableObjectMetadataItems();
32-
const { closeDropdown } = useCloseDropdown();
33-
34-
const searchableObjectItems = useMemo(
35-
() =>
36-
readableObjectMetadataItems.filter(
37-
(item) =>
38-
item.isSearchable &&
39-
item.labelPlural.toLowerCase().includes(filterSearch.toLowerCase()),
40-
),
41-
[readableObjectMetadataItems, filterSearch],
42-
);
43-
44-
const handleSelect = (objectNameSingular: string | null) => {
45-
onSelectObject(objectNameSingular);
46-
closeDropdown(OBJECT_FILTER_DROPDOWN_ID);
47-
};
48-
49-
return (
50-
<DropdownContent>
51-
<DropdownMenuHeader>{t`Object`}</DropdownMenuHeader>
52-
<DropdownMenuSearchInput
53-
value={filterSearch}
54-
onChange={(event) => setFilterSearch(event.target.value)}
55-
/>
56-
<DropdownMenuSeparator />
57-
<DropdownMenuItemsContainer hasMaxHeight>
58-
<MenuItemSelectAvatar
59-
avatar={<NavigationMenuItemStyleIcon Icon={IconCube} color="gray" />}
60-
text={t`All Objects`}
61-
selected={selectedObjectNameSingular === null}
62-
onClick={() => handleSelect(null)}
63-
/>
64-
{searchableObjectItems.map((objectMetadataItem) => {
65-
const ObjectIcon = getIcon(objectMetadataItem.icon);
66-
const iconColor = getStandardObjectIconColor(
67-
objectMetadataItem.nameSingular,
68-
);
69-
70-
return (
71-
<MenuItemSelectAvatar
72-
key={objectMetadataItem.id}
73-
avatar={
74-
<NavigationMenuItemStyleIcon
75-
Icon={ObjectIcon}
76-
color={iconColor}
77-
/>
78-
}
79-
text={objectMetadataItem.labelPlural}
80-
selected={
81-
selectedObjectNameSingular === objectMetadataItem.nameSingular
82-
}
83-
onClick={() => handleSelect(objectMetadataItem.nameSingular)}
84-
/>
85-
);
86-
})}
87-
</DropdownMenuItemsContainer>
88-
</DropdownContent>
89-
);
90-
};
9+
export const OBJECT_FILTER_DROPDOWN_ID = 'side-panel-object-filter-dropdown';
9110

9211
type SidePanelObjectFilterDropdownProps = {
9312
selectedObjectNameSingular: string | null;
@@ -115,7 +34,7 @@ export const SidePanelObjectFilterDropdown = ({
11534
/>
11635
}
11736
dropdownComponents={
118-
<ObjectFilterDropdownContent
37+
<SidePanelObjectFilterDropdownContent
11938
selectedObjectNameSingular={selectedObjectNameSingular}
12039
onSelectObject={onSelectObject}
12140
/>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { useLingui } from '@lingui/react/macro';
2+
import { useMemo, useState } from 'react';
3+
import { IconCube, useIcons } from 'twenty-ui/display';
4+
import { MenuItemSelectAvatar } from 'twenty-ui/navigation';
5+
6+
import { getStandardObjectIconColor } from '@/navigation-menu-item/common/utils/getStandardObjectIconColor';
7+
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/display/components/NavigationMenuItemStyleIcon';
8+
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
9+
import { filterReadableActiveObjectMetadataItems } from '@/object-metadata/utils/filterReadableActiveObjectMetadataItems';
10+
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
11+
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
12+
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
13+
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
14+
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
15+
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
16+
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
17+
18+
import { OBJECT_FILTER_DROPDOWN_ID } from '@/side-panel/components/SidePanelObjectFilterDropdown';
19+
20+
type SidePanelObjectFilterDropdownContentProps = {
21+
selectedObjectNameSingular: string | null;
22+
onSelectObject: (objectNameSingular: string | null) => void;
23+
};
24+
25+
export const SidePanelObjectFilterDropdownContent = ({
26+
selectedObjectNameSingular,
27+
onSelectObject,
28+
}: SidePanelObjectFilterDropdownContentProps) => {
29+
const { t } = useLingui();
30+
const { getIcon } = useIcons();
31+
const [filterSearch, setFilterSearch] = useState('');
32+
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
33+
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
34+
const { closeDropdown } = useCloseDropdown();
35+
36+
const searchableObjectItems = useMemo(
37+
() =>
38+
filterReadableActiveObjectMetadataItems(
39+
activeObjectMetadataItems,
40+
objectPermissionsByObjectMetadataId,
41+
).filter(
42+
(item) =>
43+
item.isSearchable &&
44+
item.labelPlural.toLowerCase().includes(filterSearch.toLowerCase()),
45+
),
46+
[
47+
activeObjectMetadataItems,
48+
objectPermissionsByObjectMetadataId,
49+
filterSearch,
50+
],
51+
);
52+
53+
const handleSelect = (objectNameSingular: string | null) => {
54+
onSelectObject(objectNameSingular);
55+
closeDropdown(OBJECT_FILTER_DROPDOWN_ID);
56+
};
57+
58+
return (
59+
<DropdownContent>
60+
<DropdownMenuHeader>{t`Object`}</DropdownMenuHeader>
61+
<DropdownMenuSearchInput
62+
value={filterSearch}
63+
onChange={(event) => setFilterSearch(event.target.value)}
64+
/>
65+
<DropdownMenuSeparator />
66+
<DropdownMenuItemsContainer hasMaxHeight>
67+
<MenuItemSelectAvatar
68+
avatar={<NavigationMenuItemStyleIcon Icon={IconCube} color="gray" />}
69+
text={t`All Objects`}
70+
selected={selectedObjectNameSingular === null}
71+
onClick={() => handleSelect(null)}
72+
/>
73+
{searchableObjectItems.map((objectMetadataItem) => {
74+
const ObjectIcon = getIcon(objectMetadataItem.icon);
75+
const iconColor = getStandardObjectIconColor(
76+
objectMetadataItem.nameSingular,
77+
);
78+
79+
return (
80+
<MenuItemSelectAvatar
81+
key={objectMetadataItem.id}
82+
avatar={
83+
<NavigationMenuItemStyleIcon
84+
Icon={ObjectIcon}
85+
color={iconColor}
86+
/>
87+
}
88+
text={objectMetadataItem.labelPlural}
89+
selected={
90+
selectedObjectNameSingular === objectMetadataItem.nameSingular
91+
}
92+
onClick={() => handleSelect(objectMetadataItem.nameSingular)}
93+
/>
94+
);
95+
})}
96+
</DropdownMenuItemsContainer>
97+
</DropdownContent>
98+
);
99+
};

packages/twenty-front/src/modules/side-panel/pages/search/hooks/useSidePanelSearchRecords.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { CommandLink } from '@/command-menu-item/display/components/CommandLink'
33
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
44
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
55
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
6-
import { useReadableObjectMetadataItems } from '@/object-metadata/hooks/useReadableObjectMetadataItems';
6+
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
7+
import { filterReadableActiveObjectMetadataItems } from '@/object-metadata/utils/filterReadableActiveObjectMetadataItems';
8+
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
79
import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
810
import { sidePanelSearchObjectFilterState } from '@/side-panel/states/sidePanelSearchObjectFilterState';
911
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
@@ -26,7 +28,17 @@ export const useSidePanelSearchRecords = () => {
2628
const coreClient = useApolloCoreClient();
2729

2830
const [deferredSidePanelSearch] = useDebounce(sidePanelSearch, 300);
29-
const { readableObjectMetadataItems } = useReadableObjectMetadataItems();
31+
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
32+
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
33+
34+
const readableObjectMetadataItems = useMemo(
35+
() =>
36+
filterReadableActiveObjectMetadataItems(
37+
activeObjectMetadataItems,
38+
objectPermissionsByObjectMetadataId,
39+
),
40+
[activeObjectMetadataItems, objectPermissionsByObjectMetadataId],
41+
);
3042

3143
const searchableObjectNameSingulars = useMemo(
3244
() =>

0 commit comments

Comments
 (0)