Fix breadcrumbs to display friendly object names instead of API names… (#13573)

Fixes #13250

This PR fixes the breadcrumb display issue where API names were shown
instead of friendly object names when navigating to related objects in
the detail view.

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
aaron
2025-08-03 21:18:32 +02:00
committed by GitHub
co-authored by Félix Malfait
parent 253a9221db
commit b8a26dfe8c
27 changed files with 211 additions and 109 deletions
@@ -5,15 +5,14 @@ import { ActionType } from '@/action-menu/actions/types/ActionType';
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { AppPath } from '@/types/AppPath';
import { t } from '@lingui/core/macro';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { capitalize } from 'twenty-shared/utils';
import { Avatar } from 'twenty-ui/display';
import { useDebounce } from 'use-debounce';
import { useSearchQuery } from '~/generated/graphql';
@@ -73,7 +72,10 @@ export const useCommandMenuSearchRecords = () => {
/>
),
shouldBeRegistered: () => true,
description: capitalize(searchRecord.objectNameSingular),
description:
objectMetadataItems.find(
(item) => item.nameSingular === searchRecord.objectNameSingular,
)?.labelSingular ?? searchRecord.objectNameSingular,
};
if (
@@ -116,7 +118,7 @@ export const useCommandMenuSearchRecords = () => {
};
},
);
}, [searchData, openRecordInCommandMenu]);
}, [searchData, openRecordInCommandMenu, objectMetadataItems]);
return {
loading,
@@ -21,7 +21,7 @@ import { useRunWorkflowRunOpeningInCommandMenuSideEffects } from '@/workflow/hoo
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { v4 } from 'uuid';
@@ -164,13 +164,13 @@ export const useOpenRecordInCommandMenu = () => {
theme,
});
const capitalizedObjectNameSingular = capitalize(objectNameSingular);
const objectLabelSingular = objectMetadataItem.labelSingular;
navigateCommandMenu({
page: CommandMenuPages.ViewRecord,
pageTitle: isNewRecord
? t`New ${capitalizedObjectNameSingular}`
: capitalizedObjectNameSingular,
? t`New ${objectLabelSingular}`
: objectLabelSingular,
pageIcon: Icon,
pageIconColor: IconColor,
pageId: pageComponentInstanceId,
@@ -1,7 +1,6 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { capitalize } from 'twenty-shared/utils';
export const getSelectedRecordsContextText = (
objectMetadataItem: ObjectMetadataItem,
@@ -10,5 +9,5 @@ export const getSelectedRecordsContextText = (
) => {
return totalCount === 1
? getObjectRecordIdentifier({ objectMetadataItem, record: records[0] }).name
: `${totalCount} ${capitalize(objectMetadataItem.namePlural)}`;
: `${totalCount} ${objectMetadataItem.labelPlural}`;
};
@@ -1,5 +1,6 @@
import { FavoriteFolderNavigationDrawerItemDropdown } from '@/favorites/components/FavoriteFolderNavigationDrawerItemDropdown';
import { FavoriteIcon } from '@/favorites/components/FavoriteIcon';
import { FavoriteNavigationDrawerSubItem } from '@/favorites/components/FavoriteNavigationDrawerSubItem';
import { FavoritesDroppable } from '@/favorites/components/FavoritesDroppable';
import { FAVORITE_FOLDER_DELETE_MODAL_ID } from '@/favorites/constants/FavoriteFolderDeleteModalId';
import { FavoritesDragContext } from '@/favorites/contexts/FavoritesDragContext';
@@ -18,7 +19,6 @@ import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpe
import { NavigationDrawerInput } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerInput';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer';
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { Droppable } from '@hello-pangea/dnd';
@@ -196,9 +196,9 @@ export const CurrentWorkspaceMemberFavorites = ({
index={index}
isInsideScrollableContainer
itemComponent={
<NavigationDrawerSubItem
<FavoriteNavigationDrawerSubItem
favorite={favorite}
label={favorite.labelIdentifier}
objectName={favorite.objectNameSingular}
Icon={() => <FavoriteIcon favorite={favorite} />}
to={isDragging ? undefined : favorite.link}
active={index === selectedFavoriteIndex}
@@ -1,11 +1,11 @@
import { FavoriteIcon } from '@/favorites/components/FavoriteIcon';
import { FavoriteNavigationDrawerItem } from '@/favorites/components/FavoriteNavigationDrawerItem';
import { FavoritesDroppable } from '@/favorites/components/FavoritesDroppable';
import { FavoritesDragContext } from '@/favorites/contexts/FavoritesDragContext';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { isLocationMatchingFavorite } from '@/favorites/utils/isLocationMatchingFavorite';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { useLocation } from 'react-router-dom';
@@ -42,7 +42,8 @@ export const CurrentWorkspaceMemberOrphanFavorites = () => {
isInsideScrollableContainer={true}
itemComponent={
<StyledOrphanFavoritesContainer>
<NavigationDrawerItem
<FavoriteNavigationDrawerItem
favorite={favorite}
label={favorite.labelIdentifier}
Icon={() => <FavoriteIcon favorite={favorite} />}
active={isLocationMatchingFavorite(
@@ -58,7 +59,6 @@ export const CurrentWorkspaceMemberOrphanFavorites = () => {
accent="tertiary"
/>
}
objectName={favorite.objectNameSingular}
isDragging={isDragging}
triggerEvent="CLICK"
/>
@@ -0,0 +1,50 @@
import { ProcessedFavorite } from '@/favorites/utils/sortFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
type FavoriteNavigationDrawerItemProps = {
favorite: ProcessedFavorite;
label: string;
Icon?: React.ComponentProps<typeof NavigationDrawerItem>['Icon'];
to?: string;
active?: boolean;
rightOptions?: React.ComponentProps<
typeof NavigationDrawerItem
>['rightOptions'];
isDragging?: boolean;
triggerEvent?: React.ComponentProps<
typeof NavigationDrawerItem
>['triggerEvent'];
};
export const FavoriteNavigationDrawerItem = ({
favorite,
label,
Icon,
to,
active,
rightOptions,
isDragging,
triggerEvent,
}: FavoriteNavigationDrawerItemProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: favorite.objectNameSingular || '',
});
if (!favorite.objectNameSingular) {
return null;
}
return (
<NavigationDrawerItem
label={label}
secondaryLabel={objectMetadataItem.labelSingular}
Icon={Icon}
to={to}
active={active}
rightOptions={rightOptions}
isDragging={isDragging}
triggerEvent={triggerEvent}
/>
);
};
@@ -0,0 +1,55 @@
import { ProcessedFavorite } from '@/favorites/utils/sortFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
type FavoriteNavigationDrawerSubItemProps = {
favorite: ProcessedFavorite;
label: string;
Icon?: React.ComponentProps<typeof NavigationDrawerSubItem>['Icon'];
to?: string;
active?: boolean;
subItemState?: React.ComponentProps<
typeof NavigationDrawerSubItem
>['subItemState'];
rightOptions?: React.ComponentProps<
typeof NavigationDrawerSubItem
>['rightOptions'];
isDragging?: boolean;
triggerEvent?: React.ComponentProps<
typeof NavigationDrawerSubItem
>['triggerEvent'];
};
export const FavoriteNavigationDrawerSubItem = ({
favorite,
label,
Icon,
to,
active,
subItemState,
rightOptions,
isDragging,
triggerEvent,
}: FavoriteNavigationDrawerSubItemProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: favorite.objectNameSingular || '',
});
if (!favorite.objectNameSingular) {
return null;
}
return (
<NavigationDrawerSubItem
label={label}
secondaryLabel={objectMetadataItem.labelSingular}
Icon={Icon}
to={to}
active={active}
subItemState={subItemState}
rightOptions={rightOptions}
isDragging={isDragging}
triggerEvent={triggerEvent}
/>
);
};
@@ -24,7 +24,6 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import styled from '@emotion/styled';
import { useRecoilCallback } from 'recoil';
import { capitalize } from 'twenty-shared/utils';
import { NotFound } from '~/pages/not-found/NotFound';
const StyledIndexContainer = styled.div`
@@ -102,9 +101,7 @@ export const RecordIndexContainerGater = () => {
instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
}}
>
<PageTitle
title={`${capitalize(objectMetadataItem.namePlural)}`}
/>
<PageTitle title={objectMetadataItem.labelPlural} />
<RecordIndexPageHeader />
<PageBody>
<StyledIndexContainer
@@ -9,7 +9,7 @@ import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
const StyledTitleWithSelectedRecords = styled.div`
@@ -44,7 +44,7 @@ export const RecordIndexPageHeader = () => {
const { getIcon } = useIcons();
const Icon = getIcon(objectMetadataItem?.icon);
const label = objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural);
const label = objectMetadataItem?.labelPlural ?? objectNamePlural;
const pageHeaderTitle =
contextStoreNumberOfSelectedRecords > 0 ? (
@@ -10,7 +10,6 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { capitalize } from 'twenty-shared/utils';
import { Avatar } from 'twenty-ui/display';
import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
import { SearchRecord } from '~/generated-metadata/graphql';
@@ -61,7 +60,7 @@ export const MultipleRecordPickerMenuItemContent = ({
const displayText =
searchRecord.label?.trim() ||
`Untitled ${capitalize(objectMetadataItem.nameSingular)}`;
`Untitled ${objectMetadataItem.labelSingular}`;
return (
<StyledSelectableItem
@@ -11,7 +11,6 @@ import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { FieldMetadataType } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
const StyledEditableTitleContainer = styled.div`
align-items: center;
@@ -43,12 +42,12 @@ const StyledPaginationInformation = styled.span`
export const ObjectRecordShowPageBreadcrumb = ({
objectNameSingular,
objectRecordId,
objectLabelPlural,
objectLabel,
labelIdentifierFieldMetadataItem,
}: {
objectNameSingular: string;
objectRecordId: string;
objectLabelPlural: string;
objectLabel: string;
labelIdentifierFieldMetadataItem?: FieldMetadataItem;
}) => {
const { loading } = useFindOneRecord({
@@ -95,7 +94,7 @@ export const ObjectRecordShowPageBreadcrumb = ({
}}
>
{HeaderIcon && <HeaderIcon size={theme.icon.size.md} />}
{capitalize(objectLabelPlural)}
{objectLabel}
<span>{' / '}</span>
</StyledEditableTitlePrefix>
<StyledTitle>
@@ -9,7 +9,7 @@ import { lastShowPageRecordIdState } from '@/object-record/record-field/states/l
import { useRecordIdsFromFindManyCacheRootQuery } from '@/object-record/record-show/hooks/useRecordIdsFromFindManyCacheRootQuery';
import { AppPath } from '@/types/AppPath';
import { useQueryVariablesFromParentView } from '@/views/hooks/useQueryVariablesFromParentView';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
import { useNavigateApp } from '~/hooks/useNavigateApp';
export const useRecordShowPagePagination = (
@@ -216,13 +216,13 @@ export const useRecordShowPagePagination = (
const rankFoundInView = rankInView > -1;
const objectLabel = capitalize(objectMetadataItem.labelPlural);
const objectLabelPlural = objectMetadataItem.labelPlural;
const totalCount = 1 + Math.max(totalCountBefore, totalCountAfter);
const viewNameWithCount = rankFoundInView
? `${rankInView + 1} of ${totalCount} in ${objectLabel}`
: `${objectLabel} (${totalCount})`;
? `${rankInView + 1} of ${totalCount} in ${objectLabelPlural}`
: `${objectLabelPlural} (${totalCount})`;
return {
viewName: viewNameWithCount,
@@ -17,18 +17,18 @@ export const RecordTableEmptyStateNoGroupNoRecordAtAll = () => {
createNewIndexRecord();
};
const objectLabel = useObjectLabel(objectMetadataItem);
const objectLabelSingular = useObjectLabel(objectMetadataItem);
const buttonTitle = `Add a ${objectLabel}`;
const buttonTitle = `Add a ${objectLabelSingular}`;
const title = getEmptyStateTitle(
objectMetadataItem.nameSingular,
objectLabel,
objectLabelSingular,
);
const subTitle = getEmptyStateSubTitle(
objectMetadataItem.nameSingular,
objectLabel,
objectLabelSingular,
);
return (
@@ -15,11 +15,11 @@ export const RecordTableEmptyStateNoRecordFoundForFilter = () => {
createNewIndexRecord();
};
const objectLabel = useObjectLabel(objectMetadataItem);
const objectLabelSingular = useObjectLabel(objectMetadataItem);
const buttonTitle = `Add a ${objectLabel}`;
const buttonTitle = `Add a ${objectLabelSingular}`;
const title = `No ${objectLabel} found`;
const title = `No ${objectLabelSingular} found`;
const subTitle = 'No records matching the filter criteria were found.';
@@ -7,9 +7,9 @@ import { IconPlus } from 'twenty-ui/display';
export const RecordTableEmptyStateReadOnly = () => {
const { objectMetadataItem } = useRecordTableContextOrThrow();
const objectLabel = useObjectLabel(objectMetadataItem);
const objectLabelSingular = useObjectLabel(objectMetadataItem);
const buttonTitle = `Add a ${objectLabel}`;
const buttonTitle = `Add a ${objectLabelSingular}`;
return (
<RecordTableEmptyStateDisplay
@@ -39,13 +39,13 @@ export const RecordTableEmptyStateSoftDelete = () => {
toggleSoftDeleteFilterState(false);
};
const objectLabel = useObjectLabel(objectMetadataItem);
const objectLabelSingular = useObjectLabel(objectMetadataItem);
return (
<RecordTableEmptyStateDisplay
buttonTitle={'Remove Deleted filter'}
subTitle={'No deleted records matching the filter criteria were found.'}
title={`No Deleted ${objectLabel} found`}
title={`No Deleted ${objectLabelSingular} found`}
ButtonIcon={IconFilterOff}
animatedPlaceholderType="noDeletedRecord"
onClick={handleButtonClick}
@@ -14,7 +14,6 @@ import { ObjectFieldRowWithoutRelation } from '@/settings/data-model/graph-overv
import { SettingsPath } from '@/types/SettingsPath';
import '@xyflow/react/dist/style.css';
import { useState } from 'react';
import { capitalize } from 'twenty-shared/utils';
import { IconChevronDown, IconChevronUp, useIcons } from 'twenty-ui/display';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
@@ -131,7 +130,7 @@ export const SettingsDataModelOverviewObject = ({
})}
>
{Icon && <Icon size={theme.icon.size.md} />}
{capitalize(objectMetadataItem.namePlural)}
{objectMetadataItem.labelPlural}
</StyledObjectLink>
<StyledObjectInstanceCount> · {totalCount}</StyledObjectInstanceCount>
</StyledObjectName>
@@ -44,7 +44,7 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
const Icon = getIcon(objectMetadataItem.icon);
const objectLabel = objectMetadataItem.labelPlural;
const objectLabelPlural = objectMetadataItem.labelPlural;
return (
<TableRow
@@ -62,15 +62,15 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
stroke={theme.icon.stroke.sm}
/>
)}
<StyledNameLabel title={objectLabel}>
<OverflowingTextWithTooltip text={objectLabel} />
<StyledNameLabel title={objectLabelPlural}>
<OverflowingTextWithTooltip text={objectLabelPlural} />
</StyledNameLabel>
</StyledNameTableCell>
<TableCell>
<SettingsRolePermissionsObjectLevelOverrideCellContainer
objectPermissions={objectPermission}
roleId={roleId}
objectLabel={objectLabel}
objectLabel={objectLabelPlural}
/>
</TableCell>
<TableCell align={'right'}>
@@ -41,7 +41,7 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
return null;
}
const objectLabel = objectMetadataItem.labelPlural;
const objectLabelPlural = objectMetadataItem.labelPlural;
const updateObjectPermission = (
permissionKey: SettingsRoleObjectPermissionKey,
@@ -85,7 +85,7 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
[
{
key: 'canReadObjectRecords',
label: t`See ${objectLabel}`,
label: t`See ${objectLabelPlural}`,
value: settingsDraftRoleObjectPermissions.canReadObjectRecords,
setValue: (value: boolean | null) => {
updateObjectPermission('canReadObjectRecords', value);
@@ -93,7 +93,7 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
},
{
key: 'canUpdateObjectRecords',
label: t`Edit ${objectLabel}`,
label: t`Edit ${objectLabelPlural}`,
value: settingsDraftRoleObjectPermissions.canUpdateObjectRecords,
setValue: (value: boolean | null) => {
updateObjectPermission('canUpdateObjectRecords', value);
@@ -101,7 +101,7 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
},
{
key: 'canSoftDeleteObjectRecords',
label: t`Delete ${objectLabel}`,
label: t`Delete ${objectLabelPlural}`,
value: settingsDraftRoleObjectPermissions.canSoftDeleteObjectRecords,
setValue: (value: boolean | null) => {
updateObjectPermission('canSoftDeleteObjectRecords', value);
@@ -109,7 +109,7 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
},
{
key: 'canDestroyObjectRecords',
label: t`Destroy ${objectLabel}`,
label: t`Destroy ${objectLabelPlural}`,
value: settingsDraftRoleObjectPermissions.canDestroyObjectRecords,
setValue: (value: boolean | null) => {
updateObjectPermission('canDestroyObjectRecords', value);
@@ -11,7 +11,6 @@ import styled from '@emotion/styled';
import { ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { capitalize } from 'twenty-shared/utils';
import { Pill } from 'twenty-ui/components';
import { IconComponent, Label, TablerIconsProps } from 'twenty-ui/display';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
@@ -24,7 +23,7 @@ export type NavigationDrawerItemIndentationLevel = 1 | 2;
export type NavigationDrawerItemProps = {
className?: string;
label: string;
objectName?: string;
secondaryLabel?: string;
indentationLevel?: NavigationDrawerItemIndentationLevel;
subItemState?: NavigationDrawerSubItemState;
to?: string;
@@ -142,7 +141,7 @@ const StyledItemLabel = styled.span`
font-weight: ${({ theme }) => theme.font.weight.medium};
`;
const StyledItemObjectName = styled.span`
const StyledItemSecondaryLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-weight: ${({ theme }) => theme.font.weight.regular};
`;
@@ -240,7 +239,7 @@ const StyledRightOptionsVisbility = styled.div<{
export const NavigationDrawerItem = ({
className,
label,
objectName,
secondaryLabel,
indentationLevel = DEFAULT_INDENTATION_LEVEL,
Icon,
to,
@@ -263,6 +262,7 @@ export const NavigationDrawerItem = ({
const isSettingsPage = useIsSettingsPage();
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
useRecoilState(isNavigationDrawerExpandedState);
const showBreadcrumb = indentationLevel === 2;
const showStyledSpacer = Boolean(
soon || isNew || count || keyboard || rightOptions,
@@ -330,11 +330,11 @@ export const NavigationDrawerItem = ({
<StyledLabelParent>
<StyledEllipsisContainer>
<StyledItemLabel>{label}</StyledItemLabel>
{objectName && (
<StyledItemObjectName>
{secondaryLabel && (
<StyledItemSecondaryLabel>
{' · '}
{capitalize(objectName)}
</StyledItemObjectName>
{secondaryLabel}
</StyledItemSecondaryLabel>
)}
</StyledEllipsisContainer>
</StyledLabelParent>
@@ -8,7 +8,7 @@ type NavigationDrawerSubItemProps = NavigationDrawerItemProps;
export const NavigationDrawerSubItem = ({
className,
label,
objectName,
secondaryLabel,
Icon,
to,
onClick,
@@ -26,7 +26,7 @@ export const NavigationDrawerSubItem = ({
<NavigationDrawerItem
className={className}
label={label}
objectName={objectName}
secondaryLabel={secondaryLabel}
indentationLevel={2}
subItemState={subItemState}
Icon={Icon}
@@ -1,21 +1,23 @@
import { useParams } from 'react-router-dom';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { capitalize } from 'twenty-shared/utils';
export const ViewBarPageTitle = () => {
const { objectNamePlural } = useParams();
const { currentView } = useGetCurrentViewOnly();
if (!objectNamePlural) {
return;
}
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural: objectNamePlural ?? '',
});
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const pageTitle = currentView?.name
? `${currentView?.name} - ${capitalize(objectNamePlural)}`
: capitalize(objectNamePlural);
? `${currentView?.name} - ${objectMetadataItem.labelPlural}`
: objectMetadataItem.labelPlural;
return <PageTitle title={pageTitle} />;
};
@@ -5,16 +5,16 @@ import { WorkflowDeleteRecordAction } from '@/workflow/types/Workflow';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { useEffect, useState } from 'react';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { useTheme } from '@emotion/react';
import { isDefined } from 'twenty-shared/utils';
import { HorizontalSeparator, useIcons } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
import { JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useTheme } from '@emotion/react';
type WorkflowEditActionDeleteRecordProps = {
action: WorkflowDeleteRecordAction;
@@ -29,7 +29,7 @@ type WorkflowEditActionDeleteRecordProps = {
};
type DeleteRecordFormData = {
objectName: string;
objectNameSingular: string;
objectRecordId: string;
};
@@ -52,7 +52,7 @@ export const WorkflowEditActionDeleteRecord = ({
}));
const [formData, setFormData] = useState<DeleteRecordFormData>({
objectName: action.settings.input.objectName,
objectNameSingular: action.settings.input.objectName,
objectRecordId: action.settings.input.objectRecordId,
});
const isFormDisabled = actionOptions.readonly;
@@ -72,7 +72,7 @@ export const WorkflowEditActionDeleteRecord = ({
};
const objectNameSingular = activeNonSystemObjectMetadataItems.find(
(item) => item.nameSingular === formData.objectName,
(item) => item.nameSingular === formData.objectNameSingular,
)?.nameSingular;
const saveAction = useDebouncedCallback(
@@ -82,7 +82,7 @@ export const WorkflowEditActionDeleteRecord = ({
}
const {
objectName: updatedObjectName,
objectNameSingular: updatedObjectName,
objectRecordId: updatedObjectRecordId,
} = formData;
@@ -137,12 +137,12 @@ export const WorkflowEditActionDeleteRecord = ({
label="Object"
fullWidth
disabled={isFormDisabled}
value={formData.objectName}
value={formData.objectNameSingular}
emptyOption={{ label: 'Select an option', value: '' }}
options={availableMetadata}
onChange={(objectName) => {
onChange={(objectNameSingular) => {
const newFormData: DeleteRecordFormData = {
objectName,
objectNameSingular,
objectRecordId: '',
};
@@ -35,7 +35,7 @@ type WorkflowEditActionUpdateRecordProps = {
};
type UpdateRecordFormData = {
objectName: string;
objectNameSingular: string;
objectRecordId: string;
fieldsToUpdate: string[];
[field: string]: unknown;
@@ -60,7 +60,7 @@ export const WorkflowEditActionUpdateRecord = ({
}));
const [formData, setFormData] = useState<UpdateRecordFormData>({
objectName: action.settings.input.objectName,
objectNameSingular: action.settings.input.objectName,
objectRecordId: action.settings.input.objectRecordId,
fieldsToUpdate: action.settings.input.fieldsToUpdate ?? [],
...action.settings.input.objectRecord,
@@ -83,7 +83,7 @@ export const WorkflowEditActionUpdateRecord = ({
};
const selectedObjectMetadataItem = activeNonSystemObjectMetadataItems.find(
(item) => item.nameSingular === formData.objectName,
(item) => item.nameSingular === formData.objectNameSingular,
);
const objectNameSingular = selectedObjectMetadataItem?.nameSingular;
@@ -114,7 +114,7 @@ export const WorkflowEditActionUpdateRecord = ({
}
const {
objectName: updatedObjectName,
objectNameSingular: updatedObjectName,
objectRecordId: updatedObjectRecordId,
fieldsToUpdate: updatedFieldsToUpdate,
...updatedOtherFields
@@ -174,12 +174,12 @@ export const WorkflowEditActionUpdateRecord = ({
label="Object"
fullWidth
disabled={isFormDisabled}
value={formData.objectName}
value={formData.objectNameSingular}
emptyOption={{ label: 'Select an option', value: '' }}
options={availableMetadata}
onChange={(updatedObjectName) => {
const newFormData: UpdateRecordFormData = {
objectName: updatedObjectName,
objectNameSingular: updatedObjectName,
objectRecordId: '',
fieldsToUpdate: [],
};
@@ -36,7 +36,7 @@ type WorkflowEditActionFindRecordsProps = {
};
type FindRecordsFormData = {
objectName: string;
objectNameSingular: string;
filter?: FindRecordsActionFilter;
limit?: number;
};
@@ -65,14 +65,14 @@ export const WorkflowEditActionFindRecords = ({
}));
const [formData, setFormData] = useState<FindRecordsFormData>({
objectName: action.settings.input.objectName,
objectNameSingular: action.settings.input.objectName,
limit: action.settings.input.limit,
filter: action.settings.input.filter as FindRecordsActionFilter,
});
const isFormDisabled = actionOptions.readonly;
const selectedObjectMetadataItem = activeNonSystemObjectMetadataItems.find(
(item) => item.nameSingular === formData.objectName,
(item) => item.nameSingular === formData.objectNameSingular,
);
const selectedObjectMetadataItemNameSingular =
@@ -87,7 +87,7 @@ export const WorkflowEditActionFindRecords = ({
}
const {
objectName: updatedObjectName,
objectNameSingular: updatedObjectName,
limit: updatedLimit,
filter: updatedFilter,
} = formData;
@@ -118,7 +118,7 @@ export const WorkflowEditActionFindRecords = ({
action,
defaultTitle: 'Search Records',
});
const instanceId = `workflow-edit-action-record-find-records-${action.id}-${formData.objectName}`;
const instanceId = `workflow-edit-action-record-find-records-${action.id}-${formData.objectNameSingular}`;
return (
<>
@@ -148,9 +148,9 @@ export const WorkflowEditActionFindRecords = ({
value={selectedObjectMetadataItemNameSingular}
emptyOption={{ label: 'Select an option', value: '' }}
options={availableMetadata}
onChange={(objectName) => {
onChange={(objectNameSingular) => {
const newFormData: FindRecordsFormData = {
objectName,
objectNameSingular,
limit: 1,
};
@@ -26,7 +26,7 @@ export const RecordShowPageHeader = ({
<ObjectRecordShowPageBreadcrumb
objectNameSingular={objectNameSingular}
objectRecordId={objectRecordId}
objectLabelPlural={objectMetadataItem.labelPlural}
objectLabel={objectMetadataItem.labelSingular}
labelIdentifierFieldMetadataItem={labelIdentifierFieldMetadataItem}
/>
}
@@ -1,9 +1,10 @@
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getLabelIdentifierFieldValue } from '@/object-metadata/utils/getLabelIdentifierFieldValue';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
export const RecordShowPageTitle = ({
objectNameSingular,
@@ -15,22 +16,21 @@ export const RecordShowPageTitle = ({
const { labelIdentifierFieldMetadataItem } =
useLabelIdentifierFieldMetadataItem({ objectNameSingular });
const record = useRecoilValue(recordStoreFamilyState(objectRecordId));
const labelIdentifierFieldValue = record?.labelIdentifierFieldValue;
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const pageName =
labelIdentifierFieldMetadataItem?.type === FieldMetadataType.FULL_NAME
? [
labelIdentifierFieldValue?.firstName,
labelIdentifierFieldValue?.lastName,
].join(' ')
: isDefined(labelIdentifierFieldValue)
? `${labelIdentifierFieldValue}`
: '';
const record = useRecoilValue(recordStoreFamilyState(objectRecordId));
const pageName = isDefined(record)
? getLabelIdentifierFieldValue(
record,
labelIdentifierFieldMetadataItem,
objectNameSingular,
)
: '';
const pageTitle = pageName.trim()
? `${pageName} - ${capitalize(objectNameSingular)}`
: capitalize(objectNameSingular);
? `${pageName} - ${objectMetadataItem.labelSingular}`
: objectMetadataItem.labelSingular;
return <PageTitle title={pageTitle} />;
};