feat: allow users to start the calendar week on Monday (#13295)
resolve twentyhq/core-team-issues#1255 - Introduced a new field isWeekStartMonday in the workspaceMember table. - Added a function updateCalendarStartDay to update the isWeekStartMonday value when the user toggles the corresponding field in the settings. The logic works for me, but I couldn’t find a way to create the column locally in the database for the workspaceMember tables. Please let me know if this approach looks good and how to properly create dynamic columns that are not directly handled via migration. https://github.com/user-attachments/assets/4eebada5-6a96-4a88-8e96-98f1501859e3 --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@@ -3028,6 +3028,7 @@ export type WorkspaceInviteHashValid = {
|
||||
export type WorkspaceMember = {
|
||||
__typename?: 'WorkspaceMember';
|
||||
avatarUrl?: Maybe<Scalars['String']>;
|
||||
calendarStartDay?: Maybe<Scalars['Int']>;
|
||||
colorScheme: Scalars['String'];
|
||||
dateFormat?: Maybe<WorkspaceMemberDateFormatEnum>;
|
||||
id: Scalars['UUID'];
|
||||
@@ -3729,7 +3730,7 @@ export type UpdateWorkspaceMemberRoleMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateWorkspaceMemberRoleMutation = { __typename?: 'Mutation', updateWorkspaceMemberRole: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | 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 }> | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } };
|
||||
export type UpdateWorkspaceMemberRoleMutation = { __typename?: 'Mutation', updateWorkspaceMemberRole: { __typename?: 'WorkspaceMember', id: any, 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 }> | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } };
|
||||
|
||||
export type UpsertObjectPermissionsMutationVariables = Exact<{
|
||||
upsertObjectPermissionsInput: UpsertObjectPermissionsInput;
|
||||
@@ -3748,7 +3749,7 @@ export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPer
|
||||
export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetRolesQuery = { __typename?: 'Query', getRoles: 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, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null }> };
|
||||
export type GetRolesQuery = { __typename?: 'Query', getRoles: 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, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, 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 } }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null }> };
|
||||
|
||||
export type CreateApprovedAccessDomainMutationVariables = Exact<{
|
||||
input: CreateApprovedAccessDomainInput;
|
||||
@@ -3879,7 +3880,7 @@ export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutationVaria
|
||||
|
||||
export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutation = { __typename?: 'Mutation', verifyTwoFactorAuthenticationMethodForAuthenticatedUser: { __typename?: 'VerifyTwoFactorAuthenticationMethodOutput', success: boolean } };
|
||||
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array<PermissionFlagType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: 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: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, 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: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | 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: any, 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 } | null, defaultAgent?: { __typename?: 'Agent', id: any } | 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: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, 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: any, 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, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array<PermissionFlagType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: 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: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, 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: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | 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: any, 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 } | null, defaultAgent?: { __typename?: 'Agent', id: any } | 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 };
|
||||
|
||||
@@ -3898,7 +3899,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array<PermissionFlagType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: 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: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, 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: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | 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: any, 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 } | null, defaultAgent?: { __typename?: 'Agent', id: any } | 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: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, 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: any, 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, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array<PermissionFlagType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: 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: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, 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: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | 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: any, 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 } | null, defaultAgent?: { __typename?: 'Agent', id: any } | 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 ActivateWorkflowVersionMutationVariables = Exact<{
|
||||
workflowVersionId: Scalars['String'];
|
||||
@@ -3998,7 +3999,7 @@ export type GetWorkspaceInvitationsQuery = { __typename?: 'Query', findWorkspace
|
||||
|
||||
export type DeletedWorkspaceMemberQueryFragmentFragment = { __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } };
|
||||
|
||||
export type WorkspaceMemberQueryFragmentFragment = { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } };
|
||||
export type WorkspaceMemberQueryFragmentFragment = { __typename?: 'WorkspaceMember', id: any, 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 ActivateWorkspaceMutationVariables = Exact<{
|
||||
input: ActivateWorkspaceInput;
|
||||
@@ -4142,6 +4143,7 @@ export const WorkspaceMemberQueryFragmentFragmentDoc = gql`
|
||||
timeZone
|
||||
dateFormat
|
||||
timeFormat
|
||||
calendarStartDay
|
||||
}
|
||||
`;
|
||||
export const DeletedWorkspaceMemberQueryFragmentFragmentDoc = gql`
|
||||
|
||||
@@ -2856,6 +2856,7 @@ export type WorkspaceInviteHashValid = {
|
||||
export type WorkspaceMember = {
|
||||
__typename?: 'WorkspaceMember';
|
||||
avatarUrl?: Maybe<Scalars['String']>;
|
||||
calendarStartDay?: Maybe<Scalars['Int']>;
|
||||
colorScheme: Scalars['String'];
|
||||
dateFormat?: Maybe<WorkspaceMemberDateFormatEnum>;
|
||||
id: Scalars['UUID'];
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum CalendarStartDay {
|
||||
SYSTEM = 7,
|
||||
SUNDAY = 0,
|
||||
MONDAY = 1,
|
||||
SATURDAY = 6,
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { CalendarStartDay } from '@/localization/constants/CalendarStartDay';
|
||||
|
||||
const MONDAY_KEY: keyof typeof CalendarStartDay = 'MONDAY';
|
||||
const SATURDAY_KEY: keyof typeof CalendarStartDay = 'SATURDAY';
|
||||
const SUNDAY_KEY: keyof typeof CalendarStartDay = 'SUNDAY';
|
||||
|
||||
export const detectCalendarStartDay = (): keyof typeof CalendarStartDay => {
|
||||
// Use Intl.Locale to get the first day of the week from the user's locale
|
||||
// This requires a modern browser that supports Intl.Locale
|
||||
try {
|
||||
const locale = new Intl.Locale(navigator.language);
|
||||
|
||||
// Check if the weekInfo property is available (newer browsers)
|
||||
if (
|
||||
'weekInfo' in locale &&
|
||||
locale.weekInfo !== null &&
|
||||
locale.weekInfo !== undefined &&
|
||||
typeof locale.weekInfo === 'object' &&
|
||||
'firstDay' in locale.weekInfo
|
||||
) {
|
||||
const firstDay = locale.weekInfo.firstDay;
|
||||
|
||||
// Map Intl.Locale firstDay values to our enum keys
|
||||
// Intl.Locale uses 1=Monday, 7=Sunday, 6=Saturday
|
||||
switch (firstDay) {
|
||||
case 1:
|
||||
return MONDAY_KEY;
|
||||
case 6:
|
||||
return SATURDAY_KEY;
|
||||
case 7:
|
||||
default:
|
||||
return SUNDAY_KEY;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Fallback if Intl.Locale is not supported or fails
|
||||
}
|
||||
|
||||
// Fallback: Use a heuristic based on common locale patterns
|
||||
const language = navigator.language.toLowerCase();
|
||||
|
||||
// Most European countries, Australia, New Zealand start with Monday
|
||||
if (
|
||||
language.startsWith('de') || // German
|
||||
language.startsWith('fr') || // French
|
||||
language.startsWith('es') || // Spanish
|
||||
language.startsWith('it') || // Italian
|
||||
language.startsWith('pt') || // Portuguese
|
||||
language.startsWith('nl') || // Dutch
|
||||
language.startsWith('sv') || // Swedish
|
||||
language.startsWith('no') || // Norwegian
|
||||
language.startsWith('da') || // Danish
|
||||
language.startsWith('fi') || // Finnish
|
||||
language.startsWith('pl') || // Polish
|
||||
language.startsWith('ru') || // Russian
|
||||
language.startsWith('en-gb') || // British English
|
||||
language.startsWith('en-au') || // Australian English
|
||||
language.startsWith('en-nz') // New Zealand English
|
||||
) {
|
||||
return MONDAY_KEY;
|
||||
}
|
||||
|
||||
// Middle Eastern countries often start with Saturday
|
||||
if (
|
||||
language.startsWith('ar') || // Arabic
|
||||
language.startsWith('he') || // Hebrew
|
||||
language.startsWith('fa') // Persian
|
||||
) {
|
||||
return SATURDAY_KEY;
|
||||
}
|
||||
|
||||
// Default to Sunday (US, Canada, Japan, etc.)
|
||||
return SUNDAY_KEY;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatTimeZoneLabel } from '@/localization/utils/formatTimeZoneLabel';
|
||||
import { AVAILABLE_TIME_ZONE_OPTIONS_BY_LABEL } from '@/settings/accounts/constants/AvailableTimezoneOptionsByLabel';
|
||||
import { AVAILABLE_TIME_ZONE_OPTIONS_BY_LABEL } from '@/settings/experience/constants/AvailableTimezoneOptionsByLabel';
|
||||
|
||||
/**
|
||||
* Finds the matching available IANA time zone select option from a given IANA time zone.
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
|
||||
type SettingsAccountsCalendarDateFormatSelectProps = {
|
||||
value: DateFormat;
|
||||
onChange: (nextValue: DateFormat) => void;
|
||||
timeZone: string;
|
||||
};
|
||||
|
||||
export const SettingsAccountsCalendarDateFormatSelect = ({
|
||||
onChange,
|
||||
timeZone,
|
||||
value,
|
||||
}: SettingsAccountsCalendarDateFormatSelectProps) => (
|
||||
<Select
|
||||
dropdownId="settings-accounts-calendar-date-format"
|
||||
label="Date format"
|
||||
fullWidth
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: formatInTimeZone(Date.now(), timeZone, DateFormat.MONTH_FIRST),
|
||||
value: DateFormat.MONTH_FIRST,
|
||||
},
|
||||
{
|
||||
label: formatInTimeZone(Date.now(), timeZone, DateFormat.DAY_FIRST),
|
||||
value: DateFormat.DAY_FIRST,
|
||||
},
|
||||
]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
+6
-9
@@ -1,9 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { SettingsAccountsCalendarDateFormatSelect } from '@/settings/accounts/components/SettingsAccountsCalendarDateFormatSelect';
|
||||
import { SettingsAccountsCalendarTimeFormatSelect } from '@/settings/accounts/components/SettingsAccountsCalendarTimeFormatSelect';
|
||||
import { SettingsAccountsCalendarTimeZoneSelect } from '@/settings/accounts/components/SettingsAccountsCalendarTimeZoneSelect';
|
||||
import { DateTimeSettingsDateFormatSelect } from '@/settings/experience/components/DateTimeSettingsDateFormatSelect';
|
||||
import { DateTimeSettingsTimeFormatSelect } from '@/settings/experience/components/DateTimeSettingsTimeFormatSelect';
|
||||
import { DateTimeSettingsTimeZoneSelect } from '@/settings/experience/components/DateTimeSettingsTimeZoneSelect';
|
||||
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||
@@ -27,16 +27,13 @@ export const SettingsAccountsCalendarDisplaySettings = () => {
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<SettingsAccountsCalendarTimeZoneSelect
|
||||
value={timeZone}
|
||||
onChange={setTimeZone}
|
||||
/>
|
||||
<SettingsAccountsCalendarDateFormatSelect
|
||||
<DateTimeSettingsTimeZoneSelect value={timeZone} onChange={setTimeZone} />
|
||||
<DateTimeSettingsDateFormatSelect
|
||||
value={dateFormat}
|
||||
onChange={setDateFormat}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
<SettingsAccountsCalendarTimeFormatSelect
|
||||
<DateTimeSettingsTimeFormatSelect
|
||||
value={timeFormat}
|
||||
onChange={setTimeFormat}
|
||||
timeZone={timeZone}
|
||||
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
|
||||
type SettingsAccountsCalendarTimeFormatSelectProps = {
|
||||
value: TimeFormat;
|
||||
onChange: (nextValue: TimeFormat) => void;
|
||||
timeZone: string;
|
||||
};
|
||||
|
||||
export const SettingsAccountsCalendarTimeFormatSelect = ({
|
||||
onChange,
|
||||
timeZone,
|
||||
value,
|
||||
}: SettingsAccountsCalendarTimeFormatSelectProps) => (
|
||||
<Select
|
||||
dropdownId="settings-accounts-calendar-time-format"
|
||||
label="Time format"
|
||||
fullWidth
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: `24h (${formatInTimeZone(
|
||||
Date.now(),
|
||||
timeZone,
|
||||
TimeFormat.HOUR_24,
|
||||
)})`,
|
||||
value: TimeFormat.HOUR_24,
|
||||
},
|
||||
{
|
||||
label: `12h (${formatInTimeZone(
|
||||
Date.now(),
|
||||
timeZone,
|
||||
TimeFormat.HOUR_12,
|
||||
)})`,
|
||||
value: TimeFormat.HOUR_12,
|
||||
},
|
||||
]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableTimeZoneOption';
|
||||
import { AVAILABLE_TIMEZONE_OPTIONS } from '@/settings/accounts/constants/AvailableTimezoneOptions';
|
||||
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
|
||||
type SettingsAccountsCalendarTimeZoneSelectProps = {
|
||||
value?: string;
|
||||
onChange: (nextValue: string) => void;
|
||||
};
|
||||
|
||||
export const SettingsAccountsCalendarTimeZoneSelect = ({
|
||||
value = detectTimeZone(),
|
||||
onChange,
|
||||
}: SettingsAccountsCalendarTimeZoneSelectProps) => (
|
||||
<Select
|
||||
dropdownId="settings-accounts-calendar-time-zone"
|
||||
dropdownWidth={416}
|
||||
label="Time zone"
|
||||
fullWidth
|
||||
value={findAvailableTimeZoneOption(value)?.value}
|
||||
options={AVAILABLE_TIMEZONE_OPTIONS}
|
||||
onChange={onChange}
|
||||
withSearchInput
|
||||
/>
|
||||
);
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
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';
|
||||
|
||||
type DateTimeSettingsDateFormatSelectProps = {
|
||||
value: DateFormat;
|
||||
onChange: (nextValue: DateFormat) => void;
|
||||
timeZone: string;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsDateFormatSelect = ({
|
||||
onChange,
|
||||
timeZone,
|
||||
value,
|
||||
}: DateTimeSettingsDateFormatSelectProps) => {
|
||||
const systemTimeZone = detectTimeZone();
|
||||
|
||||
const usedTimeZone = timeZone === 'system' ? systemTimeZone : timeZone;
|
||||
|
||||
const systemDateFormat = DateFormat[detectDateFormat()];
|
||||
|
||||
const systemDateFormatLabel = formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
systemDateFormat,
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-date-format"
|
||||
dropdownWidth={218}
|
||||
label={t`Date format`}
|
||||
fullWidth
|
||||
dropdownWidthAuto
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: t`System settings - ${systemDateFormatLabel}`,
|
||||
value: DateFormat.SYSTEM,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
DateFormat.MONTH_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.MONTH_FIRST,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
DateFormat.DAY_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.DAY_FIRST,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
DateFormat.YEAR_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.YEAR_FIRST,
|
||||
},
|
||||
]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||
import { detectTimeFormat } from '@/localization/utils/detectTimeFormat';
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
type DateTimeSettingsTimeFormatSelectProps = {
|
||||
value: TimeFormat;
|
||||
onChange: (nextValue: TimeFormat) => void;
|
||||
timeZone: string;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsTimeFormatSelect = ({
|
||||
onChange,
|
||||
timeZone,
|
||||
value,
|
||||
}: DateTimeSettingsTimeFormatSelectProps) => {
|
||||
const { t } = useLingui();
|
||||
const systemTimeZone = detectTimeZone();
|
||||
|
||||
const usedTimeZone = timeZone === 'system' ? systemTimeZone : timeZone;
|
||||
|
||||
const systemTimeFormat = TimeFormat[detectTimeFormat()];
|
||||
|
||||
const systemTimeFormatLabel = formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
systemTimeFormat,
|
||||
);
|
||||
|
||||
const hour24Label = formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
TimeFormat.HOUR_24,
|
||||
);
|
||||
|
||||
const hour12Label = formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
TimeFormat.HOUR_12,
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-time-format"
|
||||
dropdownWidth={218}
|
||||
label={t`Time format`}
|
||||
dropdownWidthAuto
|
||||
fullWidth
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: t`System Settings - ${systemTimeFormatLabel}`,
|
||||
value: TimeFormat.SYSTEM,
|
||||
},
|
||||
{
|
||||
label: t`24h (${hour24Label})`,
|
||||
value: TimeFormat.HOUR_24,
|
||||
},
|
||||
{
|
||||
label: t`12h (${hour12Label})`,
|
||||
value: TimeFormat.HOUR_12,
|
||||
},
|
||||
]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableTimeZoneOption';
|
||||
import { AVAILABLE_TIMEZONE_OPTIONS } from '@/settings/experience/constants/AvailableTimezoneOptions';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
|
||||
type DateTimeSettingsTimeZoneSelectProps = {
|
||||
value?: string;
|
||||
onChange: (nextValue: string) => void;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsTimeZoneSelect = ({
|
||||
value = detectTimeZone(),
|
||||
onChange,
|
||||
}: DateTimeSettingsTimeZoneSelectProps) => {
|
||||
const systemTimeZone = detectTimeZone();
|
||||
|
||||
const systemTimeZoneOption = findAvailableTimeZoneOption(systemTimeZone);
|
||||
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-time-zone"
|
||||
label={t`Time zone`}
|
||||
dropdownWidthAuto
|
||||
fullWidth
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: isDefined(systemTimeZoneOption)
|
||||
? t`System settings`.concat(` - ${systemTimeZoneOption.label}`)
|
||||
: t`System settings`,
|
||||
value: 'system',
|
||||
},
|
||||
...(AVAILABLE_TIMEZONE_OPTIONS as SelectOption<string>[]),
|
||||
]}
|
||||
onChange={onChange}
|
||||
withSearchInput
|
||||
/>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { getTimezoneOffset } from 'date-fns-tz';
|
||||
|
||||
import { AVAILABLE_TIME_ZONE_OPTIONS_BY_LABEL } from '@/settings/accounts/constants/AvailableTimezoneOptionsByLabel';
|
||||
import { AVAILABLE_TIME_ZONE_OPTIONS_BY_LABEL } from '@/settings/experience/constants/AvailableTimezoneOptionsByLabel';
|
||||
|
||||
export const AVAILABLE_TIMEZONE_OPTIONS = Object.values(
|
||||
AVAILABLE_TIME_ZONE_OPTIONS_BY_LABEL,
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { SELECT_DAY_DROPDOWN_ID } from '@/ui/input/components/internal/date/constants/SelectDayDropdownId';
|
||||
import { DayNameWithIndex } from '@/ui/input/components/internal/date/types/DayNameWithIndex';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
|
||||
export const DaySelect = ({
|
||||
label,
|
||||
selectedDayIndex,
|
||||
onChange,
|
||||
dayList,
|
||||
}: {
|
||||
label: string;
|
||||
selectedDayIndex: number;
|
||||
onChange: (dayIndex: number | string) => void;
|
||||
dayList: DayNameWithIndex[];
|
||||
}) => {
|
||||
const options: SelectOption<string | number>[] = useMemo(() => {
|
||||
const days = dayList?.map<SelectOption<string | number>>(
|
||||
({ day, index }) => ({
|
||||
label: day,
|
||||
value: index,
|
||||
}),
|
||||
);
|
||||
|
||||
return days;
|
||||
}, [dayList]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
fullWidth
|
||||
dropdownId={SELECT_DAY_DROPDOWN_ID}
|
||||
options={options}
|
||||
label={label}
|
||||
onChange={onChange}
|
||||
value={selectedDayIndex}
|
||||
dropdownWidth={218}
|
||||
dropdownWidthAuto
|
||||
/>
|
||||
);
|
||||
};
|
||||
+11
-1
@@ -4,6 +4,9 @@ import { lazy, Suspense, useContext } from 'react';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { CalendarStartDay } from '@/localization/constants/CalendarStartDay';
|
||||
import { detectCalendarStartDay } from '@/localization/utils/detectCalendarStartDay';
|
||||
import { AbsoluteDatePickerHeader } from '@/ui/input/components/internal/date/components/AbsoluteDatePickerHeader';
|
||||
import { DateTimeInput } from '@/ui/input/components/internal/date/components/DateTimeInput';
|
||||
import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader';
|
||||
@@ -17,6 +20,7 @@ import {
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconCalendarX } from 'twenty-ui/display';
|
||||
import {
|
||||
MenuItemLeftContent,
|
||||
@@ -343,7 +347,7 @@ export const DateTimePicker = ({
|
||||
|
||||
const { closeDropdown: closeDropdownMonthSelect } = useCloseDropdown();
|
||||
const { closeDropdown: closeDropdownYearSelect } = useCloseDropdown();
|
||||
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const handleClear = () => {
|
||||
closeDropdowns();
|
||||
onClear?.();
|
||||
@@ -501,6 +505,12 @@ export const DateTimePicker = ({
|
||||
openToDate={hasDate ? dateToUse : new Date()}
|
||||
disabledKeyboardNavigation
|
||||
onChange={handleDateChange as any}
|
||||
calendarStartDay={
|
||||
currentWorkspaceMember?.calendarStartDay ===
|
||||
CalendarStartDay.SYSTEM
|
||||
? CalendarStartDay[detectCalendarStartDay()]
|
||||
: (currentWorkspaceMember?.calendarStartDay ?? undefined)
|
||||
}
|
||||
customInput={
|
||||
<DateTimeInput
|
||||
date={internalDate}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const SELECT_DAY_DROPDOWN_ID = 'select-day-dropdown-Id';
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type DayNameWithIndex = {
|
||||
day: string;
|
||||
index: number;
|
||||
};
|
||||
+1
@@ -14,5 +14,6 @@ export const WORKSPACE_MEMBER_QUERY_FRAGMENT = gql`
|
||||
timeZone
|
||||
dateFormat
|
||||
timeFormat
|
||||
calendarStartDay
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -23,6 +23,7 @@ export type WorkspaceMember = {
|
||||
timeZone?: string | null;
|
||||
dateFormat?: WorkspaceMemberDateFormatEnum | null;
|
||||
timeFormat?: WorkspaceMemberTimeFormatEnum | null;
|
||||
calendarStartDay?: number | null;
|
||||
};
|
||||
|
||||
export type WorkspaceInvitation = {
|
||||
|
||||
+24
-4
@@ -13,16 +13,18 @@ import { detectTimeFormat } from '@/localization/utils/detectTimeFormat';
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { getWorkspaceDateFormatFromDateFormat } from '@/localization/utils/getWorkspaceDateFormatFromDateFormat';
|
||||
import { getWorkspaceTimeFormatFromTimeFormat } from '@/localization/utils/getWorkspaceTimeFormatFromTimeFormat';
|
||||
import { DateTimeSettingsDateFormatSelect } from '@/settings/experience/components/DateTimeSettingsDateFormatSelect';
|
||||
import { DateTimeSettingsTimeFormatSelect } from '@/settings/experience/components/DateTimeSettingsTimeFormatSelect';
|
||||
import { DateTimeSettingsTimeZoneSelect } from '@/settings/experience/components/DateTimeSettingsTimeZoneSelect';
|
||||
import { isNumber } from '@tiptap/core';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
WorkspaceMemberDateFormatEnum,
|
||||
WorkspaceMemberTimeFormatEnum,
|
||||
} from '~/generated/graphql';
|
||||
import { DateTimeSettingsDateFormatSelect } from '~/pages/settings/profile/appearance/components/DateTimeSettingsDateFormatSelect';
|
||||
import { DateTimeSettingsTimeFormatSelect } from '~/pages/settings/profile/appearance/components/DateTimeSettingsTimeFormatSelect';
|
||||
import { DateTimeSettingsTimeZoneSelect } from '~/pages/settings/profile/appearance/components/DateTimeSettingsTimeZoneSelect';
|
||||
import { DateTimeSettingsCalendarStartDaySelect } from '~/pages/settings/profile/appearance/components/DateTimeSettingsCalendarStartDaySelect';
|
||||
import { isEmptyObject } from '~/utils/isEmptyObject';
|
||||
import { logError } from '~/utils/logError';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -58,6 +60,20 @@ export const DateTimeSettings = () => {
|
||||
|
||||
if (!isDefined(currentWorkspaceMember)) return;
|
||||
|
||||
const handleUpdateCalendarStartDay = async (index: number) => {
|
||||
if (!isNumber(index) || index > 7 || index < 0) return;
|
||||
const changedFields = {
|
||||
calendarStartDay: index ?? 0,
|
||||
};
|
||||
|
||||
setCurrentWorkspaceMember({
|
||||
...currentWorkspaceMember,
|
||||
...changedFields,
|
||||
});
|
||||
|
||||
updateWorkspaceMember(changedFields);
|
||||
};
|
||||
|
||||
const handleSettingsChange = (
|
||||
settingName: 'timeZone' | 'dateFormat' | 'timeFormat',
|
||||
value: string,
|
||||
@@ -140,6 +156,10 @@ export const DateTimeSettings = () => {
|
||||
onChange={(value) => handleSettingsChange('timeFormat', value)}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
<DateTimeSettingsCalendarStartDaySelect
|
||||
value={currentWorkspaceMember?.calendarStartDay ?? 0}
|
||||
onChange={handleUpdateCalendarStartDay}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { CalendarStartDay } from '@/localization/constants/CalendarStartDay';
|
||||
import { detectCalendarStartDay } from '@/localization/utils/detectCalendarStartDay';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { DayNameWithIndex } from '@/ui/input/components/internal/date/types/DayNameWithIndex';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
|
||||
type DateTimeSettingsCalendarStartDaySelectProps = {
|
||||
value: number;
|
||||
onChange: (nextValue: number) => void;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsCalendarStartDaySelect = ({
|
||||
value,
|
||||
onChange,
|
||||
}: DateTimeSettingsCalendarStartDaySelectProps) => {
|
||||
const systemCalendarStartDay = CalendarStartDay[detectCalendarStartDay()];
|
||||
|
||||
const options: SelectOption<number>[] = useMemo(() => {
|
||||
const systemDayLabel =
|
||||
systemCalendarStartDay === CalendarStartDay.SUNDAY
|
||||
? t`System settings - Sunday`
|
||||
: systemCalendarStartDay === CalendarStartDay.MONDAY
|
||||
? t`System settings - Monday`
|
||||
: t`System settings - Saturday`;
|
||||
|
||||
const allowedDaysWeek: DayNameWithIndex[] = [
|
||||
{
|
||||
day: systemDayLabel,
|
||||
index: CalendarStartDay.SYSTEM,
|
||||
},
|
||||
{ day: t`Sunday`, index: CalendarStartDay.SUNDAY },
|
||||
{ day: t`Monday`, index: CalendarStartDay.MONDAY },
|
||||
{ day: t`Saturday`, index: CalendarStartDay.SATURDAY },
|
||||
];
|
||||
|
||||
return allowedDaysWeek.map(({ day, index }) => ({
|
||||
label: day,
|
||||
value: index,
|
||||
}));
|
||||
}, [systemCalendarStartDay]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-calendar-start-day"
|
||||
dropdownWidth={218}
|
||||
label={t`Calendar start day`}
|
||||
fullWidth
|
||||
dropdownWidthAuto
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableTimeZoneOption';
|
||||
import { AVAILABLE_TIMEZONE_OPTIONS } from '@/settings/accounts/constants/AvailableTimezoneOptions';
|
||||
import { AVAILABLE_TIMEZONE_OPTIONS } from '@/settings/experience/constants/AvailableTimezoneOptions';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
|
||||
type DateTimeSettingsTimeZoneSelectProps = {
|
||||
value?: string;
|
||||
@@ -32,7 +33,7 @@ export const DateTimeSettingsTimeZoneSelect = ({
|
||||
: t`System settings`,
|
||||
value: 'system',
|
||||
},
|
||||
...AVAILABLE_TIMEZONE_OPTIONS,
|
||||
...(AVAILABLE_TIMEZONE_OPTIONS as SelectOption<string>[]),
|
||||
]}
|
||||
onChange={onChange}
|
||||
withSearchInput
|
||||
|
||||
+11
-10
@@ -4,12 +4,12 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBa
|
||||
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { ColorSchemePicker } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { DateTimeSettings } from '~/pages/settings/profile/appearance/components/DateTimeSettings';
|
||||
import { LocalePicker } from '~/pages/settings/profile/appearance/components/LocalePicker';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { ColorSchemePicker } from 'twenty-ui/input';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
|
||||
export const SettingsExperience = () => {
|
||||
const { colorScheme, setColorScheme } = useColorScheme();
|
||||
@@ -38,13 +38,6 @@ export const SettingsExperience = () => {
|
||||
systemLabel={t`System settings`}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Date and time`}
|
||||
description={t`Configure how dates are displayed across the app`}
|
||||
/>
|
||||
<DateTimeSettings />
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
@@ -53,6 +46,14 @@ export const SettingsExperience = () => {
|
||||
/>
|
||||
<LocalePicker />
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Date and time`}
|
||||
description={t`Configure how dates are displayed across the app`}
|
||||
/>
|
||||
<DateTimeSettings />
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { Max, Min } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
@@ -38,6 +39,11 @@ export class WorkspaceMember {
|
||||
@Field({ nullable: true })
|
||||
locale: string;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
@Min(0)
|
||||
@Max(7)
|
||||
calendarStartDay: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
timeZone: string;
|
||||
|
||||
|
||||
+2
@@ -60,6 +60,7 @@ export class WorkspaceMemberTranspiler {
|
||||
timeFormat,
|
||||
timeZone,
|
||||
dateFormat,
|
||||
calendarStartDay,
|
||||
} = workspaceMemberEntity;
|
||||
|
||||
const avatarUrl = this.generateSignedAvatarUrl({
|
||||
@@ -84,6 +85,7 @@ export class WorkspaceMemberTranspiler {
|
||||
timeFormat: timeFormat as WorkspaceMemberTimeFormatEnum,
|
||||
timeZone,
|
||||
roles,
|
||||
calendarStartDay,
|
||||
} satisfies WorkspaceMember;
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -532,6 +532,7 @@ export const WORKSPACE_MEMBER_STANDARD_FIELD_IDS = {
|
||||
dateFormat: '20202020-af13-4e11-b1e7-b8cf5ea13dc0',
|
||||
timeFormat: '20202020-8acb-4cf8-a851-a6ed443c8d81',
|
||||
searchVector: '20202020-46d0-4e7f-bc26-74c0edaeb619',
|
||||
calendarStartDay: '20202020-92d0-1d7f-a126-25ededa6b142',
|
||||
};
|
||||
|
||||
export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = {
|
||||
|
||||
+14
@@ -7,6 +7,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
import { NumberDataType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
|
||||
import { FullNameMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
|
||||
@@ -143,6 +144,19 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceIsSystem()
|
||||
userEmail: string;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.calendarStartDay,
|
||||
type: FieldMetadataType.NUMBER,
|
||||
label: msg`Start of the week`,
|
||||
defaultValue: 7,
|
||||
description: msg`User's preferred start day of the week`,
|
||||
settings: {
|
||||
dataType: NumberDataType.INT,
|
||||
},
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
calendarStartDay: number;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.userId,
|
||||
type: FieldMetadataType.UUID,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { SELECT_DAY_DROPDOWN_ID } from '@/ui/input/components/internal/date/constants/SelectDayDropdownId';
|
||||
import { DayNameWithIndex } from '@/ui/input/components/internal/date/types/DayNameWithIndex';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
|
||||
export const DaySelect = ({
|
||||
label,
|
||||
selectedDayIndex,
|
||||
onChange,
|
||||
dayList,
|
||||
}: {
|
||||
label: string;
|
||||
selectedDayIndex: number;
|
||||
onChange: (dayIndex: number | string) => void;
|
||||
dayList: DayNameWithIndex[];
|
||||
}) => {
|
||||
const options: SelectOption<string | number>[] = useMemo(() => {
|
||||
const days = dayList?.map<SelectOption<string | number>>(
|
||||
({ day, index }) => ({
|
||||
label: day,
|
||||
value: index,
|
||||
}),
|
||||
);
|
||||
|
||||
return days;
|
||||
}, [dayList]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
fullWidth
|
||||
dropdownId={SELECT_DAY_DROPDOWN_ID}
|
||||
options={options}
|
||||
label={label}
|
||||
onChange={onChange}
|
||||
value={selectedDayIndex}
|
||||
dropdownWidth={218}
|
||||
dropdownWidthAuto
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export const SELECT_DAY_DROPDOWN_ID = 'select-day-dropdown-Id';
|
||||
@@ -0,0 +1,4 @@
|
||||
export type DayNameWithIndex = {
|
||||
day: string;
|
||||
index: number;
|
||||
};
|
||||
Reference in New Issue
Block a user