diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index b89e1669c5..d6af15f468 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4071,6 +4071,7 @@ export type WorkspaceMember = { id: Scalars['UUID']; locale?: Maybe; name: FullName; + numberFormat?: Maybe; roles?: Maybe>; timeFormat?: Maybe; timeZone?: Maybe; @@ -4086,6 +4087,15 @@ export enum WorkspaceMemberDateFormatEnum { YEAR_FIRST = 'YEAR_FIRST' } +/** Number format for displaying numbers */ +export enum WorkspaceMemberNumberFormatEnum { + APOSTROPHE_AND_DOT = 'APOSTROPHE_AND_DOT', + COMMAS_AND_DOT = 'COMMAS_AND_DOT', + DOTS_AND_COMMA = 'DOTS_AND_COMMA', + SPACES_AND_COMMA = 'SPACES_AND_COMMA', + SYSTEM = 'SYSTEM' +} + /** Time time as Military, Standard or system as default */ export enum WorkspaceMemberTimeFormatEnum { HOUR_12 = 'HOUR_12', @@ -4877,7 +4887,7 @@ export type UpdateWorkspaceMemberRoleMutationVariables = Exact<{ }>; -export type UpdateWorkspaceMemberRoleMutation = { __typename?: 'Mutation', updateWorkspaceMemberRole: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, roles?: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean }> | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } }; +export type UpdateWorkspaceMemberRoleMutation = { __typename?: 'Mutation', updateWorkspaceMemberRole: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, roles?: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean }> | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } }; export type UpsertFieldPermissionsMutationVariables = Exact<{ upsertFieldPermissionsInput: UpsertFieldPermissionsInput; @@ -5034,7 +5044,7 @@ export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutationVaria export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutation = { __typename?: 'Mutation', verifyTwoFactorAuthenticationMethodForAuthenticatedUser: { __typename?: 'VerifyTwoFactorAuthenticationMethodOutput', success: boolean } }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId?: string | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: string } | null, views?: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, fieldMetadataId: string, isVisible: boolean, fieldValue: string, position: number, viewId: string }> }> | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId?: string | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: string } | null, views?: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, fieldMetadataId: string, isVisible: boolean, fieldValue: string, position: number, viewId: string }> }> | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; export type WorkspaceUrlsFragmentFragment = { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }; @@ -5053,7 +5063,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId?: string | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: string } | null, views?: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, fieldMetadataId: string, isVisible: boolean, fieldValue: string, position: number, viewId: string }> }> | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId?: string | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: string } | null, views?: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, fieldMetadataId: string, isVisible: boolean, fieldValue: string, position: number, viewId: string }> }> | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; export type ViewFieldFragmentFragment = { __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }; @@ -5452,7 +5462,7 @@ export type DeletedWorkspaceMemberQueryFragmentFragment = { __typename?: 'Delete export type PartialWorkspaceMemberQueryFragmentFragment = { __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }; -export type WorkspaceMemberQueryFragmentFragment = { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }; +export type WorkspaceMemberQueryFragmentFragment = { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }; export type ActivateWorkspaceMutationVariables = Exact<{ input: ActivateWorkspaceInput; @@ -5636,6 +5646,7 @@ export const WorkspaceMemberQueryFragmentFragmentDoc = gql` dateFormat timeFormat calendarStartDay + numberFormat } `; export const PartialWorkspaceMemberQueryFragmentFragmentDoc = gql` diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 9afdb7570f..aa355577dc 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -3899,6 +3899,7 @@ export type WorkspaceMember = { id: Scalars['UUID']; locale?: Maybe; name: FullName; + numberFormat?: Maybe; roles?: Maybe>; timeFormat?: Maybe; timeZone?: Maybe; @@ -3914,6 +3915,15 @@ export enum WorkspaceMemberDateFormatEnum { YEAR_FIRST = 'YEAR_FIRST' } +/** Number format for displaying numbers */ +export enum WorkspaceMemberNumberFormatEnum { + APOSTROPHE_AND_DOT = 'APOSTROPHE_AND_DOT', + COMMAS_AND_DOT = 'COMMAS_AND_DOT', + DOTS_AND_COMMA = 'DOTS_AND_COMMA', + SPACES_AND_COMMA = 'SPACES_AND_COMMA', + SYSTEM = 'SYSTEM' +} + /** Time time as Military, Standard or system as default */ export enum WorkspaceMemberTimeFormatEnum { HOUR_12 = 'HOUR_12', diff --git a/packages/twenty-front/src/modules/billing/components/SettingsBillingCreditsSection.tsx b/packages/twenty-front/src/modules/billing/components/SettingsBillingCreditsSection.tsx index 38dde3232a..9c6a32e27a 100644 --- a/packages/twenty-front/src/modules/billing/components/SettingsBillingCreditsSection.tsx +++ b/packages/twenty-front/src/modules/billing/components/SettingsBillingCreditsSection.tsx @@ -9,8 +9,8 @@ import { ProgressBar } from 'twenty-ui/feedback'; import { Section } from 'twenty-ui/layout'; import { BACKGROUND_LIGHT, COLOR } from 'twenty-ui/theme'; import { SubscriptionStatus } from '~/generated/graphql'; -import { formatAmount } from '~/utils/format/formatAmount'; -import { formatNumber } from '~/utils/format/number'; +import { formatToShortNumber } from '~/utils/format/formatToShortNumber'; +import { formatNumber } from '~/utils/format/formatNumber'; // import { useListAvailableMeteredBillingPricesQuery } from '~/generated-metadata/graphql'; // import { MeteredPriceSelector } from '@/billing/components/internal/MeteredPriceSelector'; import { type CurrentWorkspace } from '@/auth/states/currentWorkspaceState'; @@ -54,7 +54,7 @@ export const SettingsBillingCreditsSection = ({ a.amount - b.amount; diff --git a/packages/twenty-front/src/modules/localization/constants/NumberFormat.ts b/packages/twenty-front/src/modules/localization/constants/NumberFormat.ts new file mode 100644 index 0000000000..a055a312b9 --- /dev/null +++ b/packages/twenty-front/src/modules/localization/constants/NumberFormat.ts @@ -0,0 +1,7 @@ +export enum NumberFormat { + SYSTEM = 'SYSTEM', + COMMAS_AND_DOT = 'COMMAS_AND_DOT', + SPACES_AND_COMMA = 'SPACES_AND_COMMA', + DOTS_AND_COMMA = 'DOTS_AND_COMMA', + APOSTROPHE_AND_DOT = 'APOSTROPHE_AND_DOT', +} diff --git a/packages/twenty-front/src/modules/localization/utils/__tests__/detectNumberFormat.test.ts b/packages/twenty-front/src/modules/localization/utils/__tests__/detectNumberFormat.test.ts new file mode 100644 index 0000000000..58ef89193e --- /dev/null +++ b/packages/twenty-front/src/modules/localization/utils/__tests__/detectNumberFormat.test.ts @@ -0,0 +1,73 @@ +import { detectNumberFormat } from '../detectNumberFormat'; + +// Mock navigator.language +Object.defineProperty(navigator, 'language', { + writable: true, + value: 'en-US', +}); + +describe('detectNumberFormat', () => { + beforeEach(() => { + // Reset to default + Object.defineProperty(navigator, 'language', { + writable: true, + value: 'en-US', + }); + }); + + it('should detect COMMAS_AND_DOT format for en-US locale', () => { + Object.defineProperty(navigator, 'language', { + writable: true, + value: 'en-US', + }); + expect(detectNumberFormat()).toBe('COMMAS_AND_DOT'); + }); + + it('should detect SPACES_AND_COMMA format for fr-FR locale', () => { + Object.defineProperty(navigator, 'language', { + writable: true, + value: 'fr-FR', + }); + expect(detectNumberFormat()).toBe('SPACES_AND_COMMA'); + }); + + it('should detect DOTS_AND_COMMA format for de-DE locale', () => { + Object.defineProperty(navigator, 'language', { + writable: true, + value: 'de-DE', + }); + expect(detectNumberFormat()).toBe('DOTS_AND_COMMA'); + }); + + it('should detect APOSTROPHE_AND_DOT format for de-CH locale', () => { + Object.defineProperty(navigator, 'language', { + writable: true, + value: 'de-CH', + }); + expect(detectNumberFormat()).toBe('APOSTROPHE_AND_DOT'); + }); + + it('should fallback to COMMAS_AND_DOT for unknown patterns', () => { + Object.defineProperty(navigator, 'language', { + writable: true, + value: 'ja-JP', // Uses different separators not in our patterns + }); + expect(detectNumberFormat()).toBe('COMMAS_AND_DOT'); + }); + + it('should handle invalid locale gracefully', () => { + Object.defineProperty(navigator, 'language', { + writable: true, + value: 'invalid-locale', + }); + expect(detectNumberFormat()).toBe('COMMAS_AND_DOT'); + }); + + it('should handle missing navigator.language', () => { + Object.defineProperty(navigator, 'language', { + writable: true, + value: undefined, + }); + expect(detectNumberFormat()).toBe('COMMAS_AND_DOT'); + }); +}); diff --git a/packages/twenty-front/src/modules/localization/utils/detectNumberFormat.ts b/packages/twenty-front/src/modules/localization/utils/detectNumberFormat.ts new file mode 100644 index 0000000000..4839e103b1 --- /dev/null +++ b/packages/twenty-front/src/modules/localization/utils/detectNumberFormat.ts @@ -0,0 +1,41 @@ +import { NumberFormat } from '@/localization/constants/NumberFormat'; + +const SPACE_CHARS = new Set([' ', '\u00A0', '\u202F']); // space, non-breaking space, narrow no-break space +const APOSTROPHE_CHARS = new Set(["'", '\u2019']); // apostrophe, right single quotation mark + +const FORMAT_PATTERNS = new Map([ + [',|.', NumberFormat.COMMAS_AND_DOT], + ['.|,', NumberFormat.DOTS_AND_COMMA], + ['space|,', NumberFormat.SPACES_AND_COMMA], + ['apostrophe|.', NumberFormat.APOSTROPHE_AND_DOT], +]); + +export const detectNumberFormat = (): keyof typeof NumberFormat => { + const testNumber = 1234567.89; + let language = navigator?.language || 'en-US'; + + let formatter: Intl.NumberFormat; + try { + formatter = new Intl.NumberFormat(language); + } catch { + formatter = new Intl.NumberFormat('en-US'); + } + + const parts = formatter.formatToParts(testNumber); + const thousandSeparator = + parts.find((part) => part.type === 'group')?.value || ''; + const decimalSeparator = + parts.find((part) => part.type === 'decimal')?.value || ''; + + let thousandCategory: string; + if (SPACE_CHARS.has(thousandSeparator)) { + thousandCategory = 'space'; + } else if (APOSTROPHE_CHARS.has(thousandSeparator)) { + thousandCategory = 'apostrophe'; + } else { + thousandCategory = thousandSeparator; + } + + const pattern = `${thousandCategory}|${decimalSeparator}`; + return FORMAT_PATTERNS.get(pattern) || NumberFormat.COMMAS_AND_DOT; +}; diff --git a/packages/twenty-front/src/modules/localization/utils/getNumberFormatFromWorkspaceNumberFormat.ts b/packages/twenty-front/src/modules/localization/utils/getNumberFormatFromWorkspaceNumberFormat.ts new file mode 100644 index 0000000000..d6f416ae2a --- /dev/null +++ b/packages/twenty-front/src/modules/localization/utils/getNumberFormatFromWorkspaceNumberFormat.ts @@ -0,0 +1,22 @@ +import { NumberFormat } from '@/localization/constants/NumberFormat'; +import { detectNumberFormat } from '@/localization/utils/detectNumberFormat'; +import { WorkspaceMemberNumberFormatEnum } from '~/generated/graphql'; + +export const getNumberFormatFromWorkspaceNumberFormat = ( + numberFormat: WorkspaceMemberNumberFormatEnum, +): NumberFormat => { + switch (numberFormat) { + case WorkspaceMemberNumberFormatEnum.SYSTEM: + return NumberFormat[detectNumberFormat()]; + case WorkspaceMemberNumberFormatEnum.COMMAS_AND_DOT: + return NumberFormat.COMMAS_AND_DOT; + case WorkspaceMemberNumberFormatEnum.SPACES_AND_COMMA: + return NumberFormat.SPACES_AND_COMMA; + case WorkspaceMemberNumberFormatEnum.DOTS_AND_COMMA: + return NumberFormat.DOTS_AND_COMMA; + case WorkspaceMemberNumberFormatEnum.APOSTROPHE_AND_DOT: + return NumberFormat.APOSTROPHE_AND_DOT; + default: + return NumberFormat.COMMAS_AND_DOT; + } +}; diff --git a/packages/twenty-front/src/modules/localization/utils/getWorkspaceNumberFormatFromNumberFormat.ts b/packages/twenty-front/src/modules/localization/utils/getWorkspaceNumberFormatFromNumberFormat.ts new file mode 100644 index 0000000000..1f04817dcd --- /dev/null +++ b/packages/twenty-front/src/modules/localization/utils/getWorkspaceNumberFormatFromNumberFormat.ts @@ -0,0 +1,21 @@ +import { NumberFormat } from '@/localization/constants/NumberFormat'; +import { WorkspaceMemberNumberFormatEnum } from '~/generated/graphql'; + +export const getWorkspaceNumberFormatFromNumberFormat = ( + numberFormat: NumberFormat, +): WorkspaceMemberNumberFormatEnum => { + switch (numberFormat) { + case NumberFormat.SYSTEM: + return WorkspaceMemberNumberFormatEnum.SYSTEM; + case NumberFormat.COMMAS_AND_DOT: + return WorkspaceMemberNumberFormatEnum.COMMAS_AND_DOT; + case NumberFormat.SPACES_AND_COMMA: + return WorkspaceMemberNumberFormatEnum.SPACES_AND_COMMA; + case NumberFormat.DOTS_AND_COMMA: + return WorkspaceMemberNumberFormatEnum.DOTS_AND_COMMA; + case NumberFormat.APOSTROPHE_AND_DOT: + return WorkspaceMemberNumberFormatEnum.APOSTROPHE_AND_DOT; + default: + return WorkspaceMemberNumberFormatEnum.COMMAS_AND_DOT; + } +}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts index c3e01cd692..e786098f9e 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts @@ -9,7 +9,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import { t } from '@lingui/core/macro'; -import { formatNumber } from '~/utils/format/number'; +import { formatNumber } from '~/utils/format/formatNumber'; export const useBatchCreateManyRecords = < CreatedObjectRecord extends ObjectRecord = ObjectRecord, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts index c7e1800d91..eb9fae0f7a 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts @@ -13,8 +13,8 @@ import isEmpty from 'lodash.isempty'; import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { formatAmount } from '~/utils/format/formatAmount'; -import { formatNumber } from '~/utils/format/number'; +import { formatToShortNumber } from '~/utils/format/formatToShortNumber'; +import { formatNumber } from '~/utils/format/formatNumber'; import { formatDateString } from '~/utils/string/formatDateString'; import { formatDateTimeString } from '~/utils/string/formatDateTimeString'; @@ -83,7 +83,7 @@ export const computeAggregateValueAndLabel = ({ switch (field.type) { case FieldMetadataType.CURRENCY: { value = Number(aggregateValue); - value = formatAmount(value / 1_000_000); + value = formatToShortNumber(value / 1_000_000); break; } diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/NumberFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/NumberFieldDisplay.tsx index 723e55c7a9..5ca9bf8632 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/NumberFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/NumberFieldDisplay.tsx @@ -1,22 +1,38 @@ +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { getNumberFormatFromWorkspaceNumberFormat } from '@/localization/utils/getNumberFormatFromWorkspaceNumberFormat'; import { useNumberFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay'; import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay'; +import { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; -import { formatAmount } from '~/utils/format/formatAmount'; -import { formatNumber } from '~/utils/format/number'; +import { formatToShortNumber } from '~/utils/format/formatToShortNumber'; +import { formatNumber } from '~/utils/format/formatNumber'; export const NumberFieldDisplay = () => { const { fieldValue, fieldDefinition } = useNumberFieldDisplay(); - const decimals = fieldDefinition.metadata.settings?.decimals; + const [currentWorkspaceMember] = useRecoilState(currentWorkspaceMemberState); const type = fieldDefinition.metadata.settings?.type; + const decimals = fieldDefinition.metadata.settings?.decimals; - if (!isDefined(fieldValue)) - return ; - const value = - type === 'percentage' - ? `${formatNumber(Number(fieldValue) * 100, decimals)}%` - : type === 'shortNumber' - ? formatAmount(Number(fieldValue)) - : formatNumber(Number(fieldValue), decimals); + if (!isDefined(fieldValue)) { + return ; + } - return ; + const numericValue = Number(fieldValue); + let formattedValue: string; + + if (type === 'percentage') { + formattedValue = `${formatNumber(numericValue * 100, getNumberFormatFromWorkspaceNumberFormat(currentWorkspaceMember?.numberFormat!), decimals)}%`; + } else if (type === 'shortNumber') { + formattedValue = formatToShortNumber(numericValue); + } else { + formattedValue = formatNumber( + numericValue, + getNumberFormatFromWorkspaceNumberFormat( + currentWorkspaceMember?.numberFormat!, + ), + decimals, + ); + } + + return ; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/graphFormatters.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/graphFormatters.ts index 91fb55b81e..af5f8f3539 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/graphFormatters.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/graphFormatters.ts @@ -1,6 +1,6 @@ import { isDefined } from 'twenty-shared/utils'; -import { formatAmount } from '~/utils/format/formatAmount'; -import { formatNumber } from '~/utils/format/number'; +import { formatNumber } from '~/utils/format/formatNumber'; +import { formatToShortNumber } from '~/utils/format/formatToShortNumber'; export type GraphValueFormatOptions = { displayType?: 'percentage' | 'number' | 'shortNumber' | 'currency' | 'custom'; @@ -31,7 +31,7 @@ export const formatGraphValue = ( return `${formatNumber(value * 100, decimals)}%`; case 'shortNumber': - return `${prefix}${formatAmount(value)}${suffix}`; + return `${prefix}${formatToShortNumber(value)}${suffix}`; case 'currency': { const currencyPrefix = prefix || '$'; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx index 30e5b23f91..aa16fb7b1d 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx @@ -10,7 +10,7 @@ import { NUMBER_DATA_MODEL_SELECT_OPTIONS } from '@/settings/data-model/fields/f import { Select } from '@/ui/input/components/Select'; import { useLingui } from '@lingui/react/macro'; import { IconDecimal, IconEye } from 'twenty-ui/display'; -import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number'; +import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/formatNumber'; export const settingsDataModelFieldNumberFormSchema = z.object({ settings: numberFieldDefaultValueSchema, diff --git a/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsDateFormatSelect.tsx b/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsDateFormatSelect.tsx index 8b8dac5e2e..e2514d4060 100644 --- a/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsDateFormatSelect.tsx +++ b/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsDateFormatSelect.tsx @@ -4,7 +4,7 @@ import { DateFormat } from '@/localization/constants/DateFormat'; import { detectDateFormat } from '@/localization/utils/detectDateFormat'; import { detectTimeZone } from '@/localization/utils/detectTimeZone'; import { Select } from '@/ui/input/components/Select'; -import { t } from '@lingui/core/macro'; +import { useLingui } from '@lingui/react/macro'; type DateTimeSettingsDateFormatSelectProps = { value: DateFormat; @@ -17,6 +17,8 @@ export const DateTimeSettingsDateFormatSelect = ({ timeZone, value, }: DateTimeSettingsDateFormatSelectProps) => { + const { t } = useLingui(); + const systemTimeZone = detectTimeZone(); const usedTimeZone = timeZone === 'system' ? systemTimeZone : timeZone; diff --git a/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsTimeFormatSelect.tsx b/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsTimeFormatSelect.tsx index aa9d329bbc..bf9967b9d5 100644 --- a/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsTimeFormatSelect.tsx +++ b/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsTimeFormatSelect.tsx @@ -56,11 +56,11 @@ export const DateTimeSettingsTimeFormatSelect = ({ value: TimeFormat.SYSTEM, }, { - label: t`24h (${hour24Label})`, + label: t`24h - ${hour24Label}`, value: TimeFormat.HOUR_24, }, { - label: t`12h (${hour12Label})`, + label: t`12h - ${hour12Label}`, value: TimeFormat.HOUR_12, }, ]} diff --git a/packages/twenty-front/src/modules/settings/experience/components/NumberFormatSelect.tsx b/packages/twenty-front/src/modules/settings/experience/components/NumberFormatSelect.tsx new file mode 100644 index 0000000000..03d8df281b --- /dev/null +++ b/packages/twenty-front/src/modules/settings/experience/components/NumberFormatSelect.tsx @@ -0,0 +1,75 @@ +import { NumberFormat } from '@/localization/constants/NumberFormat'; +import { detectNumberFormat } from '@/localization/utils/detectNumberFormat'; +import { Select } from '@/ui/input/components/Select'; +import { useLingui } from '@lingui/react/macro'; +import { formatNumber } from '~/utils/format/formatNumber'; + +type NumberFormatSelectProps = { + value: NumberFormat; + onChange: (nextValue: NumberFormat) => void; +}; + +export const NumberFormatSelect = ({ + onChange, + value, +}: NumberFormatSelectProps) => { + const { t } = useLingui(); + + const systemNumberFormat = NumberFormat[detectNumberFormat()]; + + const systemNumberFormatLabel = formatNumber(1234.56, systemNumberFormat, 2); + const commasAndDotExample = formatNumber( + 1234.56, + NumberFormat.COMMAS_AND_DOT, + 2, + ); + const spacesAndCommaExample = formatNumber( + 1234.56, + NumberFormat.SPACES_AND_COMMA, + 2, + ); + const dotsAndCommaExample = formatNumber( + 1234.56, + NumberFormat.DOTS_AND_COMMA, + 2, + ); + const apostropheAndDotExample = formatNumber( + 1234.56, + NumberFormat.APOSTROPHE_AND_DOT, + 2, + ); + + return ( +