Files
twenty/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts
T
Abdul Rahman 9fe2a07c55 Navbar customization v2 (#18026)
Adds color support for navigation menu items.

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-02-28 11:48:28 +01:00

156 lines
5.2 KiB
TypeScript

import { isDefined } from 'twenty-shared/utils';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/utils/recordIdentifierToObjectRecordIdentifier';
import { sortNavigationMenuItems } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
import { coreViewsState } from '@/views/states/coreViewState';
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { usePrefetchedNavigationMenuItemsData } from './usePrefetchedNavigationMenuItemsData';
type NavigationMenuItemFolder = {
id: string;
folderName: string;
icon?: string | null;
color?: string | null;
navigationMenuItems: ReturnType<typeof sortNavigationMenuItems>[number][];
};
type NavigationMenuItemFolderEntry = Pick<
NavigationMenuItemFolder,
'id' | 'folderName' | 'icon' | 'color'
>;
export const useNavigationMenuItemsByFolder = () => {
const coreViews = useAtomStateValue(coreViewsState);
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
const { navigationMenuItems, workspaceNavigationMenuItems } =
usePrefetchedNavigationMenuItemsData();
const views = coreViews.map(convertCoreViewToView);
const allNavigationMenuItems = [
...workspaceNavigationMenuItems,
...navigationMenuItems,
];
const { workspaceFolders, userFolders, itemsByFolderId } =
allNavigationMenuItems.reduce<{
workspaceFolders: NavigationMenuItemFolderEntry[];
userFolders: NavigationMenuItemFolderEntry[];
itemsByFolderId: Map<string, NavigationMenuItem[]>;
}>(
(acc, item) => {
if (isNavigationMenuItemFolder(item)) {
const folderEntry: NavigationMenuItemFolderEntry = {
id: item.id,
folderName: item.name || 'Folder',
icon: item.icon ?? undefined,
color: item.color ?? undefined,
};
if (isDefined(item.userWorkspaceId)) {
acc.userFolders.push(folderEntry);
} else {
acc.workspaceFolders.push(folderEntry);
}
} else if (isDefined(item.folderId)) {
const existingItems = acc.itemsByFolderId.get(item.folderId);
if (isDefined(existingItems)) {
existingItems.push(item);
} else {
acc.itemsByFolderId.set(item.folderId, [item]);
}
}
return acc;
},
{
workspaceFolders: [],
userFolders: [],
itemsByFolderId: new Map(),
},
);
const buildFoldersList = (folders: NavigationMenuItemFolderEntry[]) => {
const sortedFolders = [...folders].sort((a, b) => {
const folderA = allNavigationMenuItems.find((item) => item.id === a.id);
const folderB = allNavigationMenuItems.find((item) => item.id === b.id);
const positionA = folderA?.position ?? 0;
const positionB = folderB?.position ?? 0;
return positionA - positionB;
});
return sortedFolders.reduce<NavigationMenuItemFolder[]>((acc, folder) => {
const itemsInFolder = itemsByFolderId.get(folder.id) || [];
const targetRecordIdentifiersMap = itemsInFolder.reduce<
Map<string, ObjectRecordIdentifier>
>((map, item) => {
const itemTargetRecordId = item.targetRecordId;
if (!isDefined(itemTargetRecordId) || isDefined(item.viewId)) {
return map;
}
const targetRecordIdentifier = item.targetRecordIdentifier;
if (!isDefined(targetRecordIdentifier)) {
return map;
}
const itemObjectMetadata = objectMetadataItems.find(
(meta) => meta.id === item.targetObjectMetadataId,
);
if (isDefined(itemObjectMetadata)) {
const objectRecordIdentifier =
recordIdentifierToObjectRecordIdentifier({
recordIdentifier: targetRecordIdentifier,
objectMetadataItem: itemObjectMetadata,
});
map.set(itemTargetRecordId, objectRecordIdentifier);
}
return map;
}, new Map());
const sortedItems = sortNavigationMenuItems(
itemsInFolder,
true,
views,
objectMetadataItems,
targetRecordIdentifiersMap,
);
acc.push({
id: folder.id,
folderName: folder.folderName,
icon: folder.icon,
color: folder.color,
navigationMenuItems: sortedItems,
});
return acc;
}, []);
};
const workspaceNavigationMenuItemsByFolder =
buildFoldersList(workspaceFolders);
const userNavigationMenuItemsByFolder = buildFoldersList(userFolders);
const navigationMenuItemsByFolder = [
...workspaceNavigationMenuItemsByFolder,
...userNavigationMenuItemsByFolder,
];
return {
navigationMenuItemsByFolder,
workspaceNavigationMenuItemsByFolder,
userNavigationMenuItemsByFolder,
};
};