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:
+6
-4
@@ -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}`;
|
||||
};
|
||||
|
||||
+3
-3
@@ -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}
|
||||
|
||||
+3
-3
@@ -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"
|
||||
/>
|
||||
|
||||
+50
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+55
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+1
-4
@@ -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
|
||||
|
||||
+2
-2
@@ -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 ? (
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
+3
-4
@@ -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>
|
||||
|
||||
+4
-4
@@ -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,
|
||||
|
||||
+4
-4
@@ -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 (
|
||||
|
||||
+3
-3
@@ -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.';
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-2
@@ -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}
|
||||
|
||||
+1
-2
@@ -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>
|
||||
|
||||
+4
-4
@@ -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'}>
|
||||
|
||||
+5
-5
@@ -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);
|
||||
|
||||
+8
-8
@@ -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>
|
||||
|
||||
+2
-2
@@ -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} />;
|
||||
};
|
||||
|
||||
+9
-9
@@ -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: '',
|
||||
};
|
||||
|
||||
|
||||
+6
-6
@@ -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: [],
|
||||
};
|
||||
|
||||
+7
-7
@@ -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} />;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user