From ee33d9b1c9689e8ccd759915e1b2d4d5c5d21bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 10:05:16 +0100 Subject: [PATCH 01/39] add RHP screen for workflows autoreporting --- src/libs/Navigation/linkingConfig/config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 48d649cc4dd9..79a4bb84e207 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -241,6 +241,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.DESCRIPTION]: { path: ROUTES.WORKSPACE_PROFILE_DESCRIPTION.route, }, + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: { + path: ROUTES.WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY.route, + }, [SCREENS.WORKSPACE.SHARE]: { path: ROUTES.WORKSPACE_PROFILE_SHARE.route, }, From df155ac71f92d521d658ccef7c7b5540fa0fd483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 10:05:30 +0100 Subject: [PATCH 02/39] add autoreporting screen --- src/SCREENS.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 520895c89c98..42283a7c0468 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -216,6 +216,7 @@ const SCREENS = { CATEGORIES: 'Workspace_Categories', CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', + WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency', DESCRIPTION: 'Workspace_Profile_Description', SHARE: 'Workspace_Profile_Share', NAME: 'Workspace_Profile_Name', From bc460f4faf3ef1acaa4784765ead16b3e774b2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 10:05:40 +0100 Subject: [PATCH 03/39] add workflows autoreporting route --- src/ROUTES.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a8786bda3ffb..d7ab571b4ccb 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -486,6 +486,10 @@ const ROUTES = { route: 'workspace/:policyID/workflows', getRoute: (policyID: string) => `workspace/${policyID}/workflows` as const, }, + WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY: { + route: 'workspace/:policyID/workflows/autoreporting/frequency', + getRoute: (policyID: string) => `workspace/${policyID}/workflows/autoreporting/frequency` as const, + }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', getRoute: (policyID: string) => `workspace/${policyID}/card` as const, From 25f660bc70431a1e867256c134db9ccb828c3edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 10:06:12 +0100 Subject: [PATCH 04/39] add workflows autoreporting to modal stack navigator --- src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 2be262aa5f0f..7f13a4b0596e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -256,6 +256,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/ExitSurvey/ExitSurveyReasonPage').default as React.ComponentType, [SCREENS.SETTINGS.EXIT_SURVEY.RESPONSE]: () => require('../../../pages/settings/ExitSurvey/ExitSurveyResponsePage').default as React.ComponentType, [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: () => require('../../../pages/settings/ExitSurvey/ExitSurveyConfirmPage').default as React.ComponentType, + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../pages/workspace/WorkspaceProfileDescriptionPage').default as React.ComponentType }); const EnablePaymentsStackNavigator = createModalStackNavigator({ From e536b2db9f9f3a49103a8f88852e6c7d46254ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 10:07:14 +0100 Subject: [PATCH 05/39] use workflows autoreporting route --- src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index fc1ed1d19560..7ced93765241 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {FlatList, View} from 'react-native'; import * as Illustrations from '@components/Icon/Illustrations'; import MenuItem from '@components/MenuItem'; @@ -22,6 +22,8 @@ import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; import ToggleSettingOptionRow from './ToggleSettingsOptionRow'; import type {ToggleSettingOptionRowProps} from './ToggleSettingsOptionRow'; +import Navigation from '@libs/Navigation/Navigation'; +import ROUTES from '@src/ROUTES'; type WorkspaceWorkflowsPageProps = WithPolicyProps & StackScreenProps; @@ -36,6 +38,8 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { const policyOwnerDisplayName = ownerPersonalDetails[0]?.displayName; const containerStyle = useMemo(() => [styles.ph8, styles.mhn8, styles.ml11, styles.pv3, styles.pr0, styles.pl4, styles.mr0, styles.widthAuto, styles.mt4], [styles]); + const onPressAutoReportingFrequency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY.getRoute(policy?.id ?? '')), [policy?.id]); + const items: ToggleSettingOptionRowProps[] = useMemo( () => [ { @@ -50,8 +54,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { title={translate('workflowsPage.submissionFrequency')} titleStyle={styles.textLabelSupportingNormal} descriptionTextStyle={styles.textNormalThemeText} - // onPress={() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY).getRoute(route.params.policyID))} - // TODO will be done in https://github.com/Expensify/Expensify/issues/368332 + onPress={onPressAutoReportingFrequency} description={translate('workflowsPage.weeklyFrequency')} shouldShowRightIcon wrapperStyle={containerStyle} From 8114f4860909760e6d5d7c125992403586ccc05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 17:14:14 +0100 Subject: [PATCH 06/39] add new translation keys for workflows auto reporting --- src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 4d7041d4a791..f884ebad0f2c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1035,6 +1035,7 @@ export default { delaySubmissionTitle: 'Delay submissions', delaySubmissionDescription: 'Expenses are shared right away for better spend visibility. Set a slower cadence if needed.', submissionFrequency: 'Submission frequency', + submissionFrequencyDateOfMonth: 'Date of month', weeklyFrequency: 'Weekly', monthlyFrequency: 'Monthly', twiceAMonthFrequency: 'Twice a month', @@ -1047,6 +1048,9 @@ export default { addApprovalsDescription: 'Require additional approval before authorizing a payment.', makeOrTrackPaymentsTitle: 'Make or track payments', makeOrTrackPaymentsDescription: 'Add an authorized payer for payments made in Expensify, or simply track payments made elsewhere.', + editor: { + submissionFrequency: 'Choose how long Expensify should wait before sharing error-free spend.', + }, }, reportFraudPage: { title: 'Report virtual card fraud', diff --git a/src/languages/es.ts b/src/languages/es.ts index c9ff087d0de7..239e5e0708c9 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1031,6 +1031,7 @@ export default { delaySubmissionTitle: 'Retrasar envíos', delaySubmissionDescription: 'Los gastos se comparten de inmediato para una mejor visibilidad del gasto. Establece una cadencia más lenta si es necesario.', submissionFrequency: 'Frecuencia de envíos', + submissionFrequencyDateOfMonth: 'Fecha del mes', weeklyFrequency: 'Semanal', monthlyFrequency: 'Mensual', twiceAMonthFrequency: 'Dos veces al mes', @@ -1043,6 +1044,9 @@ export default { addApprovalsDescription: 'Requiere una aprobación adicional antes de autorizar un pago.', makeOrTrackPaymentsTitle: 'Realizar o seguir pagos', makeOrTrackPaymentsDescription: 'Añade un pagador autorizado para los pagos realizados en Expensify, o simplemente realiza un seguimiento de los pagos realizados en otro lugar.', + editor: { + submissionFrequency: 'Elige cuánto tiempo Expensify debe esperar antes de compartir los gastos sin errores.', + }, }, reportFraudPage: { title: 'Reportar fraude con la tarjeta virtual', From 1bc337acf9f1d6a4b7057fad32c2aab8bf26848e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 17:14:30 +0100 Subject: [PATCH 07/39] add SetWorkspaceAutoReportingFrequency --- src/libs/API/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 17cc366ba3b7..1615040ef602 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -9,6 +9,7 @@ type ApiRequest = ValueOf; const WRITE_COMMANDS = { SET_WORKSPACE_AUTO_REPORTING: 'SetWorkspaceAutoReporting', + SET_WORKSPACE_AUTO_REPORTING_FREQUENCY: 'SetWorkspaceAutoReportingFrequency', SET_WORKSPACE_APPROVAL_MODE: 'SetWorkspaceApprovalMode', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', From a515297f0fe56e20fd0c7353944c3c1ab8fce458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 17:15:11 +0100 Subject: [PATCH 08/39] add new setWorkspaceAutoReportingFrequency() --- src/libs/actions/Policy.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 57cd4a6fc071..9603c411417b 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -421,6 +421,42 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean) { API.write(WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING, params, {optimisticData, failureData, successData}); } +function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoReportingFrequency: frequency, + pendingFields: {autoReportingFrequency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: {autoReportingFrequency: null}, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: {autoReportingFrequency: null}, + }, + }, + ]; + + const params = {policyID, frequency}; + API.write(WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_FREQUENCY, params, {optimisticData, failureData, successData}); +} + function setWorkspaceApprovalMode(policyID: string, approver: string, approvalMode: ValueOf) { const isAutoApprovalEnabled = approvalMode === CONST.POLICY.APPROVAL_MODE.BASIC; @@ -2220,5 +2256,6 @@ export { setWorkspaceInviteMessageDraft, setWorkspaceAutoReporting, setWorkspaceApprovalMode, + setWorkspaceAutoReportingFrequency, updateWorkspaceDescription, }; From 618975afd5bd372375696fe44b5bf28a710153d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 17:15:42 +0100 Subject: [PATCH 09/39] fix page used for auto reporting frequency --- src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 7f13a4b0596e..26de5c4558dc 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -256,7 +256,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/ExitSurvey/ExitSurveyReasonPage').default as React.ComponentType, [SCREENS.SETTINGS.EXIT_SURVEY.RESPONSE]: () => require('../../../pages/settings/ExitSurvey/ExitSurveyResponsePage').default as React.ComponentType, [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: () => require('../../../pages/settings/ExitSurvey/ExitSurveyConfirmPage').default as React.ComponentType, - [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../pages/workspace/WorkspaceProfileDescriptionPage').default as React.ComponentType + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ From 5fccc641bdcc3ab7f2eea15f7067dde63423e57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 17:16:00 +0100 Subject: [PATCH 10/39] use auto reporting frequency --- .../workspace/workflows/WorkspaceWorkflowsPage.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 7ced93765241..f2cc24b8496c 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -10,6 +10,7 @@ import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -19,11 +20,12 @@ import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import ToggleSettingOptionRow from './ToggleSettingsOptionRow'; import type {ToggleSettingOptionRowProps} from './ToggleSettingsOptionRow'; -import Navigation from '@libs/Navigation/Navigation'; -import ROUTES from '@src/ROUTES'; +import {autoReportingFrequencyDisplayNames} from './WorkspaceAutoReportingFrequencyPage'; +import type {AutoReportingFrequencyKey} from './WorkspaceAutoReportingFrequencyPage'; type WorkspaceWorkflowsPageProps = WithPolicyProps & StackScreenProps; @@ -55,7 +57,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { titleStyle={styles.textLabelSupportingNormal} descriptionTextStyle={styles.textNormalThemeText} onPress={onPressAutoReportingFrequency} - description={translate('workflowsPage.weeklyFrequency')} + description={autoReportingFrequencyDisplayNames[(policy?.autoReportingFrequency as AutoReportingFrequencyKey) ?? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]} shouldShowRightIcon wrapperStyle={containerStyle} hoverAndPressStyle={[styles.mr0, styles.br2]} @@ -111,7 +113,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { isActive: false, // TODO will be done in https://github.com/Expensify/Expensify/issues/368335 }, ], - [policy, route.params.policyID, styles, translate, policyOwnerDisplayName, containerStyle, isOffline, StyleUtils], + [policy, route.params.policyID, styles, translate, policyOwnerDisplayName, containerStyle, isOffline, StyleUtils, onPressAutoReportingFrequency], ); const renderItem = ({item}: {item: ToggleSettingOptionRowProps}) => ( From ad5a4cbbead062e8a955ca0cf51c53cea6fba560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 17:16:29 +0100 Subject: [PATCH 11/39] auto reporting main frequency page --- .../WorkspaceAutoReportingFrequencyPage.tsx | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx new file mode 100644 index 000000000000..0788c9419643 --- /dev/null +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -0,0 +1,139 @@ +import React, {useState} from 'react'; +import {FlatList} from 'react-native-gesture-handler'; +import type {ValueOf} from 'type-fest'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MenuItem from '@components/MenuItem'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ScreenWrapper from '@components/ScreenWrapper'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import withPolicy from '@pages/workspace/withPolicy'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type AutoReportingFrequencyKey = Exclude, 'instant'>; + +type WorkspaceProfileCurrentPageProps = WithPolicyAndFullscreenLoadingProps; + +type WorkspaceAutoReportingFrequencyPageSectionItem = { + text: string; + keyForList: string; + isSelected: boolean; +}; + +type AutoReportingFrequencyDisplayNames = Record; + +const autoReportingFrequencyDisplayNames: AutoReportingFrequencyDisplayNames = { + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY]: 'Monthly', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE]: 'Daily', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]: 'Weekly', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY]: 'Twice a month', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: 'By trip', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: 'Manually', +}; + +const DAYS_OF_MONTH = 29; + +function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceProfileCurrentPageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const [isMonthlyFrequency, setIsMonthlyFrequency] = useState(policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY); + + const autoReportingFrequencyItems: WorkspaceAutoReportingFrequencyPageSectionItem[] = Object.keys(autoReportingFrequencyDisplayNames).map((frequencyKey) => { + const isSelected = policy?.autoReportingFrequency === frequencyKey; + + return { + text: autoReportingFrequencyDisplayNames[frequencyKey as AutoReportingFrequencyKey] || '', + keyForList: frequencyKey, + isSelected, + }; + }); + + const onSelectAutoReportingFrequency = (item: WorkspaceAutoReportingFrequencyPageSectionItem) => { + Policy.setWorkspaceAutoReportingFrequency(policy?.id ?? '', item.keyForList as AutoReportingFrequencyKey); + if (item.keyForList !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY) { + Navigation.goBack(); + } + setIsMonthlyFrequency(true); + }; + + // Generate days of month for the monthly frequency + const daysOfMonth = Array.from({length: DAYS_OF_MONTH}, (value, index) => { + const day = index + 1; + let suffix = 'th'; + if (day === 1 || day === 21) { + suffix = 'st'; + } else if (day === 2 || day === 22) { + suffix = 'nd'; + } else if (day === 3 || day === 23) { + suffix = 'rd'; + } + + return `${day}${suffix}`; + }).concat(['Last day of month', 'Last business day of the month']); + + const monthlyFrequencyDetails = () => ( + + + + ); + + const renderItem = ({item}: {item: WorkspaceAutoReportingFrequencyPageSectionItem}) => ( + <> + onSelectAutoReportingFrequency(item)} + showTooltip={false} + /> + {isMonthlyFrequency && item.keyForList === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY ? monthlyFrequencyDetails() : null} + + ); + + return ( + + + + Navigation.goBack()} + /> + + item.text} + /> + + + + ); +} + +WorkspaceAutoReportingFrequencyPage.displayName = 'WorkspaceAutoReportingFrequencyPage'; +export type {AutoReportingFrequencyDisplayNames, AutoReportingFrequencyKey}; +export {autoReportingFrequencyDisplayNames}; +export default withPolicy(WorkspaceAutoReportingFrequencyPage); From e867c6fb911d0c07414777957762f7f931289f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 17:59:59 +0100 Subject: [PATCH 12/39] fix lint and tsc --- .../SetWorkspaceAutoReportingFrequencyParams.ts | 9 +++++++++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 1 + src/libs/Navigation/types.ts | 3 +++ src/libs/actions/Policy.ts | 3 ++- .../workflows/WorkspaceAutoReportingFrequencyPage.tsx | 6 +++--- 6 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 src/libs/API/parameters/SetWorkspaceAutoReportingFrequencyParams.ts diff --git a/src/libs/API/parameters/SetWorkspaceAutoReportingFrequencyParams.ts b/src/libs/API/parameters/SetWorkspaceAutoReportingFrequencyParams.ts new file mode 100644 index 000000000000..877c2e21044b --- /dev/null +++ b/src/libs/API/parameters/SetWorkspaceAutoReportingFrequencyParams.ts @@ -0,0 +1,9 @@ +import type CONST from "@src/CONST"; +import type { ValueOf } from "type-fest"; + +type SetWorkspaceAutoReportingFrequencyParams = { + policyID: string; + frequency: ValueOf; +}; + +export default SetWorkspaceAutoReportingFrequencyParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 0b0a81eb21f8..135df520188a 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -146,5 +146,6 @@ export type {default as CancelPaymentParams} from './CancelPaymentParams'; export type {default as AcceptACHContractForBankAccount} from './AcceptACHContractForBankAccount'; export type {default as UpdateWorkspaceDescriptionParams} from './UpdateWorkspaceDescriptionParams'; export type {default as SetWorkspaceAutoReportingParams} from './SetWorkspaceAutoReportingParams'; +export type {default as SetWorkspaceAutoReportingFrequencyParams} from './SetWorkspaceAutoReportingFrequencyParams'; export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams'; export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 1615040ef602..322e3ad79d6c 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -297,6 +297,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT]: Parameters.AcceptACHContractForBankAccount; [WRITE_COMMANDS.UPDATE_WORKSPACE_DESCRIPTION]: Parameters.UpdateWorkspaceDescriptionParams; [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING]: Parameters.SetWorkspaceAutoReportingParams; + [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_FREQUENCY]: Parameters.SetWorkspaceAutoReportingFrequencyParams; [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; }; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 765ab76fd638..68820226059b 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -63,6 +63,9 @@ type CentralPaneNavigatorParamList = { [SCREENS.WORKSPACE.WORKFLOWS]: { policyID: string; }; + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: { + policyID: string; + }; [SCREENS.WORKSPACE.REIMBURSE]: { policyID: string; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 9603c411417b..211ff0dc7397 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -22,6 +22,7 @@ import type { OpenWorkspaceReimburseViewParams, SetWorkspaceApprovalModeParams, SetWorkspaceAutoReportingParams, + SetWorkspaceAutoReportingFrequencyParams, UpdateWorkspaceAvatarParams, UpdateWorkspaceCustomUnitAndRateParams, UpdateWorkspaceDescriptionParams, @@ -453,7 +454,7 @@ function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf }, ]; - const params = {policyID, frequency}; + const params: SetWorkspaceAutoReportingFrequencyParams = {policyID, frequency}; API.write(WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_FREQUENCY, params, {optimisticData, failureData, successData}); } diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index 0788c9419643..edcef29373c6 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -11,15 +11,15 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import withPolicy from '@pages/workspace/withPolicy'; type AutoReportingFrequencyKey = Exclude, 'instant'>; -type WorkspaceProfileCurrentPageProps = WithPolicyAndFullscreenLoadingProps; +type WorkspaceAutoReportingFrequencyPageProps = WithPolicyAndFullscreenLoadingProps; type WorkspaceAutoReportingFrequencyPageSectionItem = { text: string; @@ -40,7 +40,7 @@ const autoReportingFrequencyDisplayNames: AutoReportingFrequencyDisplayNames = { const DAYS_OF_MONTH = 29; -function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceProfileCurrentPageProps) { +function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFrequencyPageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [isMonthlyFrequency, setIsMonthlyFrequency] = useState(policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY); From aa2dcf69f3e995af535046736b39599b3a4b39bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 26 Feb 2024 18:07:30 +0100 Subject: [PATCH 13/39] fix prettier --- .../parameters/SetWorkspaceAutoReportingFrequencyParams.ts | 4 ++-- src/libs/actions/Policy.ts | 2 +- .../workflows/WorkspaceAutoReportingFrequencyPage.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/API/parameters/SetWorkspaceAutoReportingFrequencyParams.ts b/src/libs/API/parameters/SetWorkspaceAutoReportingFrequencyParams.ts index 877c2e21044b..81c6b47b8e09 100644 --- a/src/libs/API/parameters/SetWorkspaceAutoReportingFrequencyParams.ts +++ b/src/libs/API/parameters/SetWorkspaceAutoReportingFrequencyParams.ts @@ -1,5 +1,5 @@ -import type CONST from "@src/CONST"; -import type { ValueOf } from "type-fest"; +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; type SetWorkspaceAutoReportingFrequencyParams = { policyID: string; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 211ff0dc7397..b1c9321b6a3c 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -21,8 +21,8 @@ import type { OpenWorkspaceParams, OpenWorkspaceReimburseViewParams, SetWorkspaceApprovalModeParams, - SetWorkspaceAutoReportingParams, SetWorkspaceAutoReportingFrequencyParams, + SetWorkspaceAutoReportingParams, UpdateWorkspaceAvatarParams, UpdateWorkspaceCustomUnitAndRateParams, UpdateWorkspaceDescriptionParams, diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index edcef29373c6..ee9a8d29a0aa 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -11,11 +11,11 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; +import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import withPolicy from '@pages/workspace/withPolicy'; type AutoReportingFrequencyKey = Exclude, 'instant'>; From b53a702728650c3c265e1b811a2c91ba735de5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 09:06:45 +0100 Subject: [PATCH 14/39] add keys for auto reporting frequencies --- src/languages/en.ts | 10 ++++++++++ src/languages/es.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index f884ebad0f2c..b789f3b1a622 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1051,6 +1051,16 @@ export default { editor: { submissionFrequency: 'Choose how long Expensify should wait before sharing error-free spend.', }, + frequencies: { + weekly: 'Weekly', + monthly: 'Monthly', + twiceAMonth: 'Twice a month', + byTrip: 'By trip', + manually: 'Manually', + daily: 'Daily', + lastDayOfMonth: 'Last day of the month', + lastBusinessDayOfMonth: 'Last business day of the month', + }, }, reportFraudPage: { title: 'Report virtual card fraud', diff --git a/src/languages/es.ts b/src/languages/es.ts index 239e5e0708c9..cb7f93aefd08 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1047,6 +1047,16 @@ export default { editor: { submissionFrequency: 'Elige cuánto tiempo Expensify debe esperar antes de compartir los gastos sin errores.', }, + frequencies: { + weekly: 'Semanal', + monthly: 'Mensual', + twiceAMonth: 'Dos veces al mes', + byTrip: 'Por viaje', + manually: 'Manualmente', + daily: 'Diario', + lastDayOfMonth: 'Último día del mes', + lastBusinessDayOfMonth: 'Último día hábil del mes', + }, }, reportFraudPage: { title: 'Reportar fraude con la tarjeta virtual', From 78f1704e6c295278886ceb22a76475ceaddd0ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 09:07:26 +0100 Subject: [PATCH 15/39] translate auto reporting frequencies --- .../WorkspaceAutoReportingFrequencyPage.tsx | 35 ++++++++++--------- .../workflows/WorkspaceWorkflowsPage.tsx | 12 ++++--- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index ee9a8d29a0aa..a53a5839d8e0 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -9,6 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import {translate as globalTranslate} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import withPolicy from '@pages/workspace/withPolicy'; @@ -29,27 +30,27 @@ type WorkspaceAutoReportingFrequencyPageSectionItem = { type AutoReportingFrequencyDisplayNames = Record; -const autoReportingFrequencyDisplayNames: AutoReportingFrequencyDisplayNames = { - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY]: 'Monthly', - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE]: 'Daily', - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]: 'Weekly', - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY]: 'Twice a month', - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: 'By trip', - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: 'Manually', -}; +const getAutoReportingFrequencyDisplayNames = (locale: 'en' | 'es' | 'es-ES' | 'es_ES'): AutoReportingFrequencyDisplayNames => ({ + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY]: globalTranslate(locale, 'workflowsPage.frequencies.monthly'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE]: globalTranslate(locale, 'workflowsPage.frequencies.daily'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]: globalTranslate(locale, 'workflowsPage.frequencies.weekly'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY]: globalTranslate(locale, 'workflowsPage.frequencies.twiceAMonth'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: globalTranslate(locale, 'workflowsPage.frequencies.byTrip'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: globalTranslate(locale, 'workflowsPage.frequencies.manually'), +}); -const DAYS_OF_MONTH = 29; +const DAYS_OF_MONTH = 28; function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFrequencyPageProps) { - const {translate} = useLocalize(); + const {translate, preferredLocale} = useLocalize(); const styles = useThemeStyles(); const [isMonthlyFrequency, setIsMonthlyFrequency] = useState(policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY); - const autoReportingFrequencyItems: WorkspaceAutoReportingFrequencyPageSectionItem[] = Object.keys(autoReportingFrequencyDisplayNames).map((frequencyKey) => { + const autoReportingFrequencyItems: WorkspaceAutoReportingFrequencyPageSectionItem[] = Object.keys(getAutoReportingFrequencyDisplayNames(preferredLocale)).map((frequencyKey) => { const isSelected = policy?.autoReportingFrequency === frequencyKey; return { - text: autoReportingFrequencyDisplayNames[frequencyKey as AutoReportingFrequencyKey] || '', + text: getAutoReportingFrequencyDisplayNames(preferredLocale)[frequencyKey as AutoReportingFrequencyKey] || '', keyForList: frequencyKey, isSelected, }; @@ -58,9 +59,11 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre const onSelectAutoReportingFrequency = (item: WorkspaceAutoReportingFrequencyPageSectionItem) => { Policy.setWorkspaceAutoReportingFrequency(policy?.id ?? '', item.keyForList as AutoReportingFrequencyKey); if (item.keyForList !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY) { + setIsMonthlyFrequency(false); Navigation.goBack(); + } else { + setIsMonthlyFrequency(true); } - setIsMonthlyFrequency(true); }; // Generate days of month for the monthly frequency @@ -76,7 +79,7 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre } return `${day}${suffix}`; - }).concat(['Last day of month', 'Last business day of the month']); + }).concat([translate('workflowsPage.frequencies.lastDayOfMonth'), translate('workflowsPage.frequencies.lastBusinessDayOfMonth')]); const monthlyFrequencyDetails = () => ( @@ -119,7 +122,7 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre > Navigation.goBack()} + onBackButtonPress={Navigation.goBack} /> ; function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { - const {translate} = useLocalize(); + const {translate, preferredLocale} = useLocalize(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -57,7 +57,11 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { titleStyle={styles.textLabelSupportingNormal} descriptionTextStyle={styles.textNormalThemeText} onPress={onPressAutoReportingFrequency} - description={autoReportingFrequencyDisplayNames[(policy?.autoReportingFrequency as AutoReportingFrequencyKey) ?? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]} + description={ + getAutoReportingFrequencyDisplayNames(preferredLocale)[ + (policy?.autoReportingFrequency as AutoReportingFrequencyKey) ?? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY + ] + } shouldShowRightIcon wrapperStyle={containerStyle} hoverAndPressStyle={[styles.mr0, styles.br2]} @@ -113,7 +117,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { isActive: false, // TODO will be done in https://github.com/Expensify/Expensify/issues/368335 }, ], - [policy, route.params.policyID, styles, translate, policyOwnerDisplayName, containerStyle, isOffline, StyleUtils, onPressAutoReportingFrequency], + [policy, route.params.policyID, styles, translate, policyOwnerDisplayName, containerStyle, isOffline, StyleUtils, onPressAutoReportingFrequency, preferredLocale], ); const renderItem = ({item}: {item: ToggleSettingOptionRowProps}) => ( From 2c85f034756f7cdf5d01474b4d94cfc886d9f9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 13:35:40 +0100 Subject: [PATCH 16/39] add workflows translation key for monthly offset --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index b789f3b1a622..8420ebe8d0ea 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1036,6 +1036,7 @@ export default { delaySubmissionDescription: 'Expenses are shared right away for better spend visibility. Set a slower cadence if needed.', submissionFrequency: 'Submission frequency', submissionFrequencyDateOfMonth: 'Date of month', + submissionFrequencyDayOfMonth: 'Day of month', weeklyFrequency: 'Weekly', monthlyFrequency: 'Monthly', twiceAMonthFrequency: 'Twice a month', diff --git a/src/languages/es.ts b/src/languages/es.ts index cb7f93aefd08..206c9747f21f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1032,6 +1032,7 @@ export default { delaySubmissionDescription: 'Los gastos se comparten de inmediato para una mejor visibilidad del gasto. Establece una cadencia más lenta si es necesario.', submissionFrequency: 'Frecuencia de envíos', submissionFrequencyDateOfMonth: 'Fecha del mes', + submissionFrequencyDayOfMonth: 'Día del mes', weeklyFrequency: 'Semanal', monthlyFrequency: 'Mensual', twiceAMonthFrequency: 'Dos veces al mes', From dfd6052d10d6e9f0de4aed1afcbed05aaafedf90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 13:37:56 +0100 Subject: [PATCH 17/39] add new screen and route for monthly offset --- src/ROUTES.ts | 4 ++++ src/SCREENS.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 +++ 3 files changed, 8 insertions(+) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index d7ab571b4ccb..b0df084045f4 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -490,6 +490,10 @@ const ROUTES = { route: 'workspace/:policyID/workflows/autoreporting/frequency', getRoute: (policyID: string) => `workspace/${policyID}/workflows/autoreporting/frequency` as const, }, + WORKSPACE_WORKFLOWS_AUTOREPORTING_MONTHLY_OFFSET: { + route: 'workspace/:policyID/workflows/autoreporting/frequency/monthly-offset', + getRoute: (policyID: string) => `workspace/${policyID}/workflows/autoreporting/frequency/monthly-offset` as const, + }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', getRoute: (policyID: string) => `workspace/${policyID}/card` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 42283a7c0468..4e6e78b683f8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -217,6 +217,7 @@ const SCREENS = { CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency', + WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET: 'Workspace_Workflows_Auto_Reporting_Monthly_Offset', DESCRIPTION: 'Workspace_Profile_Description', SHARE: 'Workspace_Profile_Share', NAME: 'Workspace_Profile_Name', diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 79a4bb84e207..943901bcadea 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -244,6 +244,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: { path: ROUTES.WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY.route, }, + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: { + path: ROUTES.WORKSPACE_WORKFLOWS_AUTOREPORTING_MONTHLY_OFFSET.route, + }, [SCREENS.WORKSPACE.SHARE]: { path: ROUTES.WORKSPACE_PROFILE_SHARE.route, }, From 5ed5401c5fbdd1aeb90c17b64252fca7569aba3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 13:38:33 +0100 Subject: [PATCH 18/39] add api parameters for monthly offset --- .../SetWorkspaceAutoReportingMonthlyOffsetParams.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/libs/API/parameters/SetWorkspaceAutoReportingMonthlyOffsetParams.ts diff --git a/src/libs/API/parameters/SetWorkspaceAutoReportingMonthlyOffsetParams.ts b/src/libs/API/parameters/SetWorkspaceAutoReportingMonthlyOffsetParams.ts new file mode 100644 index 000000000000..d8c3d252dfc2 --- /dev/null +++ b/src/libs/API/parameters/SetWorkspaceAutoReportingMonthlyOffsetParams.ts @@ -0,0 +1,6 @@ +type SetWorkspaceAutoReportingMonthlyOffsetParams = { + policyID: string; + value: string; +}; + +export default SetWorkspaceAutoReportingMonthlyOffsetParams; From d054157f93ee99a84964ddd995b2bbee3dd5b756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 13:40:20 +0100 Subject: [PATCH 19/39] add monthly offset api params command --- src/libs/API/parameters/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 135df520188a..77bd8b6f2fbd 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -147,5 +147,6 @@ export type {default as AcceptACHContractForBankAccount} from './AcceptACHContra export type {default as UpdateWorkspaceDescriptionParams} from './UpdateWorkspaceDescriptionParams'; export type {default as SetWorkspaceAutoReportingParams} from './SetWorkspaceAutoReportingParams'; export type {default as SetWorkspaceAutoReportingFrequencyParams} from './SetWorkspaceAutoReportingFrequencyParams'; +export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './SetWorkspaceAutoReportingMonthlyOffsetParams'; export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams'; export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams'; From d5cd3fa483ec9c4c26263226c2d4a299e038e537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 13:41:33 +0100 Subject: [PATCH 20/39] handle auto reporting monthly offset action --- src/libs/API/types.ts | 2 ++ src/libs/actions/Policy.ts | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 322e3ad79d6c..c1dc4c2805a3 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -10,6 +10,7 @@ type ApiRequest = ValueOf; const WRITE_COMMANDS = { SET_WORKSPACE_AUTO_REPORTING: 'SetWorkspaceAutoReporting', SET_WORKSPACE_AUTO_REPORTING_FREQUENCY: 'SetWorkspaceAutoReportingFrequency', + SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET: 'UpdatePolicy', SET_WORKSPACE_APPROVAL_MODE: 'SetWorkspaceApprovalMode', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', @@ -298,6 +299,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_WORKSPACE_DESCRIPTION]: Parameters.UpdateWorkspaceDescriptionParams; [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING]: Parameters.SetWorkspaceAutoReportingParams; [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_FREQUENCY]: Parameters.SetWorkspaceAutoReportingFrequencyParams; + [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET]: Parameters.SetWorkspaceAutoReportingMonthlyOffsetParams; [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index b1c9321b6a3c..3d8f06f5f5f2 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -22,6 +22,7 @@ import type { OpenWorkspaceReimburseViewParams, SetWorkspaceApprovalModeParams, SetWorkspaceAutoReportingFrequencyParams, + SetWorkspaceAutoReportingMonthlyOffsetParams, SetWorkspaceAutoReportingParams, UpdateWorkspaceAvatarParams, UpdateWorkspaceCustomUnitAndRateParams, @@ -458,6 +459,44 @@ function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf API.write(WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_FREQUENCY, params, {optimisticData, failureData, successData}); } +function setWorkspaceAutoReportingMonthlyOffset(policyID: string, autoReportingOffset: number | ValueOf) { + const value = JSON.stringify({autoReportingOffset: autoReportingOffset.toString()}); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoReportingOffset, + pendingFields: {autoReportingOffset: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: {autoReportingOffset: null}, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: {autoReportingOffset: null}, + }, + }, + ]; + + const params: SetWorkspaceAutoReportingMonthlyOffsetParams = {policyID, value}; + API.write(WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET, params, {optimisticData, failureData, successData}); +} + function setWorkspaceApprovalMode(policyID: string, approver: string, approvalMode: ValueOf) { const isAutoApprovalEnabled = approvalMode === CONST.POLICY.APPROVAL_MODE.BASIC; @@ -2258,5 +2297,6 @@ export { setWorkspaceAutoReporting, setWorkspaceApprovalMode, setWorkspaceAutoReportingFrequency, + setWorkspaceAutoReportingMonthlyOffset, updateWorkspaceDescription, }; From c185ee89de62371c763b15a2c6ead6a4a774a8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 13:42:00 +0100 Subject: [PATCH 21/39] add autoreporting monthly offset to nav --- src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/types.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 26de5c4558dc..20c2d59adf7c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -257,6 +257,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/ExitSurvey/ExitSurveyResponsePage').default as React.ComponentType, [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: () => require('../../../pages/settings/ExitSurvey/ExitSurveyConfirmPage').default as React.ComponentType, [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType, + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: () => require('../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 68820226059b..8c26d7cc61bd 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -66,6 +66,9 @@ type CentralPaneNavigatorParamList = { [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: { policyID: string; }; + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: { + policyID: string; + }; [SCREENS.WORKSPACE.REIMBURSE]: { policyID: string; }; From 8d74425979c185a57191f0ed76621673d06fd2d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 13:42:16 +0100 Subject: [PATCH 22/39] autoreporting monthly offset page --- ...orkspaceAutoReportingMonthlyOffsetPage.tsx | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx new file mode 100644 index 000000000000..5a296c720475 --- /dev/null +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx @@ -0,0 +1,110 @@ +import React, {useState} from 'react'; +import type {ValueOf} from 'type-fest'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useLocalize from '@hooks/useLocalize'; +import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import withPolicy from '@pages/workspace/withPolicy'; +import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; +import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +const DAYS_OF_MONTH = 28; + +type WorkspaceAutoReportingMonthlyOffsetProps = WithPolicyOnyxProps; + +type AutoReportingOffsetKeys = ValueOf; + +type WorkspaceAutoReportingMonthlyOffsetPageItem = { + text: string; + keyForList: string; + isSelected: boolean; + isNumber?: boolean; +}; + +function WorkspaceAutoReportingMonthlyOffsetPage({policy}: WorkspaceAutoReportingMonthlyOffsetProps) { + const {translate} = useLocalize(); + const offset = policy?.autoReportingOffset ?? 0; + const [searchText, setSearchText] = useState(''); + const trimmedText = searchText.trim().toLowerCase(); + + const daysOfMonth: WorkspaceAutoReportingMonthlyOffsetPageItem[] = Array.from({length: DAYS_OF_MONTH}, (value, index) => { + const day = index + 1; + let suffix = 'th'; + if (day === 1 || day === 21) { + suffix = 'st'; + } else if (day === 2 || day === 22) { + suffix = 'nd'; + } else if (day === 3 || day === 23) { + suffix = 'rd'; + } + + return { + text: `${day}${suffix}`, + keyForList: day.toString(), // we have to cast it as string for to work + isSelected: day === offset, + isNumber: true, + }; + }).concat([ + { + keyForList: 'lastDayOfMonth', + text: translate('workflowsPage.frequencies.lastDayOfMonth'), + isSelected: offset === CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_DAY_OF_MONTH, + isNumber: false, + }, + { + keyForList: 'lastBusinessDayOfMonth', + text: translate('workflowsPage.frequencies.lastBusinessDayOfMonth'), + isSelected: offset === CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_BUSINESS_DAY_OF_MONTH, + isNumber: false, + }, + ]); + + const filteredDaysOfMonth = daysOfMonth.filter((dayItem) => dayItem.text.toLowerCase().includes(trimmedText)); + + const headerMessage = searchText.trim() && !filteredDaysOfMonth.length ? translate('common.noResultsFound') : ''; + + const onSelectDayOfMonth = (item: WorkspaceAutoReportingMonthlyOffsetPageItem) => { + Policy.setWorkspaceAutoReportingMonthlyOffset(policy?.id ?? '', item.isNumber ? parseInt(item.keyForList, 10) : (item.keyForList as AutoReportingOffsetKeys)); + Navigation.goBack(); + }; + + return ( + + + + + + + + ); +} + +WorkspaceAutoReportingMonthlyOffsetPage.displayName = 'WorkspaceAutoReportingMonthlyOffsetPage'; +export default withPolicy(WorkspaceAutoReportingMonthlyOffsetPage); From 60b718b1a2a6bd3e0121fe0154b9841884fb8e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 13:42:24 +0100 Subject: [PATCH 23/39] use autoreporting monthly offset page --- .../WorkspaceAutoReportingFrequencyPage.tsx | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index a53a5839d8e0..71ef1b635d46 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -13,16 +13,17 @@ import {translate as globalTranslate} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import withPolicy from '@pages/workspace/withPolicy'; -import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AutoReportingFrequencyKey = Exclude, 'instant'>; -type WorkspaceAutoReportingFrequencyPageProps = WithPolicyAndFullscreenLoadingProps; +type WorkspaceAutoReportingFrequencyPageProps = WithPolicyOnyxProps; -type WorkspaceAutoReportingFrequencyPageSectionItem = { +type WorkspaceAutoReportingFrequencyPageItem = { text: string; keyForList: string; isSelected: boolean; @@ -39,14 +40,14 @@ const getAutoReportingFrequencyDisplayNames = (locale: 'en' | 'es' | 'es-ES' | ' [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: globalTranslate(locale, 'workflowsPage.frequencies.manually'), }); -const DAYS_OF_MONTH = 28; +const FIRST_DAY_OF_MONTH = '1st'; function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFrequencyPageProps) { const {translate, preferredLocale} = useLocalize(); const styles = useThemeStyles(); const [isMonthlyFrequency, setIsMonthlyFrequency] = useState(policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY); - const autoReportingFrequencyItems: WorkspaceAutoReportingFrequencyPageSectionItem[] = Object.keys(getAutoReportingFrequencyDisplayNames(preferredLocale)).map((frequencyKey) => { + const autoReportingFrequencyItems: WorkspaceAutoReportingFrequencyPageItem[] = Object.keys(getAutoReportingFrequencyDisplayNames(preferredLocale)).map((frequencyKey) => { const isSelected = policy?.autoReportingFrequency === frequencyKey; return { @@ -56,7 +57,7 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre }; }); - const onSelectAutoReportingFrequency = (item: WorkspaceAutoReportingFrequencyPageSectionItem) => { + const onSelectAutoReportingFrequency = (item: WorkspaceAutoReportingFrequencyPageItem) => { Policy.setWorkspaceAutoReportingFrequency(policy?.id ?? '', item.keyForList as AutoReportingFrequencyKey); if (item.keyForList !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY) { setIsMonthlyFrequency(false); @@ -66,35 +67,31 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre } }; - // Generate days of month for the monthly frequency - const daysOfMonth = Array.from({length: DAYS_OF_MONTH}, (value, index) => { - const day = index + 1; - let suffix = 'th'; - if (day === 1 || day === 21) { - suffix = 'st'; - } else if (day === 2 || day === 22) { - suffix = 'nd'; - } else if (day === 3 || day === 23) { - suffix = 'rd'; + const getDescriptionText = () => { + if (policy?.autoReportingOffset === undefined) { + return FIRST_DAY_OF_MONTH; } - - return `${day}${suffix}`; - }).concat([translate('workflowsPage.frequencies.lastDayOfMonth'), translate('workflowsPage.frequencies.lastBusinessDayOfMonth')]); + if (typeof policy?.autoReportingOffset === 'number') { + return policy.autoReportingOffset.toString(); + } + return translate(`workflowsPage.frequencies.${policy?.autoReportingOffset}`); + }; const monthlyFrequencyDetails = () => ( Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_AUTOREPORTING_MONTHLY_OFFSET.getRoute(policy?.id ?? ''))} shouldShowRightIcon /> ); - const renderItem = ({item}: {item: WorkspaceAutoReportingFrequencyPageSectionItem}) => ( + const renderItem = ({item}: {item: WorkspaceAutoReportingFrequencyPageItem}) => ( <> item.text} + keyExtractor={(item: WorkspaceAutoReportingFrequencyPageItem) => item.text} /> From be2bbc1715bfe72421423a93060caad210106979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 14:04:09 +0100 Subject: [PATCH 24/39] handle edge case for manual auto reporting frequency --- src/libs/HttpUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index cf35cb6c4d29..b52630b2acc6 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -28,6 +28,9 @@ Onyx.connect({ // We use the AbortController API to terminate pending request in `cancelPendingRequests` let cancellationController = new AbortController(); +// Some existing old commands (6+ years) exempted from the auth writes count check +const exemptedCommandsWithAuthWrites: string[] = ['SetWorkspaceAutoReportingFrequency']; + /** * The API commands that require the skew calculation */ @@ -120,7 +123,8 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form title: CONST.ERROR_TITLE.SOCKET, }); } - if (response.jsonCode === CONST.JSON_CODE.MANY_WRITES_ERROR) { + + if (response.jsonCode === CONST.JSON_CODE.MANY_WRITES_ERROR && !exemptedCommandsWithAuthWrites.includes(response.data?.phpCommandName ?? '')) { if (response.data) { const {phpCommandName, authWriteCommands} = response.data; // eslint-disable-next-line max-len From aa27dd698aef339a76a7839fca8f439336cdb68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 15:08:37 +0100 Subject: [PATCH 25/39] remove unused ranslation key --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 8420ebe8d0ea..b789f3b1a622 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1036,7 +1036,6 @@ export default { delaySubmissionDescription: 'Expenses are shared right away for better spend visibility. Set a slower cadence if needed.', submissionFrequency: 'Submission frequency', submissionFrequencyDateOfMonth: 'Date of month', - submissionFrequencyDayOfMonth: 'Day of month', weeklyFrequency: 'Weekly', monthlyFrequency: 'Monthly', twiceAMonthFrequency: 'Twice a month', diff --git a/src/languages/es.ts b/src/languages/es.ts index 206c9747f21f..cb7f93aefd08 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1032,7 +1032,6 @@ export default { delaySubmissionDescription: 'Los gastos se comparten de inmediato para una mejor visibilidad del gasto. Establece una cadencia más lenta si es necesario.', submissionFrequency: 'Frecuencia de envíos', submissionFrequencyDateOfMonth: 'Fecha del mes', - submissionFrequencyDayOfMonth: 'Día del mes', weeklyFrequency: 'Semanal', monthlyFrequency: 'Mensual', twiceAMonthFrequency: 'Dos veces al mes', From 63c51680e6edd78fbc7de315262644237cea753c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 15:09:15 +0100 Subject: [PATCH 26/39] update translation used --- .../workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx index 5a296c720475..060b5adaee2b 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx @@ -92,7 +92,7 @@ function WorkspaceAutoReportingMonthlyOffsetPage({policy}: WorkspaceAutoReportin Date: Tue, 27 Feb 2024 15:09:30 +0100 Subject: [PATCH 27/39] fix ios display --- .../WorkspaceAutoReportingFrequencyPage.tsx | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index 71ef1b635d46..5d42b6b76cd2 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -72,7 +72,15 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre return FIRST_DAY_OF_MONTH; } if (typeof policy?.autoReportingOffset === 'number') { - return policy.autoReportingOffset.toString(); + let suffix = 'th'; + if (policy.autoReportingOffset === 1 || policy.autoReportingOffset === 21 || policy.autoReportingOffset === 31) { + suffix = 'st'; + } else if (policy.autoReportingOffset === 2 || policy.autoReportingOffset === 22) { + suffix = 'nd'; + } else if (policy.autoReportingOffset === 3 || policy.autoReportingOffset === 23) { + suffix = 'rd'; + } + return `${policy.autoReportingOffset}${suffix}`; } return translate(`workflowsPage.frequencies.${policy?.autoReportingOffset}`); }; @@ -103,33 +111,28 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre ); return ( - - - - - - item.text} - /> - - - + + + item.text} + /> + + ); } From 3506529a09bd24178cc37e168852aa52a8904592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 27 Feb 2024 16:52:59 +0100 Subject: [PATCH 28/39] fix workflows translations --- src/languages/en.ts | 6 ------ src/languages/es.ts | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index b789f3b1a622..e6f172b7c150 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1036,12 +1036,6 @@ export default { delaySubmissionDescription: 'Expenses are shared right away for better spend visibility. Set a slower cadence if needed.', submissionFrequency: 'Submission frequency', submissionFrequencyDateOfMonth: 'Date of month', - weeklyFrequency: 'Weekly', - monthlyFrequency: 'Monthly', - twiceAMonthFrequency: 'Twice a month', - byTripFrequency: 'By trip', - manuallyFrequency: 'Manually', - dailyFrequency: 'Daily', addApprovalsTitle: 'Add approvals', approver: 'Approver', connectBankAccount: 'Connect bank account', diff --git a/src/languages/es.ts b/src/languages/es.ts index cb7f93aefd08..5015428eb700 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1032,12 +1032,6 @@ export default { delaySubmissionDescription: 'Los gastos se comparten de inmediato para una mejor visibilidad del gasto. Establece una cadencia más lenta si es necesario.', submissionFrequency: 'Frecuencia de envíos', submissionFrequencyDateOfMonth: 'Fecha del mes', - weeklyFrequency: 'Semanal', - monthlyFrequency: 'Mensual', - twiceAMonthFrequency: 'Dos veces al mes', - byTripFrequency: 'Por viaje', - manuallyFrequency: 'Manual', - dailyFrequency: 'Diaria', addApprovalsTitle: 'Requerir aprobaciones', approver: 'Aprobador', connectBankAccount: 'Conectar cuenta bancaria', @@ -1053,7 +1047,7 @@ export default { twiceAMonth: 'Dos veces al mes', byTrip: 'Por viaje', manually: 'Manualmente', - daily: 'Diario', + daily: 'Diaria', lastDayOfMonth: 'Último día del mes', lastBusinessDayOfMonth: 'Último día hábil del mes', }, From 4220301fb44179a196903c1a328b2c1018d9d83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 28 Feb 2024 08:49:25 +0100 Subject: [PATCH 29/39] fix path for autoreporting routes --- src/ROUTES.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b0df084045f4..8d20c521ce0d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -487,12 +487,12 @@ const ROUTES = { getRoute: (policyID: string) => `workspace/${policyID}/workflows` as const, }, WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY: { - route: 'workspace/:policyID/workflows/autoreporting/frequency', - getRoute: (policyID: string) => `workspace/${policyID}/workflows/autoreporting/frequency` as const, + route: 'workspace/:policyID/settings/workflows/auto-reporting-frequency', + getRoute: (policyID: string) => `workspace/${policyID}/workflows/auto-reporting-frequency` as const, }, WORKSPACE_WORKFLOWS_AUTOREPORTING_MONTHLY_OFFSET: { - route: 'workspace/:policyID/workflows/autoreporting/frequency/monthly-offset', - getRoute: (policyID: string) => `workspace/${policyID}/workflows/autoreporting/frequency/monthly-offset` as const, + route: 'workspace/:policyID/settings/workflows/auto-reporting-frequency/monthly-offset', + getRoute: (policyID: string) => `workspace/${policyID}/workflows/auto-reporting-frequency/monthly-offset` as const, }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', From 16c1de749c2ee6679b4adf02d0f48500de6d013e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 28 Feb 2024 10:04:21 +0100 Subject: [PATCH 30/39] fix path --- src/ROUTES.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 8d20c521ce0d..71da8a44f82d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -488,11 +488,11 @@ const ROUTES = { }, WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY: { route: 'workspace/:policyID/settings/workflows/auto-reporting-frequency', - getRoute: (policyID: string) => `workspace/${policyID}/workflows/auto-reporting-frequency` as const, + getRoute: (policyID: string) => `workspace/${policyID}/settings/workflows/auto-reporting-frequency` as const, }, WORKSPACE_WORKFLOWS_AUTOREPORTING_MONTHLY_OFFSET: { route: 'workspace/:policyID/settings/workflows/auto-reporting-frequency/monthly-offset', - getRoute: (policyID: string) => `workspace/${policyID}/workflows/auto-reporting-frequency/monthly-offset` as const, + getRoute: (policyID: string) => `workspace/${policyID}/settings/workflows/auto-reporting-frequency/monthly-offset` as const, }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', From 11338566f930292fa54479950d432a1acfd99d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 28 Feb 2024 10:04:48 +0100 Subject: [PATCH 31/39] add new function for locale ordinal --- src/libs/Localize/index.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 64d07897aa8a..878109b0a674 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -144,6 +144,37 @@ function getPreferredListFormat(): Intl.ListFormat { return CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()]; } +/** + * Formats a number into its localized ordinal representation i.e 1st, 2nd etc + */ +function toLocaleOrdinal(locale: Locale, number: number): string { + const formatter = new Intl.PluralRules(locale, {type: 'ordinal'}); + const rule = formatter.select(number); + + const suffixes: Record> = { + en: { + one: 'st', + two: 'nd', + few: 'rd', + other: 'th', + }, + es: { + one: '.º', + two: '.º', + few: '.º', + other: '.º', + }, + }; + + const lang = locale.substring(0, 2); + + const languageSuffixes = suffixes[lang] || suffixes.en; + + const suffix = languageSuffixes[rule] || languageSuffixes.other; + + return `${number}${suffix}`; +} + /** * Format an array into a string with comma and "and" ("a dog, a cat and a chicken") */ @@ -187,5 +218,5 @@ function getDevicePreferredLocale(): Locale { return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } -export {translatableTextPropTypes, translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale}; +export {translatableTextPropTypes, translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale, toLocaleOrdinal}; export type {PhraseParameters, Phrase, MaybePhraseKey}; From 38152585a6c32f71abf814003d69c0dcc939a814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 28 Feb 2024 10:05:37 +0100 Subject: [PATCH 32/39] localize suffixes --- .../WorkspaceAutoReportingFrequencyPage.tsx | 42 ++++++++----------- ...orkspaceAutoReportingMonthlyOffsetPage.tsx | 13 ++---- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index 5d42b6b76cd2..4d58fc60e926 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -9,7 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import {translate as globalTranslate} from '@libs/Localize'; +import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import withPolicy from '@pages/workspace/withPolicy'; @@ -20,6 +20,7 @@ import ROUTES from '@src/ROUTES'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AutoReportingFrequencyKey = Exclude, 'instant'>; +type Locale = ValueOf; type WorkspaceAutoReportingFrequencyPageProps = WithPolicyOnyxProps; @@ -31,17 +32,15 @@ type WorkspaceAutoReportingFrequencyPageItem = { type AutoReportingFrequencyDisplayNames = Record; -const getAutoReportingFrequencyDisplayNames = (locale: 'en' | 'es' | 'es-ES' | 'es_ES'): AutoReportingFrequencyDisplayNames => ({ - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY]: globalTranslate(locale, 'workflowsPage.frequencies.monthly'), - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE]: globalTranslate(locale, 'workflowsPage.frequencies.daily'), - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]: globalTranslate(locale, 'workflowsPage.frequencies.weekly'), - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY]: globalTranslate(locale, 'workflowsPage.frequencies.twiceAMonth'), - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: globalTranslate(locale, 'workflowsPage.frequencies.byTrip'), - [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: globalTranslate(locale, 'workflowsPage.frequencies.manually'), +const getAutoReportingFrequencyDisplayNames = (locale: Locale): AutoReportingFrequencyDisplayNames => ({ + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY]: Localize.translate(locale, 'workflowsPage.frequencies.monthly'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE]: Localize.translate(locale, 'workflowsPage.frequencies.daily'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]: Localize.translate(locale, 'workflowsPage.frequencies.weekly'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY]: Localize.translate(locale, 'workflowsPage.frequencies.twiceAMonth'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: Localize.translate(locale, 'workflowsPage.frequencies.byTrip'), + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: Localize.translate(locale, 'workflowsPage.frequencies.manually'), }); -const FIRST_DAY_OF_MONTH = '1st'; - function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFrequencyPageProps) { const {translate, preferredLocale} = useLocalize(); const styles = useThemeStyles(); @@ -59,29 +58,24 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre const onSelectAutoReportingFrequency = (item: WorkspaceAutoReportingFrequencyPageItem) => { Policy.setWorkspaceAutoReportingFrequency(policy?.id ?? '', item.keyForList as AutoReportingFrequencyKey); - if (item.keyForList !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY) { - setIsMonthlyFrequency(false); - Navigation.goBack(); - } else { + + if (item.keyForList === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY) { setIsMonthlyFrequency(true); + return; } + + setIsMonthlyFrequency(false); + Navigation.goBack(); }; const getDescriptionText = () => { if (policy?.autoReportingOffset === undefined) { - return FIRST_DAY_OF_MONTH; + return Localize.toLocaleOrdinal(preferredLocale, 1); } if (typeof policy?.autoReportingOffset === 'number') { - let suffix = 'th'; - if (policy.autoReportingOffset === 1 || policy.autoReportingOffset === 21 || policy.autoReportingOffset === 31) { - suffix = 'st'; - } else if (policy.autoReportingOffset === 2 || policy.autoReportingOffset === 22) { - suffix = 'nd'; - } else if (policy.autoReportingOffset === 3 || policy.autoReportingOffset === 23) { - suffix = 'rd'; - } - return `${policy.autoReportingOffset}${suffix}`; + return Localize.toLocaleOrdinal(preferredLocale, policy.autoReportingOffset); } + return translate(`workflowsPage.frequencies.${policy?.autoReportingOffset}`); }; diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx index 060b5adaee2b..793055979727 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx @@ -6,6 +6,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; +import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import withPolicy from '@pages/workspace/withPolicy'; @@ -28,24 +29,16 @@ type WorkspaceAutoReportingMonthlyOffsetPageItem = { }; function WorkspaceAutoReportingMonthlyOffsetPage({policy}: WorkspaceAutoReportingMonthlyOffsetProps) { - const {translate} = useLocalize(); + const {translate, preferredLocale} = useLocalize(); const offset = policy?.autoReportingOffset ?? 0; const [searchText, setSearchText] = useState(''); const trimmedText = searchText.trim().toLowerCase(); const daysOfMonth: WorkspaceAutoReportingMonthlyOffsetPageItem[] = Array.from({length: DAYS_OF_MONTH}, (value, index) => { const day = index + 1; - let suffix = 'th'; - if (day === 1 || day === 21) { - suffix = 'st'; - } else if (day === 2 || day === 22) { - suffix = 'nd'; - } else if (day === 3 || day === 23) { - suffix = 'rd'; - } return { - text: `${day}${suffix}`, + text: Localize.toLocaleOrdinal(preferredLocale, day), keyForList: day.toString(), // we have to cast it as string for to work isSelected: day === offset, isNumber: true, From d74e99946ade5f701f06009737dca894db7b0496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 28 Feb 2024 10:13:17 +0100 Subject: [PATCH 33/39] port toLocaleOrdinal to LocaleContextProvider --- src/components/LocaleContextProvider.tsx | 9 ++++- src/libs/LocaleDigitUtils.ts | 33 ++++++++++++++++++- src/libs/Localize/index.ts | 33 +------------------ .../WorkspaceAutoReportingFrequencyPage.tsx | 6 ++-- ...orkspaceAutoReportingMonthlyOffsetPage.tsx | 5 ++- 5 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx index 7313bb4aa7bb..96aeec7bcb6a 100644 --- a/src/components/LocaleContextProvider.tsx +++ b/src/components/LocaleContextProvider.tsx @@ -50,6 +50,9 @@ type LocaleContextProps = { /** Gets the locale digit corresponding to a standard digit */ toLocaleDigit: (digit: string) => string; + /** Formats a number into its localized ordinal representation */ + toLocaleOrdinal: (number: number) => string; + /** Gets the standard digit corresponding to a locale digit */ fromLocaleDigit: (digit: string) => string; @@ -65,6 +68,7 @@ const LocaleContext = createContext({ updateLocale: () => '', formatPhoneNumber: () => '', toLocaleDigit: () => '', + toLocaleOrdinal: () => '', fromLocaleDigit: () => '', preferredLocale: CONST.LOCALES.DEFAULT, }); @@ -98,6 +102,8 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails = {} const toLocaleDigit = useMemo(() => (digit) => LocaleDigitUtils.toLocaleDigit(locale, digit), [locale]); + const toLocaleOrdinal = useMemo(() => (number) => LocaleDigitUtils.toLocaleOrdinal(locale, number), [locale]); + const fromLocaleDigit = useMemo(() => (localeDigit) => LocaleDigitUtils.fromLocaleDigit(locale, localeDigit), [locale]); const contextValue = useMemo( @@ -109,10 +115,11 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails = {} updateLocale, formatPhoneNumber, toLocaleDigit, + toLocaleOrdinal, fromLocaleDigit, preferredLocale: locale, }), - [translate, numberFormat, datetimeToRelative, datetimeToCalendarTime, updateLocale, formatPhoneNumber, toLocaleDigit, fromLocaleDigit, locale], + [translate, numberFormat, datetimeToRelative, datetimeToCalendarTime, updateLocale, formatPhoneNumber, toLocaleDigit, toLocaleOrdinal, fromLocaleDigit, locale], ); return {children}; diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts index 794c7611cb5c..ae1a1e4d2dd1 100644 --- a/src/libs/LocaleDigitUtils.ts +++ b/src/libs/LocaleDigitUtils.ts @@ -66,4 +66,35 @@ function fromLocaleDigit(locale: Locale, localeDigit: string): string { return STANDARD_DIGITS[index]; } -export {toLocaleDigit, fromLocaleDigit}; +/** + * Formats a number into its localized ordinal representation i.e 1st, 2nd etc + */ +function toLocaleOrdinal(locale: Locale, number: number): string { + const formatter = new Intl.PluralRules(locale, {type: 'ordinal'}); + const rule = formatter.select(number); + + const suffixes: Record> = { + en: { + one: 'st', + two: 'nd', + few: 'rd', + other: 'th', + }, + es: { + one: '.º', + two: '.º', + few: '.º', + other: '.º', + }, + }; + + const lang = locale.substring(0, 2); + + const languageSuffixes = suffixes[lang] || suffixes.en; + + const suffix = languageSuffixes[rule] || languageSuffixes.other; + + return `${number}${suffix}`; +} + +export {toLocaleDigit, toLocaleOrdinal, fromLocaleDigit}; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 878109b0a674..64d07897aa8a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -144,37 +144,6 @@ function getPreferredListFormat(): Intl.ListFormat { return CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()]; } -/** - * Formats a number into its localized ordinal representation i.e 1st, 2nd etc - */ -function toLocaleOrdinal(locale: Locale, number: number): string { - const formatter = new Intl.PluralRules(locale, {type: 'ordinal'}); - const rule = formatter.select(number); - - const suffixes: Record> = { - en: { - one: 'st', - two: 'nd', - few: 'rd', - other: 'th', - }, - es: { - one: '.º', - two: '.º', - few: '.º', - other: '.º', - }, - }; - - const lang = locale.substring(0, 2); - - const languageSuffixes = suffixes[lang] || suffixes.en; - - const suffix = languageSuffixes[rule] || languageSuffixes.other; - - return `${number}${suffix}`; -} - /** * Format an array into a string with comma and "and" ("a dog, a cat and a chicken") */ @@ -218,5 +187,5 @@ function getDevicePreferredLocale(): Locale { return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } -export {translatableTextPropTypes, translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale, toLocaleOrdinal}; +export {translatableTextPropTypes, translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale}; export type {PhraseParameters, Phrase, MaybePhraseKey}; diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index 4d58fc60e926..cf66af726a72 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -42,7 +42,7 @@ const getAutoReportingFrequencyDisplayNames = (locale: Locale): AutoReportingFre }); function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFrequencyPageProps) { - const {translate, preferredLocale} = useLocalize(); + const {translate, preferredLocale, toLocaleOrdinal} = useLocalize(); const styles = useThemeStyles(); const [isMonthlyFrequency, setIsMonthlyFrequency] = useState(policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY); @@ -70,10 +70,10 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre const getDescriptionText = () => { if (policy?.autoReportingOffset === undefined) { - return Localize.toLocaleOrdinal(preferredLocale, 1); + return toLocaleOrdinal(1); } if (typeof policy?.autoReportingOffset === 'number') { - return Localize.toLocaleOrdinal(preferredLocale, policy.autoReportingOffset); + return toLocaleOrdinal(policy.autoReportingOffset); } return translate(`workflowsPage.frequencies.${policy?.autoReportingOffset}`); diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx index 793055979727..84d70e799c42 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx @@ -6,7 +6,6 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; -import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import withPolicy from '@pages/workspace/withPolicy'; @@ -29,7 +28,7 @@ type WorkspaceAutoReportingMonthlyOffsetPageItem = { }; function WorkspaceAutoReportingMonthlyOffsetPage({policy}: WorkspaceAutoReportingMonthlyOffsetProps) { - const {translate, preferredLocale} = useLocalize(); + const {translate, toLocaleOrdinal} = useLocalize(); const offset = policy?.autoReportingOffset ?? 0; const [searchText, setSearchText] = useState(''); const trimmedText = searchText.trim().toLowerCase(); @@ -38,7 +37,7 @@ function WorkspaceAutoReportingMonthlyOffsetPage({policy}: WorkspaceAutoReportin const day = index + 1; return { - text: Localize.toLocaleOrdinal(preferredLocale, day), + text: toLocaleOrdinal(day), keyForList: day.toString(), // we have to cast it as string for to work isSelected: day === offset, isNumber: true, From 834399d5662ecc407205b2eaf21545c1042404d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 28 Feb 2024 18:12:10 +0100 Subject: [PATCH 34/39] add workflows delayed submission beta --- src/CONST.ts | 1 + src/libs/Permissions.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 8abd4c087b16..8679bfe4aa1d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -307,6 +307,7 @@ const CONST = { BETA_COMMENT_LINKING: 'commentLinking', VIOLATIONS: 'violations', REPORT_FIELDS: 'reportFields', + WORKFLOWS_DELAYED_SUBMISSION: 'workflowsDelayedSubmission', }, BUTTON_STATES: { DEFAULT: 'default', diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index ce5e0e674c59..c9f386f5bd7a 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -26,6 +26,10 @@ function canUseViolations(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.VIOLATIONS) || canUseAllBetas(betas); } +function canUseWorkflowsDelayedSubmission(betas: OnyxEntry): boolean { + return !!betas?.includes(CONST.BETAS.WORKFLOWS_DELAYED_SUBMISSION) || canUseAllBetas(betas); +} + /** * Link previews are temporarily disabled. */ @@ -40,4 +44,5 @@ export default { canUseLinkPreviews, canUseViolations, canUseReportFields, + canUseWorkflowsDelayedSubmission, }; From dc3e97a829b9325a372081d736f0079a495eca7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 28 Feb 2024 18:12:52 +0100 Subject: [PATCH 35/39] handle beta for delayed submission --- .../workflows/WorkspaceWorkflowsPage.tsx | 94 +++++++++++++------ 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 527d4db54843..d9974ed193be 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -1,6 +1,8 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useMemo} from 'react'; import {FlatList, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import * as Illustrations from '@components/Icon/Illustrations'; import MenuItem from '@components/MenuItem'; import Section from '@components/Section'; @@ -12,24 +14,31 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import Permissions from '@libs/Permissions'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; -import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import withPolicy from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type {Beta} from '@src/types/onyx'; import ToggleSettingOptionRow from './ToggleSettingsOptionRow'; import type {ToggleSettingOptionRowProps} from './ToggleSettingsOptionRow'; import {getAutoReportingFrequencyDisplayNames} from './WorkspaceAutoReportingFrequencyPage'; import type {AutoReportingFrequencyKey} from './WorkspaceAutoReportingFrequencyPage'; -type WorkspaceWorkflowsPageProps = WithPolicyProps & StackScreenProps; +type WorkspaceWorkflowsPageOnyxProps = { + /** Beta features list */ + betas: OnyxEntry; +}; +type WorkspaceWorkflowsPageProps = WithPolicyProps & WorkspaceWorkflowsPageOnyxProps & StackScreenProps; -function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { +function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPageProps) { const {translate, preferredLocale} = useLocalize(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -39,37 +48,42 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { const ownerPersonalDetails = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs([policy?.ownerAccountID ?? 0], CONST.EMPTY_OBJECT), false); const policyOwnerDisplayName = ownerPersonalDetails[0]?.displayName; const containerStyle = useMemo(() => [styles.ph8, styles.mhn8, styles.ml11, styles.pv3, styles.pr0, styles.pl4, styles.mr0, styles.widthAuto, styles.mt4], [styles]); + const canUseDelayedSubmission = Permissions.canUseWorkflowsDelayedSubmission(betas); const onPressAutoReportingFrequency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY.getRoute(policy?.id ?? '')), [policy?.id]); const items: ToggleSettingOptionRowProps[] = useMemo( () => [ - { - icon: Illustrations.ReceiptEnvelope, - title: translate('workflowsPage.delaySubmissionTitle'), - subtitle: translate('workflowsPage.delaySubmissionDescription'), - onToggle: (isEnabled: boolean) => { - Policy.setWorkspaceAutoReporting(route.params.policyID, isEnabled); - }, - subMenuItems: ( - - ), - isActive: policy?.harvesting?.enabled ?? false, - pendingAction: policy?.pendingFields?.isAutoApprovalEnabled, - }, + ...(canUseDelayedSubmission + ? [ + { + icon: Illustrations.ReceiptEnvelope, + title: translate('workflowsPage.delaySubmissionTitle'), + subtitle: translate('workflowsPage.delaySubmissionDescription'), + onToggle: (isEnabled: boolean) => { + Policy.setWorkspaceAutoReporting(route.params.policyID, isEnabled); + }, + subMenuItems: ( + + ), + isActive: policy?.harvesting?.enabled ?? false, + pendingAction: policy?.pendingFields?.isAutoApprovalEnabled, + }, + ] + : []), { icon: Illustrations.Approval, title: translate('workflowsPage.addApprovalsTitle'), @@ -117,7 +131,19 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { isActive: false, // TODO will be done in https://github.com/Expensify/Expensify/issues/368335 }, ], - [policy, route.params.policyID, styles, translate, policyOwnerDisplayName, containerStyle, isOffline, StyleUtils, onPressAutoReportingFrequency, preferredLocale], + [ + policy, + route.params.policyID, + styles, + translate, + policyOwnerDisplayName, + containerStyle, + isOffline, + StyleUtils, + onPressAutoReportingFrequency, + preferredLocale, + canUseDelayedSubmission, + ], ); const renderItem = ({item}: {item: ToggleSettingOptionRowProps}) => ( @@ -168,4 +194,10 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { WorkspaceWorkflowsPage.displayName = 'WorkspaceWorkflowsPage'; -export default withPolicy(WorkspaceWorkflowsPage); +export default withPolicy( + withOnyx({ + betas: { + key: ONYXKEYS.BETAS, + }, + })(WorkspaceWorkflowsPage), +); From a2d6f18a75f0c32d34226146add92b11907ec662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 29 Feb 2024 08:27:10 +0100 Subject: [PATCH 36/39] add ordinal translations --- src/languages/en.ts | 6 ++++++ src/languages/es.ts | 6 ++++++ src/libs/LocaleDigitUtils.ts | 17 +++++++++-------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index e6f172b7c150..2c81ac75da30 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1054,6 +1054,12 @@ export default { daily: 'Daily', lastDayOfMonth: 'Last day of the month', lastBusinessDayOfMonth: 'Last business day of the month', + ordinals:{ + one: 'st', + two: 'nd', + few: 'rd', + other: 'th', + }, }, }, reportFraudPage: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 5015428eb700..6bc0aa1c6507 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1050,6 +1050,12 @@ export default { daily: 'Diaria', lastDayOfMonth: 'Último día del mes', lastBusinessDayOfMonth: 'Último día hábil del mes', + ordinals:{ + one: '.º', + two: '.º', + few: '.º', + other: '.º', + }, }, }, reportFraudPage: { diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts index ae1a1e4d2dd1..9f7102736546 100644 --- a/src/libs/LocaleDigitUtils.ts +++ b/src/libs/LocaleDigitUtils.ts @@ -1,6 +1,7 @@ import _ from 'lodash'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +import * as Localize from './Localize'; import * as NumberFormatUtils from './NumberFormatUtils'; type Locale = ValueOf; @@ -75,16 +76,16 @@ function toLocaleOrdinal(locale: Locale, number: number): string { const suffixes: Record> = { en: { - one: 'st', - two: 'nd', - few: 'rd', - other: 'th', + one: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.one'), + two: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.two'), + few: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.few'), + other: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.other'), }, es: { - one: '.º', - two: '.º', - few: '.º', - other: '.º', + one: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.one'), + two: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.two'), + few: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.few'), + other: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.other'), }, }; From 82e39f7b5d647a10743735629728381858e7f15a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 29 Feb 2024 10:35:18 +0100 Subject: [PATCH 37/39] fix prettier --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 2c81ac75da30..da9b09711efe 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1054,7 +1054,7 @@ export default { daily: 'Daily', lastDayOfMonth: 'Last day of the month', lastBusinessDayOfMonth: 'Last business day of the month', - ordinals:{ + ordinals: { one: 'st', two: 'nd', few: 'rd', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6bc0aa1c6507..70acfe8d2c79 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1050,7 +1050,7 @@ export default { daily: 'Diaria', lastDayOfMonth: 'Último día del mes', lastBusinessDayOfMonth: 'Último día hábil del mes', - ordinals:{ + ordinals: { one: '.º', two: '.º', few: '.º', From 4af4334e15db34bc70a9d76fd91ae38c58fe52ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 1 Mar 2024 10:38:16 +0100 Subject: [PATCH 38/39] fix error on iOS --- src/libs/LocaleDigitUtils.ts | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts index 9f7102736546..cf31ecff22ff 100644 --- a/src/libs/LocaleDigitUtils.ts +++ b/src/libs/LocaleDigitUtils.ts @@ -71,29 +71,25 @@ function fromLocaleDigit(locale: Locale, localeDigit: string): string { * Formats a number into its localized ordinal representation i.e 1st, 2nd etc */ function toLocaleOrdinal(locale: Locale, number: number): string { - const formatter = new Intl.PluralRules(locale, {type: 'ordinal'}); - const rule = formatter.select(number); - const suffixes: Record> = { - en: { - one: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.one'), - two: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.two'), - few: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.few'), - other: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.other'), - }, - es: { - one: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.one'), - two: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.two'), - few: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.few'), - other: Localize.translate(locale, 'workflowsPage.frequencies.ordinals.other'), - }, - }; + // Defaults to "other" suffix or "th" in English + let suffixKey = 'workflowsPage.frequencies.ordinals.other'; - const lang = locale.substring(0, 2); + // Calculate last digit of the number to determine basic ordinality + const lastDigit = number % 10; - const languageSuffixes = suffixes[lang] || suffixes.en; + // Calculate last two digits to handle exceptions in the 11-13 range + const lastTwoDigits = number % 100; - const suffix = languageSuffixes[rule] || languageSuffixes.other; + if (lastDigit === 1 && lastTwoDigits !== 11) { + suffixKey = 'workflowsPage.frequencies.ordinals.one'; + } else if (lastDigit === 2 && lastTwoDigits !== 12) { + suffixKey = 'workflowsPage.frequencies.ordinals.two'; + } else if (lastDigit === 3 && lastTwoDigits !== 13) { + suffixKey = 'workflowsPage.frequencies.ordinals.few'; + } + + const suffix = Localize.translate(locale, suffixKey); return `${number}${suffix}`; } From 94c1824228b8a257514df1fd1908da0dff4e17aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 1 Mar 2024 10:48:19 +0100 Subject: [PATCH 39/39] fix typecheck --- src/libs/LocaleDigitUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts index cf31ecff22ff..156e58c59033 100644 --- a/src/libs/LocaleDigitUtils.ts +++ b/src/libs/LocaleDigitUtils.ts @@ -1,6 +1,7 @@ import _ from 'lodash'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import * as Localize from './Localize'; import * as NumberFormatUtils from './NumberFormatUtils'; @@ -71,7 +72,6 @@ function fromLocaleDigit(locale: Locale, localeDigit: string): string { * Formats a number into its localized ordinal representation i.e 1st, 2nd etc */ function toLocaleOrdinal(locale: Locale, number: number): string { - // Defaults to "other" suffix or "th" in English let suffixKey = 'workflowsPage.frequencies.ordinals.other'; @@ -89,7 +89,7 @@ function toLocaleOrdinal(locale: Locale, number: number): string { suffixKey = 'workflowsPage.frequencies.ordinals.few'; } - const suffix = Localize.translate(locale, suffixKey); + const suffix = Localize.translate(locale, suffixKey as TranslationPaths); return `${number}${suffix}`; }