fix: format numbers according to user preferences in settings views (#22501)
## Context Record and field counts in several settings views were rendered as raw numbers (e.g. `158355`), ignoring the workspace member's number format preference. This PR routes them through the existing `useNumberFormat` hook (which wraps `formatNumber` with the user's `numberFormat` preference), so they render as `158,355` / `158 355` / `158.355` / `158'355` depending on the preference. ## Changes Starting point (from the screenshot): the Settings → Data model objects table. - **`SettingsObjectItemTableRow`** — Fields and Records columns in Settings → Data model - **`SettingsAvailableStandardObjectItemTableRow`** — Fields column in Settings → Data model → New object - **`SettingsDataModelOverviewObject`** — record count next to the object name in the data model graph overview (guards against `undefined` while the count query loads) - **`SettingsLogs`** — "X of Y" record counts above the event logs table - **`SettingsAdminGeneral`** — Users column in the admin panel top workspaces table - **`SettingsAdminWorkspaceContent`** — Members value in the admin panel workspace info card - **`NoteList`** — total notes count in the record page Notes tab ## Test coverage - `npx nx lint:diff-with-main twenty-front` (oxlint + oxfmt) passes - `npx nx typecheck twenty-front` passes - No behavioral change beyond formatting; `formatNumber` defaults to 0 decimals so integer counts stay integers https://claude.ai/code/session_019pXXZSaza8TXPYK8NefQiS --- _Generated by [Claude Code](https://claude.ai/code/session_019pXXZSaza8TXPYK8NefQiS)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22501?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -2,6 +2,7 @@ import { styled } from '@linaria/react';
|
||||
import { type ReactElement } from 'react';
|
||||
|
||||
import { type Note } from '@/activities/types/Note';
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { NoteTile } from './NoteTile';
|
||||
@@ -54,26 +55,30 @@ export const NoteList = ({
|
||||
notes,
|
||||
totalCount,
|
||||
button,
|
||||
}: NoteListProps) => (
|
||||
<>
|
||||
{notes.length > 0 && (
|
||||
<StyledContainer>
|
||||
<StyledTitleBar>
|
||||
<StyledTitle>
|
||||
{title} <StyledCount>{totalCount}</StyledCount>
|
||||
</StyledTitle>
|
||||
{button}
|
||||
</StyledTitleBar>
|
||||
<StyledNoteContainer>
|
||||
{notes.map((note) => (
|
||||
<NoteTile
|
||||
key={note.id}
|
||||
note={note}
|
||||
isSingleNote={notes.length === 1}
|
||||
/>
|
||||
))}
|
||||
</StyledNoteContainer>
|
||||
</StyledContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}: NoteListProps) => {
|
||||
const { formatNumber } = useNumberFormat();
|
||||
|
||||
return (
|
||||
<>
|
||||
{notes.length > 0 && (
|
||||
<StyledContainer>
|
||||
<StyledTitleBar>
|
||||
<StyledTitle>
|
||||
{title} <StyledCount>{formatNumber(totalCount)}</StyledCount>
|
||||
</StyledTitle>
|
||||
{button}
|
||||
</StyledTitleBar>
|
||||
<StyledNoteContainer>
|
||||
{notes.map((note) => (
|
||||
<NoteTile
|
||||
key={note.id}
|
||||
note={note}
|
||||
isSingleNote={notes.length === 1}
|
||||
/>
|
||||
))}
|
||||
</StyledNoteContainer>
|
||||
</StyledContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+5
-1
@@ -1,4 +1,5 @@
|
||||
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
|
||||
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
|
||||
import { SettingsAdminServerAdmins } from '@/settings/admin-panel/components/SettingsAdminServerAdmins';
|
||||
@@ -45,6 +46,7 @@ const TOP_WORKSPACES_GRID_TEMPLATE_COLUMNS = '2fr 1fr 36px';
|
||||
|
||||
export const SettingsAdminGeneral = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { formatNumber } = useNumberFormat();
|
||||
const apolloAdminClient = useApolloAdminClient();
|
||||
const [userSearchTerm, setUserSearchTerm] = useState('');
|
||||
const [debouncedUserSearchTerm] = useDebounce(userSearchTerm, 300);
|
||||
@@ -250,7 +252,9 @@ export const SettingsAdminGeneral = () => {
|
||||
text={workspace.name || '\u2014'}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="right">{workspace.totalUsers}</TableCell>
|
||||
<TableCell align="right">
|
||||
{formatNumber(workspace.totalUsers)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
|
||||
+5
-1
@@ -1,3 +1,4 @@
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { type WorkspaceInfo } from '@/settings/admin-panel/types/WorkspaceInfo';
|
||||
import { getUpgradeHealthStatusBadge } from '@/settings/admin-panel/utils/getUpgradeHealthStatusBadge';
|
||||
import { getWorkspaceSchemaName } from '@/settings/admin-panel/utils/getWorkspaceSchemaName';
|
||||
@@ -50,6 +51,7 @@ export const SettingsAdminWorkspaceContent = ({
|
||||
workspaceUpgradeStatus,
|
||||
}: SettingsAdminWorkspaceContentProps) => {
|
||||
const { t } = useLingui();
|
||||
const { formatNumber } = useNumberFormat();
|
||||
const { dateFormat, timeFormat, timeZone } = useContext(UserContext);
|
||||
const { localeCatalog } = useAtomStateValue(dateLocaleState);
|
||||
|
||||
@@ -116,7 +118,9 @@ export const SettingsAdminWorkspaceContent = ({
|
||||
{
|
||||
Icon: IconUser,
|
||||
label: t`Members`,
|
||||
value: activeWorkspace?.totalUsers,
|
||||
value: isDefined(activeWorkspace?.totalUsers)
|
||||
? formatNumber(activeWorkspace.totalUsers)
|
||||
: activeWorkspace?.totalUsers,
|
||||
},
|
||||
{
|
||||
Icon: IconStatusChange,
|
||||
|
||||
+7
-2
@@ -3,6 +3,7 @@ import { type Node, type NodeProps } from '@xyflow/react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { ObjectMetadataIcon } from '@/object-metadata/components/ObjectMetadataIcon';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
@@ -14,7 +15,7 @@ import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
|
||||
import { ObjectFieldRowWithoutRelation } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewFieldWithoutRelation';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronDown, IconChevronUp } from 'twenty-ui/icon';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
@@ -108,6 +109,7 @@ export const SettingsDataModelOverviewObject = ({
|
||||
data: objectMetadataItem,
|
||||
}: SettingsDataModelOverviewObjectProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { formatNumber } = useNumberFormat();
|
||||
const [otherFieldsExpanded, setOtherFieldsExpanded] = useState(false);
|
||||
|
||||
const { totalCount } = useFindManyRecords({
|
||||
@@ -139,7 +141,10 @@ export const SettingsDataModelOverviewObject = ({
|
||||
{objectMetadataItem.labelPlural}
|
||||
</Link>
|
||||
</StyledObjectLinkContainer>
|
||||
<StyledObjectInstanceCount> · {totalCount}</StyledObjectInstanceCount>
|
||||
<StyledObjectInstanceCount>
|
||||
{' '}
|
||||
· {isDefined(totalCount) ? formatNumber(totalCount) : totalCount}
|
||||
</StyledObjectInstanceCount>
|
||||
</StyledObjectName>
|
||||
<SettingsItemTypeTag item={objectMetadataItem} />
|
||||
</StyledHeader>
|
||||
|
||||
+5
-1
@@ -1,3 +1,4 @@
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { ObjectMetadataIcon } from '@/object-metadata/components/ObjectMetadataIcon';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
@@ -28,6 +29,7 @@ export const SettingsAvailableStandardObjectItemTableRow = ({
|
||||
onClick,
|
||||
}: SettingsAvailableStandardObjectItemTableRowProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { formatNumber } = useNumberFormat();
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
@@ -55,7 +57,9 @@ export const SettingsAvailableStandardObjectItemTableRow = ({
|
||||
<TableCell>
|
||||
<StyledDescription>{objectItem.description}</StyledDescription>
|
||||
</TableCell>
|
||||
<TableCell align="right">{objectItem.fields.length}</TableCell>
|
||||
<TableCell align="right">
|
||||
{formatNumber(objectItem.fields.length)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+6
-4
@@ -2,6 +2,7 @@ import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { type ReactNode, useContext } from 'react';
|
||||
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { ObjectMetadataIcon } from '@/object-metadata/components/ObjectMetadataIcon';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
@@ -46,6 +47,7 @@ export const SettingsObjectMetadataItemTableRow = ({
|
||||
}: SettingsObjectMetadataItemTableRowProps) => {
|
||||
const { t } = useLingui();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { formatNumber } = useNumberFormat();
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
@@ -76,13 +78,13 @@ export const SettingsObjectMetadataItemTableRow = ({
|
||||
<SettingsItemTypeTag item={objectMetadataItem} />
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{
|
||||
{formatNumber(
|
||||
objectMetadataItem.fields.filter(
|
||||
(field) => !isHiddenSystemField(field),
|
||||
).length
|
||||
}
|
||||
).length,
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right">{totalObjectCount}</TableCell>
|
||||
<TableCell align="right">{formatNumber(totalObjectCount)}</TableCell>
|
||||
<StyledActionTableCell>{action}</StyledActionTableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo, useState } from 'react';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { isClickHouseConfiguredState } from '@/client-config/states/isClickHouseConfiguredState';
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { SettingsEmptyPlaceholder } from '@/settings/components/SettingsEmptyPlaceholder';
|
||||
import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton';
|
||||
import { EventLogFilters } from '@/settings/event-logs/components/EventLogFilters';
|
||||
@@ -89,6 +90,7 @@ const RECORDS_PER_PAGE = 100;
|
||||
|
||||
export const SettingsLogs = () => {
|
||||
const { t } = useLingui();
|
||||
const { formatNumber } = useNumberFormat();
|
||||
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
const isClickHouseConfigured = useAtomStateValue(isClickHouseConfiguredState);
|
||||
@@ -214,7 +216,7 @@ export const SettingsLogs = () => {
|
||||
|
||||
return (
|
||||
<StyledResults>
|
||||
<StyledRecordCount>{t`${displayedRecords.length} of ${totalCount + liveRecords.length}`}</StyledRecordCount>
|
||||
<StyledRecordCount>{t`${formatNumber(displayedRecords.length)} of ${formatNumber(totalCount + liveRecords.length)}`}</StyledRecordCount>
|
||||
<StyledTableWrapper>
|
||||
<EventLogResultsTable
|
||||
records={displayedRecords}
|
||||
|
||||
Reference in New Issue
Block a user