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:
Félix Malfait
2026-07-03 08:28:51 +02:00
committed by GitHub
parent 024a9b4d94
commit 86e4685781
7 changed files with 59 additions and 33 deletions
@@ -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>
)}
</>
);
};
@@ -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}
@@ -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,
@@ -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>
@@ -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>
);
};
@@ -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}