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/56] 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/56] 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/56] 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/56] 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/56] 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 b08fbc9d592afa4d27ca24011be101ca13c3dac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 26 Feb 2024 13:53:44 +0100 Subject: [PATCH 06/56] Fix sharing video player from chat to attachment modal --- ios/Podfile.lock | 4 ++-- .../AttachmentCarousel/CarouselItem.js | 1 + src/components/VideoPlayer/BaseVideoPlayer.js | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 52c817c739b3..82ffcd71c92d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1999,8 +1999,8 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 0a6794d1974aed5d653d0d0cb900493e2583e35a - Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 + Yoga: 13c8ef87792450193e117976337b8527b49e8c03 PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2 -COCOAPODS: 1.13.0 +COCOAPODS: 1.14.3 diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index edc8ab11fd27..3b2aa454c2a2 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -108,6 +108,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) { isHovered={isModalHovered} isFocused={isFocused} optionalVideoDuration={item.duration} + isUsedInCarousel /> diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index df79c7ef18da..321c765b192d 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -8,7 +8,6 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as Browser from '@libs/Browser'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -41,7 +40,6 @@ function BaseVideoPlayer({ isVideoHovered, }) { const styles = useThemeStyles(); - const {isSmallScreenWidth} = useWindowDimensions(); const {pauseVideo, playVideo, currentlyPlayingURL, updateSharedElements, sharedElement, originalParent, shareVideoPlayerElements, currentVideoPlayerRef, updateCurrentlyPlayingURL} = usePlaybackContext(); const [duration, setDuration] = useState(videoDuration * 1000); @@ -96,20 +94,24 @@ function BaseVideoPlayer({ const handlePlaybackStatusUpdate = useCallback( (e) => { - if (shouldReplayVideo(e, isPlaying, duration, position)) { + const isVideoPlaying = e.isPlaying || false; + const currentDuration = e.durationMillis || videoDuration * 1000; + const currentPositon = e.positionMillis || 0; + + if (shouldReplayVideo(e, isVideoPlaying, currentDuration, currentPositon)) { videoPlayerRef.current.setStatusAsync({positionMillis: 0, shouldPlay: true}); } - const isVideoPlaying = e.isPlaying || false; + preventPausingWhenExitingFullscreen(isVideoPlaying); setIsPlaying(isVideoPlaying); setIsLoading(!e.isLoaded || Number.isNaN(e.durationMillis)); // when video is ready to display duration is not NaN setIsBuffering(e.isBuffering || false); - setDuration(e.durationMillis || videoDuration * 1000); - setPosition(e.positionMillis || 0); + setDuration(currentDuration); + setPosition(currentPositon); onPlaybackStatusUpdate(e); }, - [onPlaybackStatusUpdate, preventPausingWhenExitingFullscreen, videoDuration, isPlaying, duration, position], + [onPlaybackStatusUpdate, preventPausingWhenExitingFullscreen, videoDuration], ); const handleFullscreenUpdate = useCallback( @@ -165,7 +167,7 @@ function BaseVideoPlayer({ } originalParent.appendChild(sharedElement); }; - }, [bindFunctions, currentVideoPlayerRef, currentlyPlayingURL, isSmallScreenWidth, originalParent, sharedElement, shouldUseSharedVideoElement, url]); + }, [bindFunctions, currentVideoPlayerRef, currentlyPlayingURL, originalParent, sharedElement, shouldUseSharedVideoElement, url]); return ( <> From 344a2deec1dc16e7c8ad216dee43a791facdead2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 26 Feb 2024 16:14:32 +0100 Subject: [PATCH 07/56] Remove Podfile.lock changes --- ios/Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 82ffcd71c92d..52c817c739b3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1999,8 +1999,8 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 0a6794d1974aed5d653d0d0cb900493e2583e35a - Yoga: 13c8ef87792450193e117976337b8527b49e8c03 + Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2 -COCOAPODS: 1.14.3 +COCOAPODS: 1.13.0 From 581f72058e8710c16a2105328654ce4fa157ae61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Chrab=C4=85szczewski?= Date: Mon, 26 Feb 2024 16:22:56 +0100 Subject: [PATCH 08/56] feat: category settings init --- src/ROUTES.ts | 5 ++ src/SCREENS.ts | 1 + src/languages/en.ts | 2 + src/languages/es.ts | 2 + .../SetWorkspaceCategoriesEnabledParams.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 6 ++ src/libs/Navigation/types.ts | 4 + src/libs/actions/Policy.ts | 53 +++++++++++ .../categories/CategorySettingsPage.tsx | 87 +++++++++++++++++++ .../categories/WorkspaceCategoriesPage.tsx | 9 ++ 13 files changed, 179 insertions(+) create mode 100644 src/libs/API/parameters/SetWorkspaceCategoriesEnabledParams.ts create mode 100644 src/pages/workspace/categories/CategorySettingsPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a8786bda3ffb..0c8825057831 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -526,6 +526,11 @@ const ROUTES = { route: 'workspace/:policyID/categories', getRoute: (policyID: string) => `workspace/${policyID}/categories` as const, }, + WORKSPACE_CATEGORY_SETTINGS: { + route: 'workspace/:policyID/categories/:categoryName', + getRoute: (policyID: string, categoryName: string) => `workspace/${policyID}/categories/${encodeURI(categoryName)}` as const, + }, + // Referral program promotion REFERRAL_DETAILS_MODAL: { route: 'referral/:contentType', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 520895c89c98..8bd8f2ddf012 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -219,6 +219,7 @@ const SCREENS = { DESCRIPTION: 'Workspace_Profile_Description', SHARE: 'Workspace_Profile_Share', NAME: 'Workspace_Profile_Name', + CATEGORY_SETTINGS: 'Category_Settings', }, EDIT_REQUEST: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 4d7041d4a791..7938e81a60bc 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1739,6 +1739,8 @@ export default { collect: 'Collect', }, categories: { + categoryName: 'Category name', + enableCategory: 'Enable category', subtitle: 'Get a better overview of where money is being spent. Use our default categories or add your own.', emptyCategories: { title: "You haven't created any categories", diff --git a/src/languages/es.ts b/src/languages/es.ts index c9ff087d0de7..e1b4cac4df86 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1763,6 +1763,8 @@ export default { collect: 'Recolectar', }, categories: { + categoryName: 'Nombre de la categoría', + enableCategory: 'Activar categoría', subtitle: 'Obtén una visión general de dónde te gastas el dinero. Utiliza las categorías predeterminadas o añade las tuyas propias.', emptyCategories: { title: 'No has creado ninguna categoría', diff --git a/src/libs/API/parameters/SetWorkspaceCategoriesEnabledParams.ts b/src/libs/API/parameters/SetWorkspaceCategoriesEnabledParams.ts new file mode 100644 index 000000000000..d976c8165a11 --- /dev/null +++ b/src/libs/API/parameters/SetWorkspaceCategoriesEnabledParams.ts @@ -0,0 +1,6 @@ +type SetWorkspaceCategoriesEnabledParams = { + policyID: string; + categories: Array<{name: string; enabled: boolean}>; +}; + +export default SetWorkspaceCategoriesEnabledParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 0b0a81eb21f8..d01c854a0b96 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -145,6 +145,7 @@ export type {default as UnHoldMoneyRequestParams} from './UnHoldMoneyRequestPara 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 SetWorkspaceCategoriesEnabledParams} from './SetWorkspaceCategoriesEnabledParams'; export type {default as SetWorkspaceAutoReportingParams} from './SetWorkspaceAutoReportingParams'; 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 17cc366ba3b7..6694274762d1 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -110,6 +110,7 @@ const WRITE_COMMANDS = { UPDATE_WORKSPACE_DESCRIPTION: 'UpdateWorkspaceDescription', CREATE_WORKSPACE: 'CreateWorkspace', CREATE_WORKSPACE_FROM_IOU_PAYMENT: 'CreateWorkspaceFromIOUPayment', + SET_WORKSPACE_CATEGORIES_ENABLED: 'SetWorkspaceCategoriesEnabled', CREATE_TASK: 'CreateTask', CANCEL_TASK: 'CancelTask', EDIT_TASK_ASSIGNEE: 'EditTaskAssignee', @@ -255,6 +256,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT_AND_RATE]: Parameters.UpdateWorkspaceCustomUnitAndRateParams; [WRITE_COMMANDS.CREATE_WORKSPACE]: Parameters.CreateWorkspaceParams; [WRITE_COMMANDS.CREATE_WORKSPACE_FROM_IOU_PAYMENT]: Parameters.CreateWorkspaceFromIOUPaymentParams; + [WRITE_COMMANDS.SET_WORKSPACE_CATEGORIES_ENABLED]: Parameters.SetWorkspaceCategoriesEnabledParams; [WRITE_COMMANDS.CREATE_TASK]: Parameters.CreateTaskParams; [WRITE_COMMANDS.CANCEL_TASK]: Parameters.CancelTaskParams; [WRITE_COMMANDS.EDIT_TASK_ASSIGNEE]: Parameters.EditTaskAssigneeParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 2be262aa5f0f..b6a8a8d90dd5 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -248,6 +248,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceProfileDescriptionPage').default as React.ComponentType, [SCREENS.WORKSPACE.SHARE]: () => require('../../../pages/workspace/WorkspaceProfileSharePage').default as React.ComponentType, [SCREENS.WORKSPACE.CURRENCY]: () => require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../pages/workspace/categories/CategorySettingsPage').default as React.ComponentType, [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType, [SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType, [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 48d649cc4dd9..7e53581d9c43 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -259,6 +259,12 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.INVITE_MESSAGE]: { path: ROUTES.WORKSPACE_INVITE_MESSAGE.route, }, + [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: { + path: ROUTES.WORKSPACE_CATEGORY_SETTINGS.route, + parse: { + categoryName: (categoryName: string) => decodeURI(categoryName), + }, + }, [SCREENS.REIMBURSEMENT_ACCOUNT]: { path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index f02bb3bd2aca..5a7eb415d0cb 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -175,6 +175,10 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.INVITE_MESSAGE]: { policyID: string; }; + [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: { + policyID: string; + categoryName: string; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 57cd4a6fc071..2b53a0d053c8 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -28,6 +28,7 @@ import type { UpdateWorkspaceGeneralSettingsParams, } from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import * as CollectionUtils from '@libs/CollectionUtils'; import DateUtils from '@libs/DateUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import Log from '@libs/Log'; @@ -43,6 +44,8 @@ import type { InvitedEmailsToAccountIDs, PersonalDetailsList, Policy, + PolicyCategories, + PolicyCategory, PolicyMember, PolicyTagList, RecentlyUsedCategories, @@ -54,6 +57,7 @@ import type { } from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy'; +import type {OnyxData} from '@src/types/onyx/Request'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -199,6 +203,18 @@ Onyx.connect({ callback: (val) => (allRecentlyUsedTags = val), }); +const allCategoryPolicies: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY_CATEGORIES, + callback: (val, key) => { + if (!key) { + return; + } + const policyID = CollectionUtils.extractCollectionItemID(key); + allCategoryPolicies[policyID] = val; + }, +}); + /** * Stores in Onyx the policy ID of the last workspace that was accessed by the user */ @@ -2178,6 +2194,42 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string return policyID; } +function setWorkspaceCategoryEnabled(policyID: string, categoriesToUpdate: Record) { + const policyCategories = allCategoryPolicies?.[policyID] ?? {}; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: { + ...Object.keys(categoriesToUpdate).reduce>((acc, key) => { + acc[key] = {...policyCategories[key], ...categoriesToUpdate[key]}; + + return acc; + }, {}), + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: { + ...policyCategories, + }, + }, + ], + }; + + const parameters = { + policyID, + categories: Object.keys(categoriesToUpdate).map((key) => categoriesToUpdate[key]), + }; + + API.write('SetWorkspaceCategoriesEnabled', parameters, onyxData); +} + export { removeMembers, addMembersToWorkspace, @@ -2221,4 +2273,5 @@ export { setWorkspaceAutoReporting, setWorkspaceApprovalMode, updateWorkspaceDescription, + setWorkspaceCategoryEnabled, }; diff --git a/src/pages/workspace/categories/CategorySettingsPage.tsx b/src/pages/workspace/categories/CategorySettingsPage.tsx new file mode 100644 index 000000000000..3a736f493fe4 --- /dev/null +++ b/src/pages/workspace/categories/CategorySettingsPage.tsx @@ -0,0 +1,87 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Switch from '@components/Switch'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import {setWorkspaceCategoryEnabled} from '@libs/actions/Policy'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; +import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type * as OnyxTypes from '@src/types/onyx'; + +type CategorySettingsPageOnyxProps = { + /** Collection of categories attached to a policy */ + policyCategories: OnyxEntry; +}; + +type CategorySettingsPageProps = CategorySettingsPageOnyxProps & StackScreenProps; + +function CategorySettingsPage({route, policyCategories}: CategorySettingsPageProps) { + const {isSmallScreenWidth} = useWindowDimensions(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const policyCategory = policyCategories?.[route.params.categoryName]; + + if (!policyCategory) { + return ; + } + + const updateWorkspaceRequiresCategory = (value: boolean) => { + setWorkspaceCategoryEnabled(route.params.policyID, {[policyCategory.name]: {name: policyCategory.name, enabled: value}}); + }; + + return ( + + + + + + + + {translate('workspace.categories.enableCategory')} + + + + + + + + ); +} + +CategorySettingsPage.displayName = 'CategorySettingsPage'; + +export default withOnyx({ + policyCategories: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params.policyID}`, + }, +})(CategorySettingsPage); diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index b8a65c28806b..b3d6c81c169a 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -16,10 +16,12 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import Navigation from '@libs/Navigation/Navigation'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -67,11 +69,18 @@ function WorkspaceCategoriesPage({policyCategories, route}: WorkspaceCategoriesP [policyCategories, selectedCategories, styles.alignSelfCenter, styles.disabledText, styles.flexRow, styles.p1, styles.pl2, theme.icon, translate], ); + const navigateToCategorySettings = (categoryName: string) => { + Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, categoryName)); + }; + const toggleCategory = (category: PolicyForList) => { setSelectedCategories((prev) => ({ ...prev, [category.value]: !prev[category.value], })); + + // FIXME: This is a temporary solution to navigate to category settings page + navigateToCategorySettings(category.text); }; const toggleAllCategories = () => { 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 09/56] 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 10/56] 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 11/56] 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 12/56] 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 13/56] 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 14/56] 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 15/56] 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 16/56] 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 17/56] 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 18/56] 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 81ff8272823a6710e6f3269cbb783b2114ba50b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Tue, 27 Feb 2024 10:08:07 +0100 Subject: [PATCH 19/56] Fix touchable video area --- src/components/VideoPlayer/BaseVideoPlayer.js | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index 321c765b192d..86ade325dec0 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -175,36 +175,36 @@ function BaseVideoPlayer({ {(isHovered) => ( - {shouldUseSharedVideoElement ? ( - <> - - {/* We are adding transparent absolute View between appended video component and control buttons to enable + { + togglePlayCurrentVideo(); + }} + style={styles.flex1} + > + {shouldUseSharedVideoElement ? ( + <> + + {/* We are adding transparent absolute View between appended video component and control buttons to enable catching onMouse events from Attachment Carousel. Due to late appending React doesn't handle element's events properly. */} - - - ) : ( - { - if (!el) { - return; - } - videoPlayerElementParentRef.current = el; - if (el.childNodes && el.childNodes[0]) { - videoPlayerElementRef.current = el.childNodes[0]; - } - }} - > - { - togglePlayCurrentVideo(); - }} + + + ) : ( + { + if (!el) { + return; + } + videoPlayerElementParentRef.current = el; + if (el.childNodes && el.childNodes[0]) { + videoPlayerElementRef.current = el.childNodes[0]; + } + }} > - - )} + + )} + {(isLoading || isBuffering) && } From 8256c9366d55b1f758dff6bc82327f18ec8b9538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Chrab=C4=85szczewski?= Date: Tue, 27 Feb 2024 11:44:36 +0100 Subject: [PATCH 20/56] fix: set policy categories params to string as strignified object --- .../API/parameters/SetWorkspaceCategoriesEnabledParams.ts | 6 +++++- src/libs/actions/Policy.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libs/API/parameters/SetWorkspaceCategoriesEnabledParams.ts b/src/libs/API/parameters/SetWorkspaceCategoriesEnabledParams.ts index d976c8165a11..0851dc366819 100644 --- a/src/libs/API/parameters/SetWorkspaceCategoriesEnabledParams.ts +++ b/src/libs/API/parameters/SetWorkspaceCategoriesEnabledParams.ts @@ -1,6 +1,10 @@ type SetWorkspaceCategoriesEnabledParams = { policyID: string; - categories: Array<{name: string; enabled: boolean}>; + /** + * Stringified JSON object with type of following structure: + * Array<{name: string; enabled: boolean}> + */ + categories: string; }; export default SetWorkspaceCategoriesEnabledParams; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 2b53a0d053c8..efec0c3a3fcc 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2224,7 +2224,7 @@ function setWorkspaceCategoryEnabled(policyID: string, categoriesToUpdate: Recor const parameters = { policyID, - categories: Object.keys(categoriesToUpdate).map((key) => categoriesToUpdate[key]), + categories: JSON.stringify(Object.keys(categoriesToUpdate).map((key) => categoriesToUpdate[key])), }; API.write('SetWorkspaceCategoriesEnabled', parameters, onyxData); From 5fec6fdd8cdf299510ad356dfcf235014edfb2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Chrab=C4=85szczewski?= Date: Tue, 27 Feb 2024 11:44:52 +0100 Subject: [PATCH 21/56] fix: hide offline indicator in RHP --- src/pages/workspace/categories/CategorySettingsPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/categories/CategorySettingsPage.tsx b/src/pages/workspace/categories/CategorySettingsPage.tsx index 3a736f493fe4..861ec6a03663 100644 --- a/src/pages/workspace/categories/CategorySettingsPage.tsx +++ b/src/pages/workspace/categories/CategorySettingsPage.tsx @@ -49,7 +49,6 @@ function CategorySettingsPage({route, policyCategories}: CategorySettingsPagePro includeSafeAreaPaddingBottom={false} style={[styles.defaultModalContainer]} testID={CategorySettingsPage.displayName} - shouldShowOfflineIndicatorInWideScreen > Date: Tue, 27 Feb 2024 13:35:40 +0100 Subject: [PATCH 22/56] 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 23/56] 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 24/56] 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 25/56] 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 26/56] 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 27/56] 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 28/56] 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 29/56] 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 30/56] 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 31/56] 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 32/56] 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 33/56] 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 34/56] 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 35/56] 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 36/56] 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 37/56] 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 38/56] 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 39/56] 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 3b12585ebdd4c8059cbdf159bcff417dbbd1ca43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Chrab=C4=85szczewski?= Date: Wed, 28 Feb 2024 10:38:34 +0100 Subject: [PATCH 40/56] fix: remove disabled menu item --- src/pages/workspace/categories/CategorySettingsPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/workspace/categories/CategorySettingsPage.tsx b/src/pages/workspace/categories/CategorySettingsPage.tsx index 861ec6a03663..d23ce119a498 100644 --- a/src/pages/workspace/categories/CategorySettingsPage.tsx +++ b/src/pages/workspace/categories/CategorySettingsPage.tsx @@ -68,8 +68,6 @@ function CategorySettingsPage({route, policyCategories}: CategorySettingsPagePro 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 41/56] 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 42/56] 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 43/56] 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 1698e9a68361eb6dfd209f3d8a485f6a914c4289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Chrab=C4=85szczewski?= Date: Thu, 29 Feb 2024 09:50:25 +0100 Subject: [PATCH 44/56] fix: conflicts --- .../categories/WorkspaceCategoriesPage.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 0a0f351fbac2..d15011489bac 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -70,18 +70,11 @@ function WorkspaceCategoriesPage({policyCategories, route}: WorkspaceCategoriesP [policyCategories, selectedCategories, styles.alignSelfCenter, styles.disabledText, styles.flexRow, styles.p1, styles.pl2, theme.icon, translate], ); - const navigateToCategorySettings = (categoryName: string) => { - Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, categoryName)); - }; - const toggleCategory = (category: PolicyForList) => { setSelectedCategories((prev) => ({ ...prev, [category.value]: !prev[category.value], })); - - // FIXME: This is a temporary solution to navigate to category settings page - navigateToCategorySettings(category.text); }; const toggleAllCategories = () => { @@ -96,15 +89,19 @@ function WorkspaceCategoriesPage({policyCategories, route}: WorkspaceCategoriesP ); - const navigateToCategorySettings = () => { + const navigateToCategoriesSettings = () => { Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES_SETTINGS.getRoute(route.params.policyID)); }; + const navigateToCategorySettings = (category: PolicyForList) => { + Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, category.text)); + }; + const settingsButton = (