From 2568b3c0bee9200a0b3c0f53c39da7864d34f09c Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 29 May 2024 10:41:39 +0700 Subject: [PATCH 001/502] fix: #announce room should default to 'Admins Only' for posting --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index df2b0b9d703e..91e48065b5b4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4743,7 +4743,7 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string, exp false, policyName, undefined, - undefined, + CONST.REPORT.WRITE_CAPABILITIES.ADMINS, // #announce contains all policy members so notifying always should be opt-in only. CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, From 15c5210ac2835853cb3fc81c3851ba53641ccbfe Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 11 Jul 2024 14:33:35 +0200 Subject: [PATCH 002/502] update the pay w/ expensify selectors logic --- src/CONST.ts | 5 ++ src/components/AddPaymentMethodMenu.tsx | 47 +++++++++++++++++-- .../parameters/CompleteGuidedSetupParams.ts | 1 + src/libs/actions/Report.ts | 2 + src/types/onyx/IntroSelected.ts | 3 ++ 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 50df9118a74e..bfb1ae701da4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1840,6 +1840,11 @@ const CONST = { BUSINESS_BANK_ACCOUNT: 'businessBankAccount', }, + PAYMENT_SELECTED: { + BBA: 'BBA', + PBA: 'PBA', + }, + PAYMENT_METHOD_ID_KEYS: { DEBIT_CARD: 'fundID', BANK_ACCOUNT: 'bankAccountID', diff --git a/src/components/AddPaymentMethodMenu.tsx b/src/components/AddPaymentMethodMenu.tsx index 325bab091bec..87644c4b67fc 100644 --- a/src/components/AddPaymentMethodMenu.tsx +++ b/src/components/AddPaymentMethodMenu.tsx @@ -1,18 +1,22 @@ import type {RefObject} from 'react'; -import React from 'react'; +import React, {useCallback} from 'react'; import type {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as ReportUserActions from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {AnchorPosition} from '@src/styles'; -import type {Report, Session} from '@src/types/onyx'; +import type {PersonalDetails, Report, Session} from '@src/types/onyx'; import type AnchorAlignment from '@src/types/utils/AnchorAlignment'; import * as Expensicons from './Icon/Expensicons'; import type {PaymentMethod} from './KYCWall/types'; +import {usePersonalDetails} from './OnyxProvider'; import PopoverMenu from './PopoverMenu'; type AddPaymentMethodMenuOnyxProps = { @@ -70,6 +74,32 @@ function AddPaymentMethodMenu({ const canUsePersonalBankAccount = shouldShowPersonalBankAccountOption || isIOUReport; + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); + const isInviteOnboardingComplete = introSelected?.isInviteOnboardingComplete ?? false; + + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + const personalDetailsList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(session?.accountID ? [session.accountID] : [], personalDetails)) as PersonalDetails[]; + const personalDetail = personalDetailsList[0] ?? {}; + + const completeEngagement = useCallback( + (paymentSelected: ValueOf) => { + if (isInviteOnboardingComplete || !introSelected?.choice) { + return; + } + + ReportUserActions.completeOnboarding( + introSelected?.choice, + CONST.ONBOARDING_MESSAGES[introSelected?.choice], + { + firstName: personalDetail.firstName ?? '', + lastName: personalDetail.lastName ?? '', + }, + paymentSelected, + ); + }, + [isInviteOnboardingComplete, introSelected?.choice, personalDetail.firstName, personalDetail.lastName], + ); + return ( { + completeEngagement(CONST.PAYMENT_SELECTED.PBA); onItemSelected(CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT); }, }, @@ -95,7 +126,10 @@ function AddPaymentMethodMenu({ { text: translate('common.businessBankAccount'), icon: Expensicons.Building, - onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT), + onSelected: () => { + completeEngagement(CONST.PAYMENT_SELECTED.BBA); + onItemSelected(CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT); + }, }, ] : []), @@ -103,7 +137,10 @@ function AddPaymentMethodMenu({ { text: translate('common.debitCard'), icon: Expensicons.CreditCard, - onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD), + onSelected: () => { + completeEngagement(CONST.PAYMENT_SELECTED.PBA); + onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD); + }, }, ], ]} diff --git a/src/libs/API/parameters/CompleteGuidedSetupParams.ts b/src/libs/API/parameters/CompleteGuidedSetupParams.ts index 8e1273ac6053..0b2c0b66ef0a 100644 --- a/src/libs/API/parameters/CompleteGuidedSetupParams.ts +++ b/src/libs/API/parameters/CompleteGuidedSetupParams.ts @@ -6,6 +6,7 @@ type CompleteGuidedSetupParams = { actorAccountID: number; guidedSetupData: string; engagementChoice: OnboardingPurposeType; + paymentSelected?: string; }; export default CompleteGuidedSetupParams; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 3060f53f12c3..bc9c114442a0 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3158,6 +3158,7 @@ function completeOnboarding( }, adminsChatReportID?: string, onboardingPolicyID?: string, + paymentSelected?: string, ) { const isAccountIDOdd = AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? 0); const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; @@ -3504,6 +3505,7 @@ function completeOnboarding( lastName, actorAccountID, guidedSetupData: JSON.stringify(guidedSetupData), + paymentSelected, }; API.write(WRITE_COMMANDS.COMPLETE_GUIDED_SETUP, parameters, {optimisticData, successData, failureData}); diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts index 6850f651ca2a..6693745d2c95 100644 --- a/src/types/onyx/IntroSelected.ts +++ b/src/types/onyx/IntroSelected.ts @@ -4,6 +4,9 @@ import type {OnboardingPurposeType} from '@src/CONST'; type IntroSelected = { /** The choice that the user selected in the engagement modal */ choice: OnboardingPurposeType; + + /** Whether the onboarding is complete */ + isInviteOnboardingComplete: boolean; }; export default IntroSelected; From e2a6f60a2904d1351d4068230943fc81bbcf4c14 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Jul 2024 16:08:01 -0700 Subject: [PATCH 003/502] Remove front-end workaround for authWrites issue --- src/libs/HttpUtils.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index a826c668be12..f8df7aa69846 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -37,9 +37,6 @@ const abortControllerMap = new Map(); abortControllerMap.set(ABORT_COMMANDS.All, new AbortController()); abortControllerMap.set(ABORT_COMMANDS.SearchForReports, 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 */ @@ -133,7 +130,7 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form }); } - if (response.jsonCode === CONST.JSON_CODE.MANY_WRITES_ERROR && !exemptedCommandsWithAuthWrites.includes(response.data?.phpCommandName ?? '')) { + if (response.jsonCode === CONST.JSON_CODE.MANY_WRITES_ERROR) { if (response.data) { const {phpCommandName, authWriteCommands} = response.data; // eslint-disable-next-line max-len From f0a484508ae082a765140f53ccd0e0535c4a3333 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:03:15 +0200 Subject: [PATCH 004/502] update completeEngagement in AddPaymentMethodMenu with inviteType check for choice --- src/CONST.ts | 100 ++++++++++++++++-- src/components/AddPaymentMethodMenu.tsx | 13 ++- src/libs/actions/Report.ts | 2 + .../BaseOnboardingPurpose.tsx | 2 +- src/types/onyx/IntroSelected.ts | 9 +- 5 files changed, 112 insertions(+), 14 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index a273c2c105c4..c3a977f07e26 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4,6 +4,7 @@ import dateSubtract from 'date-fns/sub'; import Config from 'react-native-config'; import * as KeyCommand from 'react-native-key-command'; import type {ValueOf} from 'type-fest'; +import type {Video} from './libs/actions/Report'; import BankAccount from './libs/models/BankAccount'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; @@ -63,7 +64,7 @@ const chatTypes = { // Explicit type annotation is required const cardActiveStates: number[] = [2, 3, 4, 7]; -const onboardingChoices = { +const selectableOnboardingChoices = { PERSONAL_SPEND: 'newDotPersonalSpend', MANAGE_TEAM: 'newDotManageTeam', EMPLOYER: 'newDotEmployer', @@ -71,8 +72,40 @@ const onboardingChoices = { LOOKING_AROUND: 'newDotLookingAround', }; +const backendOnboardingChoices = { + ADMIN: 'newDotAdmin', + SUBMIT: 'newDotSubmit', +}; + +const onboardingChoices = { + ...selectableOnboardingChoices, + ...backendOnboardingChoices, +}; + type OnboardingPurposeType = ValueOf; +const onboardingInviteTypes = { + IOU: 'iou', + INVOICE: 'invoice', + CHAT: 'chat', +}; + +type OnboardingInviteType = ValueOf; + +type OnboardingTaskType = { + type: string; + autoCompleted: boolean; + title: string; + description: string | ((params: Partial<{adminsRoomLink: string; workspaceLink: string}>) => string); +}; + +type OnboardingMessageType = { + message: string; + video?: Video; + tasks: OnboardingTaskType[]; + type?: string; +}; + const CONST = { RECENT_WAYPOINTS_NUMBER: 20, DEFAULT_DB_NAME: 'OnyxDB', @@ -4189,6 +4222,8 @@ const CONST = { ONBOARDING_INTRODUCTION: 'Let’s get you set up 🔧', ONBOARDING_CHOICES: {...onboardingChoices}, + SELECTABLE_ONBOARDING_CHOICES: {...selectableOnboardingChoices}, + ONBOARDING_INVITE_TYPES: {...onboardingInviteTypes}, ACTIONABLE_TRACK_EXPENSE_WHISPER_MESSAGE: 'What would you like to do with this expense?', ONBOARDING_CONCIERGE: { [onboardingChoices.EMPLOYER]: @@ -4231,7 +4266,7 @@ const CONST = { }, ONBOARDING_MESSAGES: { - [onboardingChoices.EMPLOYER]: { + [(onboardingChoices.EMPLOYER || onboardingChoices.SUBMIT)]: { message: 'Getting paid back is as easy as sending a message. Let’s go over the basics.', video: { url: `${CLOUDFRONT_URL}/videos/guided-setup-get-paid-back-v2.mp4`, @@ -4302,7 +4337,7 @@ const CONST = { type: 'meetGuide', autoCompleted: false, title: 'Meet your setup specialist', - description: ({adminsRoomLink}: {adminsRoomLink: string}) => + description: ({adminsRoomLink}) => `Meet your setup specialist, who can answer any questions as you get started with Expensify. Yes, a real human!\n` + '\n' + `Chat with the specialist in your [#admins room](${adminsRoomLink}).`, @@ -4311,7 +4346,7 @@ const CONST = { type: 'setupCategories', autoCompleted: false, title: 'Set up categories', - description: ({workspaceLink}: {workspaceLink: string}) => + description: ({workspaceLink}) => '*Set up categories* so your team can code expenses for easy reporting.\n' + '\n' + 'Here’s how to set up categories:\n' + @@ -4328,7 +4363,7 @@ const CONST = { type: 'addExpenseApprovals', autoCompleted: false, title: 'Add expense approvals', - description: ({workspaceLink}: {workspaceLink: string}) => + description: ({workspaceLink}) => '*Add expense approvals* to review your team’s spend and keep it under control.\n' + '\n' + 'Here’s how to add expense approvals:\n' + @@ -4345,7 +4380,7 @@ const CONST = { type: 'inviteTeam', autoCompleted: false, title: 'Invite your team', - description: ({workspaceLink}: {workspaceLink: string}) => + description: ({workspaceLink}) => '*Invite your team* to Expensify so they can start tracking expenses today.\n' + '\n' + 'Here’s how to invite your team:\n' + @@ -4448,12 +4483,61 @@ const CONST = { }, ], }, + [onboardingChoices.ADMIN]: { + message: "Hey 👋\nAs an admin, learn how to manage your team's workspace and submit expenses yourself.", + video: { + url: `${CLOUDFRONT_URL}/videos/guided-setup-manage-team-v2.mp4`, + thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-manage-team.jpg`, + duration: 55, + width: 1280, + height: 960, + }, + tasks: [ + { + type: 'meetSetupSpecialist', + autoCompleted: false, + title: 'Meet your setup specialist', + description: + '*Meet your setup specialist* who can answer any questions as you get started with Expensify. Yes, a real human!' + + '\n' + + 'Chat with them in your #admins room or schedule a call today.', + }, + { + type: 'reviewWorkspaceSettings', + autoCompleted: false, + title: 'Review your workspace settings', + description: + "Here's how to review and update your workspace settings:" + + '\n' + + '1. Click your profile picture.' + + '2. Click *Workspaces* > [Your workspace].' + + '\n' + + "Make any changes there and we'll track them in the #admins room.", + }, + { + type: 'submitExpense', + autoCompleted: false, + title: 'Submit an expense', + description: + '*Submit an expense* by entering an amount or scanning a receipt.\n' + + '\n' + + 'Here’s how to submit an expense:\n' + + '\n' + + '1. Click the green *+* button.\n' + + '2. Choose *Submit expense*.\n' + + '3. Enter an amount or scan a receipt.\n' + + '4. Add your reimburser to the request.\n' + + '\n' + + 'Then, send your request and wait for that sweet “Cha-ching!” when it’s complete.', + }, + ], + }, [onboardingChoices.LOOKING_AROUND]: { message: "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", tasks: [], }, - }, + } satisfies Record, REPORT_FIELD_TITLE_FIELD_ID: 'text_title', @@ -5433,6 +5517,6 @@ type FeedbackSurveyOptionID = ValueOf; type CancellationType = ValueOf; -export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType, IOURequestType, SubscriptionType, FeedbackSurveyOptionID, CancellationType}; +export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType, IOURequestType, SubscriptionType, FeedbackSurveyOptionID, CancellationType, OnboardingInviteType}; export default CONST; diff --git a/src/components/AddPaymentMethodMenu.tsx b/src/components/AddPaymentMethodMenu.tsx index 9847aba8df2c..51ac2776b876 100644 --- a/src/components/AddPaymentMethodMenu.tsx +++ b/src/components/AddPaymentMethodMenu.tsx @@ -87,9 +87,18 @@ function AddPaymentMethodMenu({ return; } + let choice = introSelected.choice; + if (introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.IOU && paymentSelected === CONST.PAYMENT_SELECTED.BBA) { + choice = CONST.ONBOARDING_CHOICES.MANAGE_TEAM; + } + + if (introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.INVOICE && paymentSelected !== CONST.PAYMENT_SELECTED.BBA) { + choice = CONST.ONBOARDING_CHOICES.SUBMIT; + } + ReportUserActions.completeOnboarding( - introSelected?.choice, - CONST.ONBOARDING_MESSAGES[introSelected?.choice], + choice, + CONST.ONBOARDING_MESSAGES[choice], { firstName: personalDetail.firstName ?? '', lastName: personalDetail.lastName ?? '', diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index de651ff27610..e6ccc3d274ef 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3851,6 +3851,8 @@ function markAsManuallyExported(reportID: string) { }); } +export type {Video}; + export { searchInServer, addComment, diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index e9d0887bdac7..8d45a4893171 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -85,7 +85,7 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute(route.params?.backTo)); }, [selectedPurpose, route]); - const menuItems: MenuItemProps[] = Object.values(CONST.ONBOARDING_CHOICES).map((choice) => { + const menuItems: MenuItemProps[] = Object.values(CONST.SELECTABLE_ONBOARDING_CHOICES).map((choice) => { const translationKey = `onboarding.purpose.${choice}` as const; const isSelected = selectedPurpose === choice; return { diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts index 6693745d2c95..0e1b4ec60ae4 100644 --- a/src/types/onyx/IntroSelected.ts +++ b/src/types/onyx/IntroSelected.ts @@ -1,12 +1,15 @@ -import type {OnboardingPurposeType} from '@src/CONST'; +import type {OnboardingInviteType, OnboardingPurposeType} from '@src/CONST'; /** Model of onboarding */ -type IntroSelected = { +type IntroSelected = Partial<{ /** The choice that the user selected in the engagement modal */ choice: OnboardingPurposeType; + /** The invite type */ + inviteType: OnboardingInviteType; + /** Whether the onboarding is complete */ isInviteOnboardingComplete: boolean; -}; +}>; export default IntroSelected; From 7cea50d93f6e3e05bf7adaa211d1b049b6ffe918 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:05:00 +0200 Subject: [PATCH 005/502] fix lint, add useCallback dependency --- src/components/AddPaymentMethodMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AddPaymentMethodMenu.tsx b/src/components/AddPaymentMethodMenu.tsx index 51ac2776b876..839ab6a0c9bb 100644 --- a/src/components/AddPaymentMethodMenu.tsx +++ b/src/components/AddPaymentMethodMenu.tsx @@ -106,7 +106,7 @@ function AddPaymentMethodMenu({ paymentSelected, ); }, - [isInviteOnboardingComplete, introSelected?.choice, personalDetail.firstName, personalDetail.lastName], + [isInviteOnboardingComplete, introSelected?.inviteType, introSelected?.choice, personalDetail.firstName, personalDetail.lastName], ); const isPersonalOnlyOption = canUsePersonalBankAccount && !canUseBusinessBankAccount; From aa98fd457e0c7277dd72ac3097279e635632b195 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:05:47 +0200 Subject: [PATCH 006/502] fix prettier --- src/CONST.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index c3a977f07e26..0ab391ac1a36 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4266,7 +4266,7 @@ const CONST = { }, ONBOARDING_MESSAGES: { - [(onboardingChoices.EMPLOYER || onboardingChoices.SUBMIT)]: { + [onboardingChoices.EMPLOYER || onboardingChoices.SUBMIT]: { message: 'Getting paid back is as easy as sending a message. Let’s go over the basics.', video: { url: `${CLOUDFRONT_URL}/videos/guided-setup-get-paid-back-v2.mp4`, From 30814200a69d53fe22cacff111c87397c1577ac7 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 8 Aug 2024 15:05:45 +0200 Subject: [PATCH 007/502] Add ExpenseReportRulesSection --- .../rules/ExpenseReportRulesSection.tsx | 24 +++++++++++++++++++ src/pages/workspace/rules/PolicyRulesPage.tsx | 9 ++----- 2 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 src/pages/workspace/rules/ExpenseReportRulesSection.tsx diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx new file mode 100644 index 000000000000..3f786bae6ce3 --- /dev/null +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import Section from '@components/Section'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type ExpenseReportRulesSectionProps = { + policyID: string; +}; + +function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + return ( +
+ ); +} + +export default ExpenseReportRulesSection; diff --git a/src/pages/workspace/rules/PolicyRulesPage.tsx b/src/pages/workspace/rules/PolicyRulesPage.tsx index b23d72ca6787..f17fa5dab572 100644 --- a/src/pages/workspace/rules/PolicyRulesPage.tsx +++ b/src/pages/workspace/rules/PolicyRulesPage.tsx @@ -11,6 +11,7 @@ import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSection import * as Illustrations from '@src/components/Icon/Illustrations'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; +import ExpenseReportRulesSection from './ExpenseReportRulesSection'; type PolicyRulesPageProps = StackScreenProps; @@ -43,13 +44,7 @@ function PolicyRulesPage({route}: PolicyRulesPageProps) { titleStyles={styles.accountSettingsSectionTitle} subtitleMuted /> -
+ From 6a2e4d9fdbf60c51b0fc03082615b726b5807f98 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Sun, 11 Aug 2024 18:05:19 +0100 Subject: [PATCH 008/502] Make the Room Members view of rooms and expense chats consistent with groups --- src/CONST.ts | 8 + src/ROUTES.ts | 4 + src/SCREENS.ts | 2 + .../ButtonWithDropdownMenu/types.ts | 3 + .../SelectionList/BaseSelectionList.tsx | 2 +- src/components/SelectionList/types.ts | 3 + src/languages/en.ts | 2 + src/languages/es.ts | 2 + .../ModalStackNavigators/index.tsx | 4 + .../Navigators/RightModalNavigator.tsx | 4 + src/libs/Navigation/linkingConfig/config.ts | 5 + src/libs/Navigation/types.ts | 9 + src/pages/RoomMembersDetailsPage.tsx | 137 ++++++++++++++ src/pages/RoomMembersPage.tsx | 177 ++++++++++++------ .../home/report/withReportOrNotFound.tsx | 5 +- 15 files changed, 311 insertions(+), 56 deletions(-) create mode 100644 src/pages/RoomMembersDetailsPage.tsx diff --git a/src/CONST.ts b/src/CONST.ts index eaae7b82ef74..52047b65a3d6 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -919,6 +919,9 @@ const CONST = { EXPORT_TO_INTEGRATION: 'exportToIntegration', MARK_AS_EXPORTED: 'markAsExported', }, + ROOM_MEMBERS_BULK_ACTION_TYPES: { + REMOVE: 'remove', + }, }, NEXT_STEP: { ICONS: { @@ -4026,6 +4029,11 @@ const CONST = { */ MAX_SELECTION_LIST_PAGE_LENGTH: 500, + /** + * We only include the members search bar when we hit 8 number of members + */ + SHOULD_SHOW_MEMBERS_SEARCH_INPUT_BREAKPOINT: 8, + /** * Bank account names */ diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 27f565929c56..39dc4b6a8510 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -354,6 +354,10 @@ const ROUTES = { route: 'r/:reportID/members', getRoute: (reportID: string) => `r/${reportID}/members` as const, }, + ROOM_MEMBERS_DETAILS: { + route: 'r/:reportID/members/:accountID', + getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/members/${accountID}` as const, + }, ROOM_INVITE: { route: 'r/:reportID/invite/:role?', getRoute: (reportID: string, role?: string) => `r/${reportID}/invite/${role}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index d125c9658bf2..14c2c822f6b4 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -152,6 +152,7 @@ const SCREENS = { SIGN_IN: 'SignIn', PRIVATE_NOTES: 'Private_Notes', ROOM_MEMBERS: 'RoomMembers', + ROOM_MEMBERS_DETAILS: 'RoomMembers_Details', ROOM_INVITE: 'RoomInvite', REFERRAL: 'Referral', PROCESS_MONEY_REQUEST_HOLD: 'ProcessMoneyRequestHold', @@ -490,6 +491,7 @@ const SCREENS = { }, ROOM_MEMBERS_ROOT: 'RoomMembers_Root', ROOM_INVITE_ROOT: 'RoomInvite_Root', + ROOM_MEMBERS_DETAILS_ROOT: 'RoomMembersDetails_Root', FLAG_COMMENT_ROOT: 'FlagComment_Root', REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount', GET_ASSISTANCE: 'GetAssistance', diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index e4b81da94942..19d05499c48a 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -10,6 +10,8 @@ type PaymentType = DeepValueOf; +type RoomMemberBulkActionType = DeepValueOf; + type WorkspaceDistanceRatesBulkActionType = DeepValueOf; type WorkspaceTaxRatesBulkActionType = DeepValueOf; @@ -98,6 +100,7 @@ type ButtonWithDropdownMenuProps = { export type { PaymentType, WorkspaceMemberBulkActionType, + RoomMemberBulkActionType, WorkspaceDistanceRatesBulkActionType, DropdownOption, ButtonWithDropdownMenuProps, diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index e78d3d43d1ec..89d283d7d1de 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -97,6 +97,7 @@ function BaseSelectionList( shouldDelayFocus = true, shouldUpdateFocusedIndex = false, onLongPressRow, + shouldShowTextInput = !!textInputLabel || !!textInputIconLeft, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -105,7 +106,6 @@ function BaseSelectionList( const listRef = useRef>>(null); const innerTextInputRef = useRef(null); const focusTimeoutRef = useRef(null); - const shouldShowTextInput = !!textInputLabel || !!textInputIconLeft; const shouldShowSelectAll = !!onSelectAll; const activeElementRole = useActiveElementRole(); const isFocused = useIsFocused(); diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 782307876e55..c1358f152a21 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -329,6 +329,9 @@ type BaseSelectionListProps = Partial & { /** Callback to fire when an error is dismissed */ onDismissError?: (item: TItem) => void; + /** Whether to show the text input */ + shouldShowTextInput?: boolean; + /** Label for the text input */ textInputLabel?: string; diff --git a/src/languages/en.ts b/src/languages/en.ts index bfe0eef70178..327628867ccc 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2999,6 +2999,7 @@ export default { removeMembersTitle: 'Remove members', removeMemberButtonTitle: 'Remove from workspace', removeMemberGroupButtonTitle: 'Remove from group', + removeMemberRoomButtonTitle: 'Remove from room', removeMemberPrompt: ({memberName}: {memberName: string}) => `Are you sure you want to remove ${memberName}?`, removeMemberTitle: 'Remove member', transferOwner: 'Transfer owner', @@ -3581,6 +3582,7 @@ export default { }, }, roomMembersPage: { + roomMembersListTitle: 'Directory of all room members.', memberNotFound: 'Member not found. To invite a new member to the room, please use the invite button above.', notAuthorized: `You don't have access to this page. If you're trying to join this room, just ask a room member to add you. Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`, removeMembersPrompt: 'Are you sure you want to remove the selected members from the room?', diff --git a/src/languages/es.ts b/src/languages/es.ts index 76d55a096808..2f5f765d9364 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3051,6 +3051,7 @@ export default { removeMembersTitle: 'Eliminar miembros', removeMemberButtonTitle: 'Quitar del espacio de trabajo', removeMemberGroupButtonTitle: 'Quitar del grupo', + removeMemberRoomButtonTitle: 'Quitar de la sala', removeMemberPrompt: ({memberName}: {memberName: string}) => `¿Estás seguro de que deseas eliminar a ${memberName}?`, removeMemberTitle: 'Eliminar miembro', transferOwner: 'Transferir la propiedad', @@ -3635,6 +3636,7 @@ export default { }, }, roomMembersPage: { + roomMembersListTitle: 'Directorio de los miembros de la sala.', memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro a la sala de chat, por favor, utiliza el botón invitar que está más arriba.', notAuthorized: `No tienes acceso a esta página. Si estás intentando unirte a esta sala, pide a un miembro de la sala que te añada. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, removeMembersPrompt: '¿Estás seguro de que quieres eliminar a los miembros seleccionados de la sala de chat?', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index f903da87b4e9..e0595142fa0d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -156,6 +156,9 @@ const RoomInviteModalStackNavigator = createModalStackNavigator require('../../../../pages/RoomInvitePage').default, }); +const RoomMembersDetailsModalStackNavigator = createModalStackNavigator({ + [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: () => require('../../../../pages/RoomMembersDetailsPage').default, +}); const NewChatModalStackNavigator = createModalStackNavigator({ [SCREENS.NEW_CHAT.ROOT]: () => require('../../../../pages/NewChatSelectorPage').default, [SCREENS.NEW_CHAT.NEW_CHAT_CONFIRM]: () => require('../../../../pages/NewChatConfirmPage').default, @@ -549,6 +552,7 @@ export { ReportSettingsModalStackNavigator, RoomInviteModalStackNavigator, RoomMembersModalStackNavigator, + RoomMembersDetailsModalStackNavigator, SettingsModalStackNavigator, SignInModalStackNavigator, CategoriesModalStackNavigator, diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 44355cbbe955..6835d38739fc 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -107,6 +107,10 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.ROOM_INVITE} component={ModalStackNavigators.RoomInviteModalStackNavigator} /> + ['config'] = { [SCREENS.ROOM_MEMBERS_ROOT]: ROUTES.ROOM_MEMBERS.route, }, }, + [SCREENS.RIGHT_MODAL.ROOM_MEMBERS_DETAILS]: { + screens: { + [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: ROUTES.ROOM_MEMBERS_DETAILS.route, + }, + }, [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: { screens: { [SCREENS.MONEY_REQUEST.START]: ROUTES.MONEY_REQUEST_START.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index a43eab452463..6243132f574f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -771,6 +771,13 @@ type RoomInviteNavigatorParamList = { }; }; +type RoomMembersDetailsNavigatorParamList = { + [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: { + reportID: string; + accountID: string; + }; +}; + type MoneyRequestNavigatorParamList = { [SCREENS.MONEY_REQUEST.STEP_SEND_FROM]: { iouType: IOUType; @@ -1073,6 +1080,7 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.REPORT_DESCRIPTION]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.PARTICIPANTS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.ROOM_MEMBERS]: NavigatorScreenParams; + [SCREENS.RIGHT_MODAL.ROOM_MEMBERS_DETAILS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.ROOM_INVITE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.NEW_TASK]: NavigatorScreenParams; @@ -1357,6 +1365,7 @@ export type { RightModalNavigatorParamList, RoomInviteNavigatorParamList, RoomMembersNavigatorParamList, + RoomMembersDetailsNavigatorParamList, RootStackParamList, SearchNavigatorParamList, SettingsNavigatorParamList, diff --git a/src/pages/RoomMembersDetailsPage.tsx b/src/pages/RoomMembersDetailsPage.tsx new file mode 100644 index 000000000000..b2f8da10c7b7 --- /dev/null +++ b/src/pages/RoomMembersDetailsPage.tsx @@ -0,0 +1,137 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import Avatar from '@components/Avatar'; +import Button from '@components/Button'; +import ConfirmModal from '@components/ConfirmModal'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Report from '@libs/actions/Report'; +import Navigation from '@navigation/Navigation'; +import type {RoomMembersDetailsNavigatorParamList} from '@navigation/types'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetails, PersonalDetailsList} from '@src/types/onyx'; +import NotFoundPage from './ErrorPage/NotFoundPage'; +import withReportOrNotFound from './home/report/withReportOrNotFound'; +import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; + +type RoomMembersDetailsPageOnyxProps = { + /** Personal details of all users */ + personalDetails: OnyxEntry; +}; + +type RoomMembersDetailsPagePageProps = WithReportOrNotFoundProps & + StackScreenProps & + RoomMembersDetailsPageOnyxProps; + +function RoomMembersDetailsPage({personalDetails, report, route}: RoomMembersDetailsPagePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const StyleUtils = useStyleUtils(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + + const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = React.useState(false); + + const accountID = Number(route.params.accountID); + const backTo = ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? '-1'); + + const member = report?.participants?.[accountID]; + const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); + const fallbackIcon = details.fallbackIcon ?? ''; + const displayName = details.displayName ?? ''; + const isSelectedMemberCurrentUser = accountID === currentUserPersonalDetails?.accountID; + const removeUser = useCallback(() => { + setIsRemoveMemberConfirmModalVisible(false); + Report.removeFromGroupChat(report?.reportID, [accountID]); + Navigation.goBack(backTo); + }, [backTo, report, accountID]); + + const navigateToProfile = useCallback(() => { + Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute())); + }, [accountID]); + + if (!member) { + return ; + } + + return ( + + Navigation.goBack(backTo)} + /> + + + + {!!(details.displayName ?? '') && ( + + {displayName} + + )} + <> +
); } diff --git a/src/pages/workspace/rules/PolicyRulesPage.tsx b/src/pages/workspace/rules/PolicyRulesPage.tsx index f17fa5dab572..27f2095745ef 100644 --- a/src/pages/workspace/rules/PolicyRulesPage.tsx +++ b/src/pages/workspace/rules/PolicyRulesPage.tsx @@ -21,6 +21,7 @@ function PolicyRulesPage({route}: PolicyRulesPageProps) { const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + console.log('POLICY RULES PAGE'); return ( Date: Mon, 12 Aug 2024 10:27:57 +0200 Subject: [PATCH 012/502] replace items with ToggleSettingOptionRow --- .../workspace/AccessOrNotFoundWrapper.tsx | 2 +- .../rules/ExpenseReportRulesSection.tsx | 66 +++++++++++-------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index 816d2b74dbc6..15ab46ad2587 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -122,7 +122,7 @@ function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps const shouldShowFullScreenLoadingIndicator = !isMoneyRequest && isLoadingReportData !== false && (!Object.entries(policy ?? {}).length || !policy?.id); - const isFeatureEnabled = featureName ? PolicyUtils.isPolicyFeatureEnabled(policy, featureName) : true; + const isFeatureEnabled = true; const [isPolicyFeatureEnabled, setIsPolicyFeatureEnabled] = useState(isFeatureEnabled); const {isOffline} = useNetwork(); diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 95d034f3ca3e..82033355c6b5 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -33,34 +33,46 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const showBorderBottom = index !== EXPENSE_REPORT_RULE_TYPES.length - 1; return ( - {}} - style={[{paddingHorizontal: 0}, showBorderBottom && styles.borderBottom]} - titleContainerStyle={styles.pv6} - descriptionTextStyle={styles.pt1} - isLabelHoverable={false} - shouldShowRightComponent - interactive={false} - titleComponent={ - - - - {translate(title)} - - - {}} - isActive={false} - /> - - } - shouldShowDescriptionOnTop={false} + {}} /> + + // {}} + // style={[{paddingHorizontal: 0}, showBorderBottom && styles.borderBottom]} + // titleContainerStyle={styles.pv6} + // descriptionTextStyle={styles.pt1} + // isLabelHoverable={false} + // shouldShowRightComponent + // interactive={false} + // titleComponent={ + // + // + // + // {translate(title)} + // + // + // {}} + // isActive={false} + // /> + // + // } + // shouldShowDescriptionOnTop={false} + // /> ); })}
From e99bf8855eca6db8e47034aca0d2a26b98322c4a Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 12 Aug 2024 12:08:00 +0200 Subject: [PATCH 013/502] add custom report names section content wip --- src/languages/en.ts | 8 +- src/languages/es.ts | 8 +- .../rules/ExpenseReportRulesSection.tsx | 107 +++++++++++------- 3 files changed, 71 insertions(+), 52 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 08056c234de3..d84c008fdb96 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3532,13 +3532,13 @@ export default { title: 'Expense reports', subtitle: 'Automate expense report compliance, approvals, and payment.', customReportNamesTitle: 'Custom report names', - customReportNamesDescription: 'Create custom names using our extensive formulas.', + customReportNamesSubtitle: 'Create custom names using our extensive formulas.', preventSelfApprovalsTitle: 'Prevent self-approvals', - preventSelfApprovalsDescription: 'Prevent workspace members from approving their own expense reports.', + preventSelfApprovalsSubtitle: 'Prevent workspace members from approving their own expense reports.', autoApproveCompliantReportsTitle: 'Auto-approve compliant reports', - autoApproveCompliantReportsDescription: 'Configure which expense reports are eligible for auto-approval.', + autoApproveCompliantReportsSubtitle: 'Configure which expense reports are eligible for auto-approval.', autoPayApprovedReportsTitle: 'Auto-pay approved reports', - autoPayApprovedReportsDescription: 'Configure which expense reports are eligible for auto-pay.', + autoPayApprovedReportsSubtitle: 'Configure which expense reports are eligible for auto-pay.', }, }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index e6a39a9ea626..c237f0d83410 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3571,13 +3571,13 @@ export default { title: 'Informes de gastos', subtitle: 'Automatiza la conformidad, aprobaciones y pagos de informes de gastos.', customReportNamesTitle: 'Nombres personalizados de informes', - customReportNamesDescription: 'Nombres personalizados de informes', + customReportNamesSubtitle: 'Nombres personalizados de informes', preventSelfApprovalsTitle: 'Evitar autoaprobaciones', - preventSelfApprovalsDescription: 'Evita que los miembros del espacio de trabajo aprueben sus propios informes de gastos.', + preventSelfApprovalsSubtitle: 'Evita que los miembros del espacio de trabajo aprueben sus propios informes de gastos.', autoApproveCompliantReportsTitle: 'Aprobación automática de informes conformes', - autoApproveCompliantReportsDescription: 'Configura qué informes de gastos son elegibles para la aprobación automática.', + autoApproveCompliantReportsSubtitle: 'Configura qué informes de gastos son elegibles para la aprobación automática.', autoPayApprovedReportsTitle: 'Pago automático de informes aprobados', - autoPayApprovedReportsDescription: 'Configura qué informes de gastos son elegibles para el pago automático.', + autoPayApprovedReportsSubtitle: 'Configura qué informes de gastos son elegibles para el pago automático.', }, }, }, diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 82033355c6b5..b27018f03219 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -1,19 +1,11 @@ import React from 'react'; -import {View} from 'react-native'; import MenuItem from '@components/MenuItem'; import Section from '@components/Section'; -import Text from '@components/Text'; +import Switch from '@components/Switch'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; -const EXPENSE_REPORT_RULE_TYPES = [ - {title: 'workspace.rules.expenseReportRules.customReportNamesTitle', description: 'workspace.rules.expenseReportRules.customReportNamesDescription'}, - {title: 'workspace.rules.expenseReportRules.preventSelfApprovalsTitle', description: 'workspace.rules.expenseReportRules.preventSelfApprovalsDescription'}, - {title: 'workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle', description: 'workspace.rules.expenseReportRules.autoApproveCompliantReportsDescription'}, - {title: 'workspace.rules.expenseReportRules.autoPayApprovedReportsTitle', description: 'workspace.rules.expenseReportRules.autoPayApprovedReportsDescription'}, -] as const; - type ExpenseReportRulesSectionProps = { policyID: string; }; @@ -21,6 +13,61 @@ type ExpenseReportRulesSectionProps = { function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + + const optionItems = [ + { + title: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), + subtitle: translate('workspace.rules.expenseReportRules.customReportNamesSubtitle'), + switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), + isActive: true, + onToggle: (isEnabled: boolean) => {}, + subMenuItems: [ + {}} + />, + {}} + rightComponent={ + {}} + isOn={false} + /> + } + shouldShowRightComponent + style={[styles.sectionMenuItemTopDescription, styles.mt6, styles.mbn3]} + // wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]} + // brickRoadIndicator={hasReimburserError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + />, + ], + }, + { + title: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), + subtitle: translate('workspace.rules.expenseReportRules.preventSelfApprovalsSubtitle'), + switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), + onToggle: (isEnabled: boolean) => {}, + }, + { + title: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), + subtitle: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsSubtitle'), + switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), + onToggle: (isEnabled: boolean) => {}, + }, + + { + title: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), + subtitle: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsSubtitle'), + switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), + onToggle: (isEnabled: boolean) => {}, + }, + ]; + return (
- {EXPENSE_REPORT_RULE_TYPES.map(({title, description}, index) => { - const showBorderBottom = index !== EXPENSE_REPORT_RULE_TYPES.length - 1; + {optionItems.map(({title, subtitle, isActive, subMenuItems}, index) => { + const showBorderBottom = index !== optionItems.length - 1; return ( {}} /> - - // {}} - // style={[{paddingHorizontal: 0}, showBorderBottom && styles.borderBottom]} - // titleContainerStyle={styles.pv6} - // descriptionTextStyle={styles.pt1} - // isLabelHoverable={false} - // shouldShowRightComponent - // interactive={false} - // titleComponent={ - // - // - // - // {translate(title)} - // - // - // {}} - // isActive={false} - // /> - // - // } - // shouldShowDescriptionOnTop={false} - // /> ); })}
From bff54c33ae8d70584ead2f9c022ed4b48b8483bb Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 12 Aug 2024 12:13:35 +0200 Subject: [PATCH 014/502] add translations --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/pages/workspace/rules/ExpenseReportRulesSection.tsx | 8 +++----- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index d84c008fdb96..8d59602a49f4 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3533,6 +3533,8 @@ export default { subtitle: 'Automate expense report compliance, approvals, and payment.', customReportNamesTitle: 'Custom report names', customReportNamesSubtitle: 'Create custom names using our extensive formulas.', + customNameTitle: 'Custom name', + preventMembersFromChangingCustomNamesTitle: 'Prevent members from changing custom report names', preventSelfApprovalsTitle: 'Prevent self-approvals', preventSelfApprovalsSubtitle: 'Prevent workspace members from approving their own expense reports.', autoApproveCompliantReportsTitle: 'Auto-approve compliant reports', diff --git a/src/languages/es.ts b/src/languages/es.ts index c237f0d83410..d7d4a8c6cf3a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3572,6 +3572,8 @@ export default { subtitle: 'Automatiza la conformidad, aprobaciones y pagos de informes de gastos.', customReportNamesTitle: 'Nombres personalizados de informes', customReportNamesSubtitle: 'Nombres personalizados de informes', + customNameTitle: 'Nombre personalizado', + preventMembersFromChangingCustomNamesTitle: 'Impedir que los miembros cambien los nombres de los informes personalizados', preventSelfApprovalsTitle: 'Evitar autoaprobaciones', preventSelfApprovalsSubtitle: 'Evita que los miembros del espacio de trabajo aprueben sus propios informes de gastos.', autoApproveCompliantReportsTitle: 'Aprobación automática de informes conformes', diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index b27018f03219..761b355116bd 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -23,27 +23,25 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { onToggle: (isEnabled: boolean) => {}, subMenuItems: [ {}} />, {}} rightComponent={ {}} isOn={false} /> } shouldShowRightComponent style={[styles.sectionMenuItemTopDescription, styles.mt6, styles.mbn3]} - // wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]} - // brickRoadIndicator={hasReimburserError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} />, ], }, From 94a0db255314b722cea45390de4109f76fae0160 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 12 Aug 2024 16:09:46 +0200 Subject: [PATCH 015/502] setup rhp nav --- src/ROUTES.ts | 4 ++ src/SCREENS.ts | 1 + .../ModalStackNavigators/index.tsx | 1 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 ++ src/libs/Navigation/types.ts | 3 ++ .../rules/ExpenseReportRulesSection.tsx | 4 +- .../workspace/rules/RulesCustomNamePage.tsx | 37 +++++++++++++++++++ 8 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/pages/workspace/rules/RulesCustomNamePage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 04de05a6d4e2..3054b8d36531 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -952,6 +952,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/distance-rates/:rateID/tax-rate/edit', getRoute: (policyID: string, rateID: string) => `settings/workspaces/${policyID}/distance-rates/${rateID}/tax-rate/edit` as const, }, + RULES_CUSTOM_NAME: { + route: 'settings/workspaces/:policyID/rules/name', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/name` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { route: 'referral/:contentType', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index a0edbf11b146..0cb3662b10f8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -435,6 +435,7 @@ const SCREENS = { DISTANCE_RATE_TAX_RATE_EDIT: 'Distance_Rate_Tax_Rate_Edit', UPGRADE: 'Workspace_Upgrade', RULES: 'Policy_Rules', + RULES_CUSTOM_NAME: 'Rules_Custom_Name', }, EDIT_REQUEST: { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index d02cf14f85eb..332afd9dd1ad 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -451,6 +451,7 @@ const SettingsModalStackNavigator = createModalStackNavigator('../../../../pages/workspace/accounting/intacct/import/SageIntacctAddUserDimensionPage').default, [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EDIT_USER_DIMENSION]: () => require('../../../../pages/workspace/accounting/intacct/import/SageIntacctEditUserDimensionsPage').default, + [SCREENS.WORKSPACE.RULES_CUSTOM_NAME]: () => require('../../../../pages/workspace/rules/RulesCustomNamePage').default, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index c27ccaab5176..ea9a56cb873b 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -170,6 +170,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.EXPENSIFY_CARD_LIMIT, SCREENS.WORKSPACE.EXPENSIFY_CARD_LIMIT_TYPE, ], + [SCREENS.WORKSPACE.RULES]: [SCREENS.WORKSPACE.RULES_CUSTOM_NAME], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index b19d1261ad4e..282c4d566117 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -735,6 +735,9 @@ const config: LinkingOptions['config'] = { taxID: (taxID: string) => decodeURIComponent(taxID), }, }, + [SCREENS.WORKSPACE.RULES_CUSTOM_NAME]: { + path: ROUTES.RULES_CUSTOM_NAME.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index a21363ab8cd5..c6e6ee0029dd 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -697,6 +697,9 @@ type SettingsNavigatorParamList = { policyID: string; cardID: string; }; + [SCREENS.WORKSPACE.RULES_CUSTOM_NAME]: { + policyID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 761b355116bd..92bed7650750 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -4,7 +4,9 @@ import Section from '@components/Section'; import Switch from '@components/Switch'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; +import ROUTES from '@src/ROUTES'; type ExpenseReportRulesSectionProps = { policyID: string; @@ -27,7 +29,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { titleStyle={styles.textLabelSupportingEmptyValue} shouldShowRightIcon style={[styles.sectionMenuItemTopDescription, styles.mt6, styles.mbn3]} - onPress={() => {}} + onPress={() => Navigation.navigate(ROUTES.RULES_CUSTOM_NAME.getRoute(policyID))} />, ; + +function RulesCustomNamePage({route}: RulesReceiptRequiredAmountPageProps) { + const {translate} = useLocalize(); + + return ( + + + Navigation.goBack()} + /> + + + ); +} + +export default RulesCustomNamePage; From b07708af9192dff808a4331c020462c2e59acdac Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 13 Aug 2024 11:50:34 +0200 Subject: [PATCH 016/502] add bullet points, wire up form wip --- src/ONYXKEYS.ts | 3 ++ src/components/BulletList.tsx | 51 ++++++++++++++++++ src/languages/en.ts | 8 +++ .../workspace/rules/RulesCustomNamePage.tsx | 52 ++++++++++++++++++- src/types/form/RulesCustomNameModalForm.ts | 18 +++++++ src/types/form/index.ts | 1 + 6 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 src/components/BulletList.tsx create mode 100644 src/types/form/RulesCustomNameModalForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 40ab87055ca8..8fcee07a9473 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -612,6 +612,8 @@ const ONYXKEYS = { SEARCH_ADVANCED_FILTERS_FORM_DRAFT: 'searchAdvancedFiltersFormDraft', TEXT_PICKER_MODAL_FORM: 'textPickerModalForm', TEXT_PICKER_MODAL_FORM_DRAFT: 'textPickerModalFormDraft', + RULES_CUSTOM_NAME_MODAL_FORM: 'rulesCustomNameModalForm', + RULES_CUSTOM_NAME_MODAL_FORM_DRAFT: 'rulesCustomNameModalFormDraft', }, } as const; @@ -687,6 +689,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.SAGE_INTACCT_DIMENSION_TYPE_FORM]: FormTypes.SageIntacctDimensionForm; [ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM]: FormTypes.SearchAdvancedFiltersForm; [ONYXKEYS.FORMS.TEXT_PICKER_MODAL_FORM]: FormTypes.TextPickerModalForm; + [ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM]: FormTypes.RulesCustomNameModalForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/components/BulletList.tsx b/src/components/BulletList.tsx new file mode 100644 index 000000000000..52f93f1b269f --- /dev/null +++ b/src/components/BulletList.tsx @@ -0,0 +1,51 @@ +import type {ReactNode} from 'react'; +import React from 'react'; +import {View} from 'react-native'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Text from './Text'; + +type BulletListItem = string; + +type BulletListProps = { + /** List of items for the list. Eeach item will be rendered as a sepearte point. */ + items: BulletListItem[]; + + /** Header section of the list */ + header: string | ReactNode; +}; + +function BulletList({items, header}: BulletListProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + + const baseTextStyles = [styles.mutedNormalTextLabel]; + + const renderBulletListHeader = () => { + if (typeof header === 'string') { + return {header}; + } + return header; + }; + + const renderBulletPoint = (item: string) => { + return ( + + + {item} + + ); + }; + + return ( + + {renderBulletListHeader()} + {items.map((item) => renderBulletPoint(item))} + + ); +} + +BulletList.displayName = 'BulletList'; + +export type {BulletListProps}; +export default BulletList; diff --git a/src/languages/en.ts b/src/languages/en.ts index 8d59602a49f4..9bfefe4e0ded 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3534,6 +3534,14 @@ export default { customReportNamesTitle: 'Custom report names', customReportNamesSubtitle: 'Create custom names using our extensive formulas.', customNameTitle: 'Custom name', + customNameDescription: 'Choose a custom name for expense reports using our ', + customNameDescriptionLink: 'extensive formulas', + customNameInputLabel: 'Name', + customNameEmailPhoneExample: 'Member’s email or phone: {report:submit:from}', + customNameStartDateExample: 'Report start date: {report:startdate}', + customNameWorkspaceNameExample: 'Workspace name: {report:policyname}', + customNameReportIDExample: 'Report ID: {report:id}', + customNameTotalExample: 'Total: {report:total}.', preventMembersFromChangingCustomNamesTitle: 'Prevent members from changing custom report names', preventSelfApprovalsTitle: 'Prevent self-approvals', preventSelfApprovalsSubtitle: 'Prevent workspace members from approving their own expense reports.', diff --git a/src/pages/workspace/rules/RulesCustomNamePage.tsx b/src/pages/workspace/rules/RulesCustomNamePage.tsx index 778a8c88ad3c..e058e1deedf4 100644 --- a/src/pages/workspace/rules/RulesCustomNamePage.tsx +++ b/src/pages/workspace/rules/RulesCustomNamePage.tsx @@ -1,18 +1,35 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; +import {View} from 'react-native'; +import BulletList from '@components/BulletList'; +import FormProvider from '@components/Form/FormProvider'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; type RulesReceiptRequiredAmountPageProps = StackScreenProps; function RulesCustomNamePage({route}: RulesReceiptRequiredAmountPageProps) { const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const RULE_EXAMPLE_BULLET_POINTS = [ + translate('workspace.rules.expenseReportRules.customNameEmailPhoneExample'), + translate('workspace.rules.expenseReportRules.customNameStartDateExample'), + translate('workspace.rules.expenseReportRules.customNameWorkspaceNameExample'), + translate('workspace.rules.expenseReportRules.customNameReportIDExample'), + translate('workspace.rules.expenseReportRules.customNameTotalExample'), + ] as const; return ( Navigation.goBack()} /> + + + {translate('workspace.rules.expenseReportRules.customNameDescription')} + {}} + > + {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} + + . + + + {}} + submitButtonText={translate('common.save')} + enabledWhenOffline + > + + + ); } +RulesCustomNamePage.displayName = 'RulesCustomNamePage'; + export default RulesCustomNamePage; diff --git a/src/types/form/RulesCustomNameModalForm.ts b/src/types/form/RulesCustomNameModalForm.ts new file mode 100644 index 000000000000..dfd3bdb741d3 --- /dev/null +++ b/src/types/form/RulesCustomNameModalForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + CUSTOM_NAME: 'customName', +} as const; + +type InputID = ValueOf; + +type RulesCustomNameModalForm = Form< + InputID, + { + [INPUT_IDS.CUSTOM_NAME]: string; + } +>; + +export type {RulesCustomNameModalForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 60c068b2bea1..0dae33578242 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -65,5 +65,6 @@ export type {NetSuiteTokenInputForm} from './NetSuiteTokenInputForm'; export type {NetSuiteCustomFormIDForm} from './NetSuiteCustomFormIDForm'; export type {SearchAdvancedFiltersForm} from './SearchAdvancedFiltersForm'; export type {EditExpensifyCardLimitForm} from './EditExpensifyCardLimitForm'; +export type {RulesCustomNameModalForm} from './RulesCustomNameModalForm'; export type {default as TextPickerModalForm} from './TextPickerModalForm'; export type {default as Form} from './Form'; From 2e82822fd2a6250bc2754c82c7ae7816589e6164 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 13 Aug 2024 12:47:35 +0200 Subject: [PATCH 017/502] wire up custom name form --- .../SetWorkspaceReportTitleParams.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Workspace/Rules.ts | 59 +++++++++++++++++++ .../workspace/rules/RulesCustomNamePage.tsx | 23 +++++--- 5 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 src/libs/API/parameters/SetWorkspaceReportTitleParams.ts create mode 100644 src/libs/actions/Workspace/Rules.ts diff --git a/src/libs/API/parameters/SetWorkspaceReportTitleParams.ts b/src/libs/API/parameters/SetWorkspaceReportTitleParams.ts new file mode 100644 index 000000000000..239374935282 --- /dev/null +++ b/src/libs/API/parameters/SetWorkspaceReportTitleParams.ts @@ -0,0 +1,6 @@ +type SetWorkspaceReportTitleParams = { + policyID: string; + value: string; +}; + +export default SetWorkspaceReportTitleParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 72011f64384e..c7850d33dfd0 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -270,3 +270,4 @@ export type {default as UpdateExpensifyCardLimitParams} from './UpdateExpensifyC export type {CreateWorkspaceApprovalParams, UpdateWorkspaceApprovalParams, RemoveWorkspaceApprovalParams} from './WorkspaceApprovalParams'; export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFlowParams'; export type {default as SetPolicyRulesEnabledParams} from './SetPolicyRulesEnabledParams'; +export type {default as SetWorkspaceReportTitleParams} from './SetWorkspaceReportTitleParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 80b22a9d62cc..a484de27fcf6 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -14,6 +14,7 @@ const WRITE_COMMANDS = { SET_WORKSPACE_APPROVAL_MODE: 'SetWorkspaceApprovalMode', SET_WORKSPACE_PAYER: 'SetWorkspacePayer', SET_WORKSPACE_REIMBURSEMENT: 'SetWorkspaceReimbursement', + SET_WORKSPACE_REPORT_TITLE: 'SetWorkspaceReportTitle', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -508,6 +509,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams; [WRITE_COMMANDS.SET_WORKSPACE_PAYER]: Parameters.SetWorkspacePayerParams; [WRITE_COMMANDS.SET_WORKSPACE_REIMBURSEMENT]: Parameters.SetWorkspaceReimbursementParams; + [WRITE_COMMANDS.SET_WORKSPACE_REPORT_TITLE]: Parameters.SetWorkspaceReportTitleParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts new file mode 100644 index 000000000000..80de07eef23f --- /dev/null +++ b/src/libs/actions/Workspace/Rules.ts @@ -0,0 +1,59 @@ +import type {OnyxUpdate} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import * as API from '@libs/API'; +import type {SetWorkspaceReportTitleParams} from '@libs/API/parameters'; +import {WRITE_COMMANDS} from '@libs/API/types'; +import ONYXKEYS from '@src/ONYXKEYS'; + +/** + * Call the API to deactivate the card and request a new one + * @param cardID - id of the card that is going to be replaced + * @param reason - reason for replacement + */ +function modifyExpenseReportsNames(customName: string, policyID: string) { + console.log('modifyExpenseReportsNames ', customName, ' ', policyID); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: true, + errors: null, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const parameters: SetWorkspaceReportTitleParams = { + value: customName, + policyID, + }; + + // API.write(WRITE_COMMANDS.SET_WORKSPACE_REPORT_TITLE, parameters, { + // optimisticData, + // successData, + // failureData, + // }); +} + +// eslint-disable-next-line import/prefer-default-export +export {modifyExpenseReportsNames}; diff --git a/src/pages/workspace/rules/RulesCustomNamePage.tsx b/src/pages/workspace/rules/RulesCustomNamePage.tsx index e058e1deedf4..2b6398f868f5 100644 --- a/src/pages/workspace/rules/RulesCustomNamePage.tsx +++ b/src/pages/workspace/rules/RulesCustomNamePage.tsx @@ -3,6 +3,7 @@ import React from 'react'; import {View} from 'react-native'; import BulletList from '@components/BulletList'; import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -13,13 +14,17 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/RulesCustomNameModalForm'; -type RulesReceiptRequiredAmountPageProps = StackScreenProps; +type RulesCustomNamePageProps = StackScreenProps; + +function RulesCustomNamePage({route}: RulesCustomNamePageProps) { + const {policyID} = route.params; -function RulesCustomNamePage({route}: RulesReceiptRequiredAmountPageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -29,7 +34,7 @@ function RulesCustomNamePage({route}: RulesReceiptRequiredAmountPageProps) { translate('workspace.rules.expenseReportRules.customNameWorkspaceNameExample'), translate('workspace.rules.expenseReportRules.customNameReportIDExample'), translate('workspace.rules.expenseReportRules.customNameTotalExample'), - ] as const; + ] as const satisfies string[]; return ( {}} + href="https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates" > {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} @@ -62,14 +67,18 @@ function RulesCustomNamePage({route}: RulesReceiptRequiredAmountPageProps) { style={[styles.flexGrow1, styles.mh5]} formID={ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM} // validate={validator} - onSubmit={() => {}} + onSubmit={({customName}) => WorkspaceRulesActions.modifyExpenseReportsNames(customName, policyID)} submitButtonText={translate('common.save')} enabledWhenOffline > - + Date: Tue, 13 Aug 2024 13:59:31 +0200 Subject: [PATCH 018/502] cleanup custom names, prevent from changing names wip --- src/components/BulletList.tsx | 2 -- .../SetPolicyDefaultReportTitleParams.ts | 6 ++++++ .../SetWorkspaceReportTitleParams.ts | 6 ------ src/libs/API/parameters/index.ts | 2 +- src/libs/API/types.ts | 4 ++-- src/libs/actions/Workspace/Rules.ts | 19 +++++++++---------- .../rules/ExpenseReportRulesSection.tsx | 6 +++++- 7 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyDefaultReportTitleParams.ts delete mode 100644 src/libs/API/parameters/SetWorkspaceReportTitleParams.ts diff --git a/src/components/BulletList.tsx b/src/components/BulletList.tsx index 52f93f1b269f..ac6bc70bf314 100644 --- a/src/components/BulletList.tsx +++ b/src/components/BulletList.tsx @@ -1,7 +1,6 @@ import type {ReactNode} from 'react'; import React from 'react'; import {View} from 'react-native'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Text from './Text'; @@ -16,7 +15,6 @@ type BulletListProps = { }; function BulletList({items, header}: BulletListProps) { - const theme = useTheme(); const styles = useThemeStyles(); const baseTextStyles = [styles.mutedNormalTextLabel]; diff --git a/src/libs/API/parameters/SetPolicyDefaultReportTitleParams.ts b/src/libs/API/parameters/SetPolicyDefaultReportTitleParams.ts new file mode 100644 index 000000000000..e35dc53e7c7c --- /dev/null +++ b/src/libs/API/parameters/SetPolicyDefaultReportTitleParams.ts @@ -0,0 +1,6 @@ +type SetPolicyDefaultReportTitleParams = { + policyID: string; + value: string; +}; + +export default SetPolicyDefaultReportTitleParams; diff --git a/src/libs/API/parameters/SetWorkspaceReportTitleParams.ts b/src/libs/API/parameters/SetWorkspaceReportTitleParams.ts deleted file mode 100644 index 239374935282..000000000000 --- a/src/libs/API/parameters/SetWorkspaceReportTitleParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -type SetWorkspaceReportTitleParams = { - policyID: string; - value: string; -}; - -export default SetWorkspaceReportTitleParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index c7850d33dfd0..e2be10bda1ec 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -270,4 +270,4 @@ export type {default as UpdateExpensifyCardLimitParams} from './UpdateExpensifyC export type {CreateWorkspaceApprovalParams, UpdateWorkspaceApprovalParams, RemoveWorkspaceApprovalParams} from './WorkspaceApprovalParams'; export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFlowParams'; export type {default as SetPolicyRulesEnabledParams} from './SetPolicyRulesEnabledParams'; -export type {default as SetWorkspaceReportTitleParams} from './SetWorkspaceReportTitleParams'; +export type {default as SetPolicyDefaultReportTitleParams} from './SetPolicyDefaultReportTitleParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a484de27fcf6..eb7117bfdafd 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -14,7 +14,7 @@ const WRITE_COMMANDS = { SET_WORKSPACE_APPROVAL_MODE: 'SetWorkspaceApprovalMode', SET_WORKSPACE_PAYER: 'SetWorkspacePayer', SET_WORKSPACE_REIMBURSEMENT: 'SetWorkspaceReimbursement', - SET_WORKSPACE_REPORT_TITLE: 'SetWorkspaceReportTitle', + SET_POLICY_DEFAULT_REPORT_TITLE: 'SetPolicyDefaultReportTitle', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -509,7 +509,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams; [WRITE_COMMANDS.SET_WORKSPACE_PAYER]: Parameters.SetWorkspacePayerParams; [WRITE_COMMANDS.SET_WORKSPACE_REIMBURSEMENT]: Parameters.SetWorkspaceReimbursementParams; - [WRITE_COMMANDS.SET_WORKSPACE_REPORT_TITLE]: Parameters.SetWorkspaceReportTitleParams; + [WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE]: Parameters.SetPolicyDefaultReportTitleParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 80de07eef23f..6c354b47e8ac 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -1,17 +1,16 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {SetWorkspaceReportTitleParams} from '@libs/API/parameters'; +import type {SetPolicyDefaultReportTitleParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import ONYXKEYS from '@src/ONYXKEYS'; /** * Call the API to deactivate the card and request a new one - * @param cardID - id of the card that is going to be replaced - * @param reason - reason for replacement + * @param customName - name pattern to be used for the reports + * @param policyID - id of the policy to apply the naming pattern to */ function modifyExpenseReportsNames(customName: string, policyID: string) { - console.log('modifyExpenseReportsNames ', customName, ' ', policyID); const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -43,16 +42,16 @@ function modifyExpenseReportsNames(customName: string, policyID: string) { }, ]; - const parameters: SetWorkspaceReportTitleParams = { + const parameters: SetPolicyDefaultReportTitleParams = { value: customName, policyID, }; - // API.write(WRITE_COMMANDS.SET_WORKSPACE_REPORT_TITLE, parameters, { - // optimisticData, - // successData, - // failureData, - // }); + API.write(WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE, parameters, { + optimisticData, + successData, + failureData, + }); } // eslint-disable-next-line import/prefer-default-export diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 92bed7650750..4c60e5bcab4f 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import {useOnyx} from 'react-native-onyx'; import MenuItem from '@components/MenuItem'; import Section from '@components/Section'; import Switch from '@components/Switch'; @@ -6,6 +7,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; type ExpenseReportRulesSectionProps = { @@ -15,6 +17,7 @@ type ExpenseReportRulesSectionProps = { function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const optionItems = [ { @@ -39,7 +42,8 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { {}} - isOn={false} + disabled={!!policy?.fieldList?.deletable} + isOn={policy?.fieldList} /> } shouldShowRightComponent From 4d1510167b223ba0bbd1f2626d24cbc1232ca393 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 13 Aug 2024 14:05:02 +0200 Subject: [PATCH 019/502] cleanup custom names --- src/libs/actions/Workspace/Rules.ts | 4 ++-- src/pages/workspace/rules/RulesCustomNamePage.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 6c354b47e8ac..531a5975c4d0 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -10,7 +10,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; * @param customName - name pattern to be used for the reports * @param policyID - id of the policy to apply the naming pattern to */ -function modifyExpenseReportsNames(customName: string, policyID: string) { +function modifyPolicyDefaultReportTitle(customName: string, policyID: string) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -55,4 +55,4 @@ function modifyExpenseReportsNames(customName: string, policyID: string) { } // eslint-disable-next-line import/prefer-default-export -export {modifyExpenseReportsNames}; +export {modifyPolicyDefaultReportTitle}; diff --git a/src/pages/workspace/rules/RulesCustomNamePage.tsx b/src/pages/workspace/rules/RulesCustomNamePage.tsx index 2b6398f868f5..847c4e439c60 100644 --- a/src/pages/workspace/rules/RulesCustomNamePage.tsx +++ b/src/pages/workspace/rules/RulesCustomNamePage.tsx @@ -67,7 +67,7 @@ function RulesCustomNamePage({route}: RulesCustomNamePageProps) { style={[styles.flexGrow1, styles.mh5]} formID={ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM} // validate={validator} - onSubmit={({customName}) => WorkspaceRulesActions.modifyExpenseReportsNames(customName, policyID)} + onSubmit={({customName}) => WorkspaceRulesActions.modifyPolicyDefaultReportTitle(customName, policyID)} submitButtonText={translate('common.save')} enabledWhenOffline > From b4673ea694d87183e5269fe2d0e6e74f7885b160 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:58:16 +0100 Subject: [PATCH 020/502] Clear search value after removing room members --- src/pages/RoomMembersPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index dbb84a0b004a..a727d1d19fa3 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -137,6 +137,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { } setSelectedMembers([]); setRemoveMembersConfirmModalVisible(false); + setSearchValue(''); }; /** From 73a339bab1b74ca02d462902f7fa4979a4d750a2 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:10:58 +0100 Subject: [PATCH 021/502] Fix canSelectMultiple logic --- src/pages/RoomMembersPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index a727d1d19fa3..c8af45c948bd 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -356,7 +356,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { /> Date: Tue, 13 Aug 2024 20:51:50 +0200 Subject: [PATCH 022/502] wire up prevent member created title --- src/components/BulletList.tsx | 5 +- ...etPolicyPreventMemberCreatedTitleParams.ts | 6 ++ src/libs/API/types.ts | 1 + src/libs/actions/Workspace/Rules.ts | 65 +++++++++++++++++-- .../rules/ExpenseReportRulesSection.tsx | 24 ++++--- 5 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams.ts diff --git a/src/components/BulletList.tsx b/src/components/BulletList.tsx index ac6bc70bf314..bead1ece6728 100644 --- a/src/components/BulletList.tsx +++ b/src/components/BulletList.tsx @@ -28,7 +28,10 @@ function BulletList({items, header}: BulletListProps) { const renderBulletPoint = (item: string) => { return ( - + {item} diff --git a/src/libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams.ts b/src/libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams.ts new file mode 100644 index 000000000000..92f44aacff41 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams.ts @@ -0,0 +1,6 @@ +type SetPolicyPreventMemberCreatedTitleParams = { + policyID: string; + enforced: boolean; +}; + +export default SetPolicyPreventMemberCreatedTitleParams; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index eb7117bfdafd..6a98d17e037b 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -15,6 +15,7 @@ const WRITE_COMMANDS = { SET_WORKSPACE_PAYER: 'SetWorkspacePayer', SET_WORKSPACE_REIMBURSEMENT: 'SetWorkspaceReimbursement', SET_POLICY_DEFAULT_REPORT_TITLE: 'SetPolicyDefaultReportTitle', + SET_POLICY_PREVENT_MEMBER_CREATED_TITLE: 'SetPolicyPreventMemberCreatedTitle', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 531a5975c4d0..874dff9fd91f 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -2,7 +2,9 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; import type {SetPolicyDefaultReportTitleParams} from '@libs/API/parameters'; +import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams'; import {WRITE_COMMANDS} from '@libs/API/types'; +import en from '@src/languages/en'; import ONYXKEYS from '@src/ONYXKEYS'; /** @@ -47,12 +49,61 @@ function modifyPolicyDefaultReportTitle(customName: string, policyID: string) { policyID, }; - API.write(WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE, parameters, { - optimisticData, - successData, - failureData, - }); + // API.write(WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE, parameters, { + // optimisticData, + // successData, + // failureData, + // }); } -// eslint-disable-next-line import/prefer-default-export -export {modifyPolicyDefaultReportTitle}; +/** + * Call the API to deactivate the card and request a new one + * @param enforced - flag whether to enforce policy name + * @param policyID - id of the policy to apply the naming pattern to + */ +function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) { + console.log('setPolicyPreventMemberCreatedTitle ', enforced); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: true, + errors: null, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const parameters: SetPolicyPreventMemberCreatedTitleParams = { + enforced, + policyID, + }; + + // API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_MEMBER_CREATED_TITLE, parameters, { + // optimisticData, + // successData, + // failureData, + // }); +} + +export {modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle}; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 4c60e5bcab4f..a4d2a5cda6c6 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -7,6 +7,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; +import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -34,20 +35,17 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { style={[styles.sectionMenuItemTopDescription, styles.mt6, styles.mbn3]} onPress={() => Navigation.navigate(ROUTES.RULES_CUSTOM_NAME.getRoute(policyID))} />, - {}} - rightComponent={ - {}} - disabled={!!policy?.fieldList?.deletable} - isOn={policy?.fieldList} - /> - } - shouldShowRightComponent - style={[styles.sectionMenuItemTopDescription, styles.mt6, styles.mbn3]} + switchAccessibilityLabel={translate('workspace.rules.expenseReportRules.preventMembersFromChangingCustomNamesTitle')} + wrapperStyle={[styles.sectionMenuItemTopDescription, {marginTop: 24}]} + titleStyle={{paddingVertical: 10}} + // titleStyle={styles.pv2} + // subtitleStyle={styles.pt1} + isActive={false} + onToggle={(isOn) => WorkspaceRulesActions.setPolicyPreventMemberCreatedTitle(!isOn, policyID)} + // disabled={!!policy?.fieldList?.deletable} + // isOn={false} />, ], }, From 6be513d30abc6dcb8167f93e099d9081a6463502 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 13 Aug 2024 23:13:30 +0200 Subject: [PATCH 023/502] wire up prevent self approval --- .../SetPolicyPreventSelfApprovalParams.ts | 6 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Workspace/Rules.ts | 54 +++++++++++++++++-- .../rules/ExpenseReportRulesSection.tsx | 2 +- 5 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyPreventSelfApprovalParams.ts diff --git a/src/libs/API/parameters/SetPolicyPreventSelfApprovalParams.ts b/src/libs/API/parameters/SetPolicyPreventSelfApprovalParams.ts new file mode 100644 index 000000000000..7b8398905fee --- /dev/null +++ b/src/libs/API/parameters/SetPolicyPreventSelfApprovalParams.ts @@ -0,0 +1,6 @@ +type SetPolicyPreventSelfApprovalParams = { + policyID: string; + preventSelfApproval: boolean; +}; + +export default SetPolicyPreventSelfApprovalParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index e2be10bda1ec..8078ff8458ef 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -271,3 +271,4 @@ export type {CreateWorkspaceApprovalParams, UpdateWorkspaceApprovalParams, Remov export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFlowParams'; export type {default as SetPolicyRulesEnabledParams} from './SetPolicyRulesEnabledParams'; export type {default as SetPolicyDefaultReportTitleParams} from './SetPolicyDefaultReportTitleParams'; +export type {default as SetPolicyPreventSelfApprovalParams} from './SetPolicyPreventSelfApprovalParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 6a98d17e037b..19af3be579ea 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -16,6 +16,7 @@ const WRITE_COMMANDS = { SET_WORKSPACE_REIMBURSEMENT: 'SetWorkspaceReimbursement', SET_POLICY_DEFAULT_REPORT_TITLE: 'SetPolicyDefaultReportTitle', SET_POLICY_PREVENT_MEMBER_CREATED_TITLE: 'SetPolicyPreventMemberCreatedTitle', + SET_POLICY_PREVENT_SELF_APPROVAL: 'SetPolicyPreventSelfApproval', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -511,6 +512,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_PAYER]: Parameters.SetWorkspacePayerParams; [WRITE_COMMANDS.SET_WORKSPACE_REIMBURSEMENT]: Parameters.SetWorkspaceReimbursementParams; [WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE]: Parameters.SetPolicyDefaultReportTitleParams; + [WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL]: Parameters.SetPolicyPreventSelfApprovalParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 874dff9fd91f..d842f61871de 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -1,10 +1,9 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {SetPolicyDefaultReportTitleParams} from '@libs/API/parameters'; +import type {SetPolicyDefaultReportTitleParams, SetPolicyPreventSelfApprovalParams} from '@libs/API/parameters'; import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams'; import {WRITE_COMMANDS} from '@libs/API/types'; -import en from '@src/languages/en'; import ONYXKEYS from '@src/ONYXKEYS'; /** @@ -106,4 +105,53 @@ function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) // }); } -export {modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle}; +/** + * Call the API to deactivate the card and request a new one + * @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports + * @param policyID - id of the policy to apply the naming pattern to + */ +function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: true, + errors: null, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const parameters: SetPolicyPreventSelfApprovalParams = { + preventSelfApproval, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL, parameters, { + optimisticData, + successData, + failureData, + }); +} + +export {modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval}; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index a4d2a5cda6c6..82e2d01908c6 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -53,7 +53,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { title: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), subtitle: translate('workspace.rules.expenseReportRules.preventSelfApprovalsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), - onToggle: (isEnabled: boolean) => {}, + onToggle: (isEnabled: boolean) => WorkspaceRulesActions.setPolicyPreventSelfApproval(isEnabled, policyID), }, { title: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), From 2169b1787df656a97c8af309a481df7ad9ed55c9 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 14 Aug 2024 13:41:11 +0200 Subject: [PATCH 024/502] add remaining routes --- src/ROUTES.ts | 12 +++ src/SCREENS.ts | 3 + src/languages/en.ts | 3 + .../ModalStackNavigators/index.tsx | 3 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 7 +- src/libs/Navigation/linkingConfig/config.ts | 9 ++ src/libs/Navigation/types.ts | 9 ++ .../rules/ExpenseReportRulesSection.tsx | 30 +++++- .../RulesAutoApproveReportsUnderPage.tsx | 74 +++++++++++++++ .../rules/RulesAutoPayReportsUnderPage.tsx | 94 +++++++++++++++++++ .../rules/RulesRandomReportAuditPage.tsx | 94 +++++++++++++++++++ 11 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx create mode 100644 src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx create mode 100644 src/pages/workspace/rules/RulesRandomReportAuditPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 3054b8d36531..ce81e33a1e51 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -956,6 +956,18 @@ const ROUTES = { route: 'settings/workspaces/:policyID/rules/name', getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/name` as const, }, + RULES_AUTO_APPROVE_REPORTS_UNDER: { + route: 'settings/workspaces/:policyID/rules/auto-approve', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/auto-approve` as const, + }, + RULES_RANDOM_REPORT_AUDIT: { + route: 'settings/workspaces/:policyID/rules/audit', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/audit` as const, + }, + RULES_AUTO_PAY_REPORTS_UNDER: { + route: 'settings/workspaces/:policyID/rules/auto-pay', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/auto-pay` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { route: 'referral/:contentType', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 0cb3662b10f8..963396335ae4 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -436,6 +436,9 @@ const SCREENS = { UPGRADE: 'Workspace_Upgrade', RULES: 'Policy_Rules', RULES_CUSTOM_NAME: 'Rules_Custom_Name', + RULES_AUTO_APPROVE_REPORTS_UNDER: 'Rules_Auto_Approve_Reports_Under', + RULES_RANDOM_REPORT_AUDIT: 'Rules_Random_Report_Audit', + RULES_AUTO_PAY_REPORTS_UNDER: 'Rules_AutoPay_Reports_Under', }, EDIT_REQUEST: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 9bfefe4e0ded..9782a82ab933 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3547,8 +3547,11 @@ export default { preventSelfApprovalsSubtitle: 'Prevent workspace members from approving their own expense reports.', autoApproveCompliantReportsTitle: 'Auto-approve compliant reports', autoApproveCompliantReportsSubtitle: 'Configure which expense reports are eligible for auto-approval.', + autoApproveReportsUnderTitle: 'Auto-approve reports under', + randomReportAuditTitle: 'Random report audit', autoPayApprovedReportsTitle: 'Auto-pay approved reports', autoPayApprovedReportsSubtitle: 'Configure which expense reports are eligible for auto-pay.', + autoPayReportsUnderTitle: 'Auto-pay reports under', }, }, }, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 332afd9dd1ad..c763b02da258 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -452,6 +452,9 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/accounting/intacct/import/SageIntacctEditUserDimensionsPage').default, [SCREENS.WORKSPACE.RULES_CUSTOM_NAME]: () => require('../../../../pages/workspace/rules/RulesCustomNamePage').default, + [SCREENS.WORKSPACE.RULES_AUTO_APPROVE_REPORTS_UNDER]: () => require('../../../../pages/workspace/rules/RulesAutoApproveReportsUnderPage').default, + [SCREENS.WORKSPACE.RULES_RANDOM_REPORT_AUDIT]: () => require('../../../../pages/workspace/rules/RulesRandomReportAuditPage').default, + [SCREENS.WORKSPACE.RULES_AUTO_PAY_REPORTS_UNDER]: () => require('../../../../pages/workspace/rules/RulesAutoPayReportsUnderPage').default, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index ea9a56cb873b..919bf97db681 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -170,7 +170,12 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.EXPENSIFY_CARD_LIMIT, SCREENS.WORKSPACE.EXPENSIFY_CARD_LIMIT_TYPE, ], - [SCREENS.WORKSPACE.RULES]: [SCREENS.WORKSPACE.RULES_CUSTOM_NAME], + [SCREENS.WORKSPACE.RULES]: [ + SCREENS.WORKSPACE.RULES_CUSTOM_NAME, + SCREENS.WORKSPACE.RULES_AUTO_APPROVE_REPORTS_UNDER, + SCREENS.WORKSPACE.RULES_RANDOM_REPORT_AUDIT, + SCREENS.WORKSPACE.RULES_AUTO_PAY_REPORTS_UNDER, + ], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 282c4d566117..9fba0d9b6d85 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -738,6 +738,15 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.RULES_CUSTOM_NAME]: { path: ROUTES.RULES_CUSTOM_NAME.route, }, + [SCREENS.WORKSPACE.RULES_AUTO_APPROVE_REPORTS_UNDER]: { + path: ROUTES.RULES_AUTO_APPROVE_REPORTS_UNDER.route, + }, + [SCREENS.WORKSPACE.RULES_RANDOM_REPORT_AUDIT]: { + path: ROUTES.RULES_RANDOM_REPORT_AUDIT.route, + }, + [SCREENS.WORKSPACE.RULES_AUTO_PAY_REPORTS_UNDER]: { + path: ROUTES.RULES_AUTO_PAY_REPORTS_UNDER.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c6e6ee0029dd..ef5e37a650ab 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -700,6 +700,15 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.RULES_CUSTOM_NAME]: { policyID: string; }; + [SCREENS.WORKSPACE.RULES_AUTO_APPROVE_REPORTS_UNDER]: { + policyID: string; + }; + [SCREENS.WORKSPACE.RULES_RANDOM_REPORT_AUDIT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.RULES_AUTO_PAY_REPORTS_UNDER]: { + policyID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 82e2d01908c6..f8aa7c3e8d0f 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -2,7 +2,6 @@ import React from 'react'; import {useOnyx} from 'react-native-onyx'; import MenuItem from '@components/MenuItem'; import Section from '@components/Section'; -import Switch from '@components/Switch'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; @@ -25,7 +24,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { title: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), subtitle: translate('workspace.rules.expenseReportRules.customReportNamesSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), - isActive: true, + isActive: false, onToggle: (isEnabled: boolean) => {}, subMenuItems: [ {}, + subMenuItems: [ + Navigation.navigate(ROUTES.RULES_AUTO_APPROVE_REPORTS_UNDER.getRoute(policyID))} + />, + Navigation.navigate(ROUTES.RULES_RANDOM_REPORT_AUDIT.getRoute(policyID))} + />, + ], }, { @@ -67,6 +83,16 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { subtitle: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), onToggle: (isEnabled: boolean) => {}, + isActive: true, + subMenuItems: [ + Navigation.navigate(ROUTES.RULES_AUTO_PAY_REPORTS_UNDER.getRoute(policyID))} + />, + ], }, ]; diff --git a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx new file mode 100644 index 000000000000..6e4555a77d5b --- /dev/null +++ b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx @@ -0,0 +1,74 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {View} from 'react-native'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; + +type RulesAutoApproveReportsUnderPageProps = StackScreenProps; + +function RulesAutoApproveReportsUnderPage({route}: RulesAutoApproveReportsUnderPageProps) { + const {policyID} = route.params; + + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + return ( + + + Navigation.goBack()} + /> + + + {translate('workspace.rules.expenseReportRules.customNameDescription')} + + {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} + + . + + + {/* WorkspaceRulesActions.modifyPolicyDefaultReportTitle(customName, policyID)} + submitButtonText={translate('common.save')} + enabledWhenOffline + > + + */} + + + ); +} + +RulesAutoApproveReportsUnderPage.displayName = 'RulesAutoApproveReportsUnderPage'; + +export default RulesAutoApproveReportsUnderPage; diff --git a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx new file mode 100644 index 000000000000..f651e8d0ba6f --- /dev/null +++ b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx @@ -0,0 +1,94 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {View} from 'react-native'; +import BulletList from '@components/BulletList'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/RulesCustomNameModalForm'; + +type RulesAutoPayReportsUnderPageProps = StackScreenProps; + +function RulesAutoPayReportsUnderPage({route}: RulesAutoPayReportsUnderPageProps) { + const {policyID} = route.params; + + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const RULE_EXAMPLE_BULLET_POINTS = [ + translate('workspace.rules.expenseReportRules.customNameEmailPhoneExample'), + translate('workspace.rules.expenseReportRules.customNameStartDateExample'), + translate('workspace.rules.expenseReportRules.customNameWorkspaceNameExample'), + translate('workspace.rules.expenseReportRules.customNameReportIDExample'), + translate('workspace.rules.expenseReportRules.customNameTotalExample'), + ] as const satisfies string[]; + + return ( + + + Navigation.goBack()} + /> + + + {translate('workspace.rules.expenseReportRules.customNameDescription')} + + {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} + + . + + + WorkspaceRulesActions.modifyPolicyDefaultReportTitle(customName, policyID)} + submitButtonText={translate('common.save')} + enabledWhenOffline + > + + + + + + + ); +} + +RulesAutoPayReportsUnderPage.displayName = 'RulesAutoPayReportsUnderPage'; + +export default RulesAutoPayReportsUnderPage; diff --git a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx new file mode 100644 index 000000000000..e5587ec16c2c --- /dev/null +++ b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx @@ -0,0 +1,94 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {View} from 'react-native'; +import BulletList from '@components/BulletList'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/RulesCustomNameModalForm'; + +type RulesRandomReportAuditPageProps = StackScreenProps; + +function RulesRandomReportAuditPage({route}: RulesRandomReportAuditPageProps) { + const {policyID} = route.params; + + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const RULE_EXAMPLE_BULLET_POINTS = [ + translate('workspace.rules.expenseReportRules.customNameEmailPhoneExample'), + translate('workspace.rules.expenseReportRules.customNameStartDateExample'), + translate('workspace.rules.expenseReportRules.customNameWorkspaceNameExample'), + translate('workspace.rules.expenseReportRules.customNameReportIDExample'), + translate('workspace.rules.expenseReportRules.customNameTotalExample'), + ] as const satisfies string[]; + + return ( + + + Navigation.goBack()} + /> + + + {translate('workspace.rules.expenseReportRules.customNameDescription')} + + {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} + + . + + + WorkspaceRulesActions.modifyPolicyDefaultReportTitle(customName, policyID)} + submitButtonText={translate('common.save')} + enabledWhenOffline + > + + + + + + + ); +} + +RulesRandomReportAuditPage.displayName = 'RulesRandomReportAuditPage'; + +export default RulesRandomReportAuditPage; From 132ccbbdf3839150a4b5fa958fd6e2efba89c876 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 14 Aug 2024 15:43:48 +0200 Subject: [PATCH 025/502] auto approve reports under amount page wip --- src/CONST.ts | 1 + src/ONYXKEYS.ts | 3 + src/components/AmountForm.tsx | 51 +++++++++++++++- src/languages/en.ts | 2 + .../rules/ExpenseReportRulesSection.tsx | 2 +- .../RulesAutoApproveReportsUnderPage.tsx | 58 ++++++++++--------- .../RulesAutoApproveReportsUnderModalForm.ts | 18 ++++++ src/types/form/index.ts | 1 + 8 files changed, 108 insertions(+), 28 deletions(-) create mode 100644 src/types/form/RulesAutoApproveReportsUnderModalForm.ts diff --git a/src/CONST.ts b/src/CONST.ts index 40252b445c6b..a3e1c811882f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1184,6 +1184,7 @@ const CONST = { VISIBLE_PASSWORD: 'visible-password', ASCII_CAPABLE: 'ascii-capable', NUMBER_PAD: 'number-pad', + DECIMAL_PAD: 'decimal-pad', }, INPUT_MODE: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8fcee07a9473..04be910e0aa7 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -614,6 +614,8 @@ const ONYXKEYS = { TEXT_PICKER_MODAL_FORM_DRAFT: 'textPickerModalFormDraft', RULES_CUSTOM_NAME_MODAL_FORM: 'rulesCustomNameModalForm', RULES_CUSTOM_NAME_MODAL_FORM_DRAFT: 'rulesCustomNameModalFormDraft', + RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM: 'rulesAutoApproveReportsUnderModalForm', + RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM_DRAFT: 'rulesAutoApproveReportsUnderModalFormDraft', }, } as const; @@ -690,6 +692,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM]: FormTypes.SearchAdvancedFiltersForm; [ONYXKEYS.FORMS.TEXT_PICKER_MODAL_FORM]: FormTypes.TextPickerModalForm; [ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM]: FormTypes.RulesCustomNameModalForm; + [ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM]: FormTypes.RulesAutoApproveReportsUnderModalForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 1eb272dce49a..2545364aa701 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -12,6 +12,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; import BigNumberPad from './BigNumberPad'; import FormHelpMessage from './FormHelpMessage'; +import TextInput from './TextInput'; import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; @@ -41,6 +42,10 @@ type AmountFormProps = { /** Custom max amount length. It defaults to CONST.IOU.AMOUNT_MAX_LENGTH */ amountMaxLength?: number; + + label?: string; + + displayAsTextInput?: boolean; } & Pick & Pick; @@ -57,7 +62,19 @@ const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; function AmountForm( - {value: amount, currency = CONST.CURRENCY.USD, extraDecimals = 0, amountMaxLength, errorText, onInputChange, onCurrencyButtonPress, isCurrencyPressable = true, ...rest}: AmountFormProps, + { + value: amount, + currency = CONST.CURRENCY.USD, + extraDecimals = 0, + amountMaxLength, + errorText, + onInputChange, + onCurrencyButtonPress, + displayAsTextInput = false, + isCurrencyPressable = true, + label, + ...rest + }: AmountFormProps, forwardedRef: ForwardedRef, ) { const styles = useThemeStyles(); @@ -195,6 +212,38 @@ function AmountForm( const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); + if (displayAsTextInput) { + return ( + { + if (typeof forwardedRef === 'function') { + forwardedRef(ref); + } else if (forwardedRef && 'current' in forwardedRef) { + // eslint-disable-next-line no-param-reassign + forwardedRef.current = ref; + } + textInput.current = ref; + }} + selection={selection} + onSelectionChange={(e: NativeSyntheticEvent) => { + if (!shouldUpdateSelection) { + return; + } + setSelection(e.nativeEvent.selection); + }} + prefixCharacter={currency} + prefixStyle={styles.colorMuted} + keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD} + // eslint-disable-next-line react/jsx-props-no-spreading + {...rest} + /> + ); + } + return ( <> {}, - isActive: true, + isActive: false, subMenuItems: [ ; function RulesAutoApproveReportsUnderPage({route}: RulesAutoApproveReportsUnderPageProps) { const {policyID} = route.params; + const {inputCallbackRef} = useAutoFocusInput(); const {translate} = useLocalize(); const styles = useThemeStyles(); + // const defaultValue = CurrencyUtils.convertToFrontendAmountAsString(policy?.maxExpenseAmountNoReceipt, policy?.outputCurrency); + return ( Navigation.goBack()} /> - - - {translate('workspace.rules.expenseReportRules.customNameDescription')} - - {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} - - . - - - {/* WorkspaceRulesActions.modifyPolicyDefaultReportTitle(customName, policyID)} + onSubmit={({amount}) => {}} submitButtonText={translate('common.save')} enabledWhenOffline > - - */} + + + {translate('workspace.rules.expenseReportRules.autoApproveReportsUnderDescription')} + + ); diff --git a/src/types/form/RulesAutoApproveReportsUnderModalForm.ts b/src/types/form/RulesAutoApproveReportsUnderModalForm.ts new file mode 100644 index 000000000000..23371595ed59 --- /dev/null +++ b/src/types/form/RulesAutoApproveReportsUnderModalForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + MAX_EXPENSE_AUTO_APPROVAL_AMOUNT: 'maxExpenseAutoApprovalAmount', +} as const; + +type InputID = ValueOf; + +type RulesAutoApproveReportsUnderModalForm = Form< + InputID, + { + [INPUT_IDS.MAX_EXPENSE_AUTO_APPROVAL_AMOUNT]: string; + } +>; + +export type {RulesAutoApproveReportsUnderModalForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 0dae33578242..1a8f3f57041d 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -66,5 +66,6 @@ export type {NetSuiteCustomFormIDForm} from './NetSuiteCustomFormIDForm'; export type {SearchAdvancedFiltersForm} from './SearchAdvancedFiltersForm'; export type {EditExpensifyCardLimitForm} from './EditExpensifyCardLimitForm'; export type {RulesCustomNameModalForm} from './RulesCustomNameModalForm'; +export type {RulesAutoApproveReportsUnderModalForm} from './RulesAutoApproveReportsUnderModalForm'; export type {default as TextPickerModalForm} from './TextPickerModalForm'; export type {default as Form} from './Form'; From 285cf5e263b205b58bfe227a6ef0242eae2f4cd2 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 14 Aug 2024 16:17:20 +0200 Subject: [PATCH 026/502] wire up auto approval --- .../SetPolicyAutomaticApprovalLimit.ts | 6 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Workspace/Rules.ts | 53 ++++++++++++++++++- .../RulesAutoApproveReportsUnderPage.tsx | 3 +- 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyAutomaticApprovalLimit.ts diff --git a/src/libs/API/parameters/SetPolicyAutomaticApprovalLimit.ts b/src/libs/API/parameters/SetPolicyAutomaticApprovalLimit.ts new file mode 100644 index 000000000000..bc9c07570f97 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyAutomaticApprovalLimit.ts @@ -0,0 +1,6 @@ +type SetPolicyAutomaticApprovalLimitParams = { + policyID: string; + limit: number; +}; + +export default SetPolicyAutomaticApprovalLimitParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 8078ff8458ef..b962623594c2 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -272,3 +272,4 @@ export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFl export type {default as SetPolicyRulesEnabledParams} from './SetPolicyRulesEnabledParams'; export type {default as SetPolicyDefaultReportTitleParams} from './SetPolicyDefaultReportTitleParams'; export type {default as SetPolicyPreventSelfApprovalParams} from './SetPolicyPreventSelfApprovalParams'; +export type {default as SetPolicyAutomaticApprovalLimitParams} from './SetPolicyAutomaticApprovalLimit'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 19af3be579ea..c805f0cf6a76 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -17,6 +17,7 @@ const WRITE_COMMANDS = { SET_POLICY_DEFAULT_REPORT_TITLE: 'SetPolicyDefaultReportTitle', SET_POLICY_PREVENT_MEMBER_CREATED_TITLE: 'SetPolicyPreventMemberCreatedTitle', SET_POLICY_PREVENT_SELF_APPROVAL: 'SetPolicyPreventSelfApproval', + SET_POLICY_AUTOMATIC_APPROVAL_LIMIT: 'SetPolicyAutomaticApprovalLimit', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -513,6 +514,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_REIMBURSEMENT]: Parameters.SetWorkspaceReimbursementParams; [WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE]: Parameters.SetPolicyDefaultReportTitleParams; [WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL]: Parameters.SetPolicyPreventSelfApprovalParams; + [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT]: Parameters.SetPolicyAutomaticApprovalLimitParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index d842f61871de..32965a330133 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -1,7 +1,7 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {SetPolicyDefaultReportTitleParams, SetPolicyPreventSelfApprovalParams} from '@libs/API/parameters'; +import type {SetPolicyAutomaticApprovalLimitParams, SetPolicyDefaultReportTitleParams, SetPolicyPreventSelfApprovalParams} from '@libs/API/parameters'; import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams'; import {WRITE_COMMANDS} from '@libs/API/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -154,4 +154,53 @@ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: st }); } -export {modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval}; +/** + * Call the API to deactivate the card and request a new one + * @param limit - max amount for auto-approval of the reports in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function setPolicyAutomaticApprovalLimit(limit: number, policyID: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM, + value: { + isLoading: true, + errors: null, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const parameters: SetPolicyAutomaticApprovalLimitParams = { + limit, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT, parameters, { + optimisticData, + successData, + failureData, + }); +} + +export {modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, setPolicyAutomaticApprovalLimit}; diff --git a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx index 0990bbe2d1ff..4328e6e7ea64 100644 --- a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx @@ -14,6 +14,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -49,7 +50,7 @@ function RulesAutoApproveReportsUnderPage({route}: RulesAutoApproveReportsUnderP style={[styles.flexGrow1, styles.mh5, styles.mt5]} formID={ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM} // validate={validator} - onSubmit={({amount}) => {}} + onSubmit={({maxExpenseAutoApprovalAmount}) => WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(parseInt(maxExpenseAutoApprovalAmount, 10), policyID)} submitButtonText={translate('common.save')} enabledWhenOffline > From 5797fb181714d3eb81f8bd1421262b53559033c4 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 14 Aug 2024 23:01:27 +0200 Subject: [PATCH 027/502] add percentage form --- src/components/Form/types.ts | 2 + src/components/PercentageForm.tsx | 101 ++++++++++++++++++ .../TextInput/BaseTextInput/index.tsx | 14 +++ .../TextInput/BaseTextInput/types.ts | 16 ++- src/languages/en.ts | 2 + src/libs/MoneyRequestUtils.ts | 22 +++- .../rules/ExpenseReportRulesSection.tsx | 1 - .../rules/RulesRandomReportAuditPage.tsx | 58 ++++------ src/styles/index.ts | 42 ++++++-- 9 files changed, 203 insertions(+), 55 deletions(-) create mode 100644 src/components/PercentageForm.tsx diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 5f56bbeceea6..88ccc31c0979 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -11,6 +11,7 @@ import type CountrySelector from '@components/CountrySelector'; import type CurrencySelector from '@components/CurrencySelector'; import type DatePicker from '@components/DatePicker'; import type EmojiPickerButtonDropdown from '@components/EmojiPicker/EmojiPickerButtonDropdown'; +import type PercentageForm from '@components/PercentageForm'; import type Picker from '@components/Picker'; import type RadioButtons from '@components/RadioButtons'; import type RoomNameInput from '@components/RoomNameInput'; @@ -42,6 +43,7 @@ type ValidInputs = | typeof CountrySelector | typeof CurrencySelector | typeof AmountForm + | typeof PercentageForm | typeof BusinessTypePicker | typeof DimensionTypeSelector | typeof StateSelector diff --git a/src/components/PercentageForm.tsx b/src/components/PercentageForm.tsx new file mode 100644 index 000000000000..a7bd2e2bb67d --- /dev/null +++ b/src/components/PercentageForm.tsx @@ -0,0 +1,101 @@ +import type {ForwardedRef} from 'react'; +import React, {forwardRef, useCallback, useMemo, useRef, useState} from 'react'; +import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; +import CONST from '@src/CONST'; +import TextInput from './TextInput'; +import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; + +type PercentageFormProps = { + /** Amount supplied by the FormProvider */ + value?: string; + + /** Error to display at the bottom of the component */ + errorText?: string; + + /** Callback to update the amount in the FormProvider */ + onInputChange?: (value: string) => void; + + label?: string; +}; + +/** + * Returns the new selection object based on the updated amount's length + */ +const getNewSelection = (oldSelection: {start: number; end: number}, prevLength: number, newLength: number) => { + const cursorPosition = oldSelection.end + (newLength - prevLength); + return {start: cursorPosition, end: cursorPosition}; +}; + +function PercentageForm({value: amount, errorText, onInputChange, label, ...rest}: PercentageFormProps, forwardedRef: ForwardedRef) { + const {toLocaleDigit, numberFormat} = useLocalize(); + + const textInput = useRef(null); + + const currentAmount = useMemo(() => (typeof amount === 'string' ? amount : ''), [amount]); + + const [selection, setSelection] = useState({ + start: currentAmount.length, + end: currentAmount.length, + }); + + const forwardDeletePressedRef = useRef(false); + + /** + * Sets the selection and the amount accordingly to the value passed to the input + * @param newAmount - Changed amount from user input + */ + const setNewAmount = useCallback( + (newAmount: string) => { + // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value + // More info: https://github.com/Expensify/App/issues/16974 + const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount); + // Use a shallow copy of selection to trigger setSelection + // More info: https://github.com/Expensify/App/issues/16385 + if (!MoneyRequestUtils.validatePercentage(newAmountWithoutSpaces)) { + setSelection((prevSelection) => ({...prevSelection})); + return; + } + + const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); + const isForwardDelete = currentAmount.length > strippedAmount.length && forwardDeletePressedRef.current; + setSelection(getNewSelection(selection, isForwardDelete ? strippedAmount.length : currentAmount.length, strippedAmount.length)); + onInputChange?.(strippedAmount); + }, + [currentAmount, onInputChange, selection], + ); + + const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); + + return ( + { + if (typeof forwardedRef === 'function') { + forwardedRef(ref); + } else if (forwardedRef && 'current' in forwardedRef) { + // eslint-disable-next-line no-param-reassign + forwardedRef.current = ref; + } + textInput.current = ref; + }} + selection={selection} + onSelectionChange={(e: NativeSyntheticEvent) => { + setSelection(e.nativeEvent.selection); + }} + suffixCharacter="%" + keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD} + // eslint-disable-next-line react/jsx-props-no-spreading + {...rest} + /> + ); +} + +PercentageForm.displayName = 'PercentageForm'; + +export default forwardRef(PercentageForm); +export type {PercentageFormProps}; diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 685d54d86765..d6458910715d 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -61,11 +61,14 @@ function BaseTextInput( shouldInterceptSwipe = false, autoCorrect = true, prefixCharacter = '', + suffixCharacter = '', inputID, isMarkdownEnabled = false, shouldShowClearButton = false, prefixContainerStyle = [], prefixStyle = [], + suffixContainerStyle = [], + suffixStyle = [], contentWidth, ...inputProps }: BaseTextInputProps, @@ -402,6 +405,17 @@ function BaseTextInput( defaultValue={defaultValue} markdownStyle={markdownStyle} /> + {!!suffixCharacter && ( + + + {suffixCharacter} + + + )} {isFocused && !isReadOnly && shouldShowClearButton && !!value && setValue('')} />} {inputProps.isLoading && ( ; + /** Style for the suffix */ + suffixStyle?: StyleProp; + + /** Style for the suffix container */ + suffixContainerStyle?: StyleProp; + /** The width of inner content */ contentWidth?: number; }; @@ -129,4 +139,4 @@ type BaseTextInputRef = HTMLFormElement | AnimatedTextInputRef; type BaseTextInputProps = CustomBaseTextInputProps & TextInputProps; -export type {BaseTextInputProps, BaseTextInputRef, CustomBaseTextInputProps}; +export type {BaseTextInputProps, BaseTextInputRef, CustomBaseTextInputProps}; \ No newline at end of file diff --git a/src/languages/en.ts b/src/languages/en.ts index 531371a232d5..371ae1932dbf 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -248,6 +248,7 @@ export default { conjunctionAt: 'at', conjunctionTo: 'to', genericErrorMessage: 'Oops... something went wrong and your request could not be completed. Please try again later.', + percentage: 'Percentage', error: { invalidAmount: 'Invalid amount.', acceptTerms: 'You must accept the Terms of Service to continue.', @@ -3551,6 +3552,7 @@ export default { autoApproveReportsUnderTitle: 'Auto-approve reports under', autoApproveReportsUnderDescription: 'Fully compliant expense reports under this amount will be automatically approved.', randomReportAuditTitle: 'Random report audit', + randomReportAuditDescription: 'Require that some reports be manually approved, even if eligible for auto-approval.', autoPayApprovedReportsTitle: 'Auto-pay approved reports', autoPayApprovedReportsSubtitle: 'Configure which expense reports are eligible for auto-pay.', autoPayReportsUnderTitle: 'Auto-pay reports under', diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index 67ba9f62421d..206bb8509af6 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -49,6 +49,15 @@ function validateAmount(amount: string, decimals: number, amountMaxLength: numbe return amount === '' || decimalNumberRegex.test(amount); } +/** + * Check if percentage is between 0 and 100 + */ +function validatePercentage(amount: string): boolean { + const regexString = '^(100|[0-9]{1,2})$'; + const percentageRegex = new RegExp(regexString, 'i'); + return amount === '' || percentageRegex.test(amount); +} + /** * Replaces each character by calling `convertFn`. If `convertFn` throws an error, then * the original character will be preserved. @@ -80,4 +89,15 @@ function isScanRequest(selectedTab: SelectedTabRequest): boolean { return selectedTab === CONST.TAB_REQUEST.SCAN; } -export {addLeadingZero, isDistanceRequest, isScanRequest, replaceAllDigits, stripCommaFromAmount, stripDecimalsFromAmount, stripSpacesFromAmount, replaceCommasWithPeriod, validateAmount}; +export { + addLeadingZero, + isDistanceRequest, + isScanRequest, + replaceAllDigits, + stripCommaFromAmount, + stripDecimalsFromAmount, + stripSpacesFromAmount, + replaceCommasWithPeriod, + validateAmount, + validatePercentage, +}; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 3326d2dd7a92..a00158cdf145 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -77,7 +77,6 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { />, ], }, - { title: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), subtitle: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsSubtitle'), diff --git a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx index e5587ec16c2c..51c0a3f49939 100644 --- a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx +++ b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx @@ -1,16 +1,16 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; -import BulletList from '@components/BulletList'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import PercentageForm from '@components/PercentageForm'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; -import TextInput from '@components/TextInput'; -import TextLink from '@components/TextLink'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; @@ -18,23 +18,18 @@ import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import INPUT_IDS from '@src/types/form/RulesCustomNameModalForm'; +import INPUT_IDS from '@src/types/form/RulesAutoApproveReportsUnderModalForm'; type RulesRandomReportAuditPageProps = StackScreenProps; function RulesRandomReportAuditPage({route}: RulesRandomReportAuditPageProps) { const {policyID} = route.params; + const {inputCallbackRef} = useAutoFocusInput(); const {translate} = useLocalize(); const styles = useThemeStyles(); - const RULE_EXAMPLE_BULLET_POINTS = [ - translate('workspace.rules.expenseReportRules.customNameEmailPhoneExample'), - translate('workspace.rules.expenseReportRules.customNameStartDateExample'), - translate('workspace.rules.expenseReportRules.customNameWorkspaceNameExample'), - translate('workspace.rules.expenseReportRules.customNameReportIDExample'), - translate('workspace.rules.expenseReportRules.customNameTotalExample'), - ] as const satisfies string[]; + // const defaultValue = CurrencyUtils.convertToFrontendAmountAsString(policy?.maxExpenseAmountNoReceipt, policy?.outputCurrency); return ( Navigation.goBack()} /> - - - {translate('workspace.rules.expenseReportRules.customNameDescription')} - - {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} - - . - - WorkspaceRulesActions.modifyPolicyDefaultReportTitle(customName, policyID)} + onSubmit={({maxExpenseAutoApprovalAmount}) => WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(parseInt(maxExpenseAutoApprovalAmount, 10), policyID)} submitButtonText={translate('common.save')} enabledWhenOffline > - - - + + + {translate('workspace.rules.expenseReportRules.randomReportAuditDescription')} + diff --git a/src/styles/index.ts b/src/styles/index.ts index 6095855bbab2..0c19753e6203 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1,19 +1,19 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import type {LineLayerStyleProps} from '@rnmapbox/maps/src/utils/MapboxStyles'; +import type { LineLayerStyleProps } from '@rnmapbox/maps/src/utils/MapboxStyles'; import lodashClamp from 'lodash/clamp'; -import type {LineLayer} from 'react-map-gl'; -import type {AnimatableNumericValue, Animated, ImageStyle, TextStyle, ViewStyle} from 'react-native'; -import {StyleSheet} from 'react-native'; -import type {CustomAnimation} from 'react-native-animatable'; -import type {PickerStyle} from 'react-native-picker-select'; -import type {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; -import type {ValueOf} from 'type-fest'; +import type { LineLayer } from 'react-map-gl'; +import type { AnimatableNumericValue, Animated, ImageStyle, TextStyle, ViewStyle } from 'react-native'; +import { StyleSheet } from 'react-native'; +import type { CustomAnimation } from 'react-native-animatable'; +import type { PickerStyle } from 'react-native-picker-select'; +import type { MixedStyleDeclaration, MixedStyleRecord } from 'react-native-render-html'; +import type { ValueOf } from 'type-fest'; import type DotLottieAnimation from '@components/LottieAnimations/types'; import * as Browser from '@libs/Browser'; import CONST from '@src/CONST'; -import {defaultTheme} from './theme'; +import { defaultTheme } from './theme'; import colors from './theme/colors'; -import type {ThemeColors} from './theme/types'; +import type { ThemeColors } from './theme/types'; import addOutlineWidth from './utils/addOutlineWidth'; import borders from './utils/borders'; import chatContentScrollViewPlatformStyles from './utils/chatContentScrollViewPlatformStyles'; @@ -44,6 +44,7 @@ import wordBreak from './utils/wordBreak'; import writingDirection from './utils/writingDirection'; import variables from './variables'; + type ColorScheme = ValueOf; type StatusBarStyle = ValueOf; @@ -1282,6 +1283,18 @@ const styles = (theme: ThemeColors) => paddingBottom: 8, }, + textInputSuffixWrapper: { + position: 'absolute', + right: 0, + top: 0, + height: variables.inputHeight, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + paddingTop: 23, + paddingBottom: 8, + }, + textInputPrefix: { color: theme.text, ...FontUtils.fontFamily.platform.EXP_NEUE, @@ -1289,6 +1302,13 @@ const styles = (theme: ThemeColors) => verticalAlign: 'middle', }, + textInputSuffix: { + color: theme.text, + ...FontUtils.fontFamily.platform.EXP_NEUE, + fontSize: variables.fontSizeNormal, + verticalAlign: 'middle', + }, + pickerContainer: { borderBottomWidth: 2, paddingLeft: 0, @@ -5161,4 +5181,4 @@ const defaultStyles = styles(defaultTheme); export default styles; export {defaultStyles}; -export type {Styles, ThemeStyles, StatusBarStyle, ColorScheme, AnchorPosition, AnchorDimensions}; +export type {Styles, ThemeStyles, StatusBarStyle, ColorScheme, AnchorPosition, AnchorDimensions}; \ No newline at end of file From 2e75abdae93b4c070d5d6529b354bd1ae59fcff6 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 14 Aug 2024 23:42:05 +0200 Subject: [PATCH 028/502] wire up auto approval audit rate --- src/ONYXKEYS.ts | 3 ++ .../SetPolicyAutomaticApprovalAuditRate.ts | 6 +++ ...rams.ts => SetPolicyDefaultReportTitle.ts} | 0 ...ams.ts => SetPolicyPreventSelfApproval.ts} | 0 src/libs/API/parameters/index.ts | 5 +- src/libs/API/types.ts | 2 + src/libs/actions/Workspace/Rules.ts | 52 ++++++++++++++++++- src/pages/workspace/rules/PolicyRulesPage.tsx | 1 - .../rules/RulesRandomReportAuditPage.tsx | 9 ++-- .../form/RulesRandomReportAuditModalForm.ts | 18 +++++++ src/types/form/index.ts | 1 + 11 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyAutomaticApprovalAuditRate.ts rename src/libs/API/parameters/{SetPolicyDefaultReportTitleParams.ts => SetPolicyDefaultReportTitle.ts} (100%) rename src/libs/API/parameters/{SetPolicyPreventSelfApprovalParams.ts => SetPolicyPreventSelfApproval.ts} (100%) create mode 100644 src/types/form/RulesRandomReportAuditModalForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 04be910e0aa7..65b7b604b4c6 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -616,6 +616,8 @@ const ONYXKEYS = { RULES_CUSTOM_NAME_MODAL_FORM_DRAFT: 'rulesCustomNameModalFormDraft', RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM: 'rulesAutoApproveReportsUnderModalForm', RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM_DRAFT: 'rulesAutoApproveReportsUnderModalFormDraft', + RULES_RANDOM_REPORT_AUDIT_MODAL_FORM: 'rulesRandomReportAuditModalForm', + RULES_RANDOM_REPORT_AUDIT_MODAL_FORM_DRAFT: 'rulesRandomReportAuditModalFormDraft', }, } as const; @@ -693,6 +695,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.TEXT_PICKER_MODAL_FORM]: FormTypes.TextPickerModalForm; [ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM]: FormTypes.RulesCustomNameModalForm; [ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM]: FormTypes.RulesAutoApproveReportsUnderModalForm; + [ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM]: FormTypes.RulesRandomReportAuditModalForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/libs/API/parameters/SetPolicyAutomaticApprovalAuditRate.ts b/src/libs/API/parameters/SetPolicyAutomaticApprovalAuditRate.ts new file mode 100644 index 000000000000..50cfb43d4a54 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyAutomaticApprovalAuditRate.ts @@ -0,0 +1,6 @@ +type SetPolicyAutomaticApprovalAuditRateParams = { + policyID: string; + auditRate: number; +}; + +export default SetPolicyAutomaticApprovalAuditRateParams; diff --git a/src/libs/API/parameters/SetPolicyDefaultReportTitleParams.ts b/src/libs/API/parameters/SetPolicyDefaultReportTitle.ts similarity index 100% rename from src/libs/API/parameters/SetPolicyDefaultReportTitleParams.ts rename to src/libs/API/parameters/SetPolicyDefaultReportTitle.ts diff --git a/src/libs/API/parameters/SetPolicyPreventSelfApprovalParams.ts b/src/libs/API/parameters/SetPolicyPreventSelfApproval.ts similarity index 100% rename from src/libs/API/parameters/SetPolicyPreventSelfApprovalParams.ts rename to src/libs/API/parameters/SetPolicyPreventSelfApproval.ts diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index b962623594c2..72896113cde8 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -270,6 +270,7 @@ export type {default as UpdateExpensifyCardLimitParams} from './UpdateExpensifyC export type {CreateWorkspaceApprovalParams, UpdateWorkspaceApprovalParams, RemoveWorkspaceApprovalParams} from './WorkspaceApprovalParams'; export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFlowParams'; export type {default as SetPolicyRulesEnabledParams} from './SetPolicyRulesEnabledParams'; -export type {default as SetPolicyDefaultReportTitleParams} from './SetPolicyDefaultReportTitleParams'; -export type {default as SetPolicyPreventSelfApprovalParams} from './SetPolicyPreventSelfApprovalParams'; +export type {default as SetPolicyDefaultReportTitleParams} from './SetPolicyDefaultReportTitle'; +export type {default as SetPolicyPreventSelfApprovalParams} from './SetPolicyPreventSelfApproval'; export type {default as SetPolicyAutomaticApprovalLimitParams} from './SetPolicyAutomaticApprovalLimit'; +export type {default as SetPolicyAutomaticApprovalAuditRateParams} from './SetPolicyAutomaticApprovalAuditRate'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index c805f0cf6a76..e04807124ccc 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -18,6 +18,7 @@ const WRITE_COMMANDS = { SET_POLICY_PREVENT_MEMBER_CREATED_TITLE: 'SetPolicyPreventMemberCreatedTitle', SET_POLICY_PREVENT_SELF_APPROVAL: 'SetPolicyPreventSelfApproval', SET_POLICY_AUTOMATIC_APPROVAL_LIMIT: 'SetPolicyAutomaticApprovalLimit', + SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE: 'SetPolicyAutomaticApprovalAuditRate', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -515,6 +516,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE]: Parameters.SetPolicyDefaultReportTitleParams; [WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL]: Parameters.SetPolicyPreventSelfApprovalParams; [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT]: Parameters.SetPolicyAutomaticApprovalLimitParams; + [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE]: Parameters.SetPolicyAutomaticApprovalAuditRateParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 32965a330133..fb8485dc4d52 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -2,6 +2,7 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; import type {SetPolicyAutomaticApprovalLimitParams, SetPolicyDefaultReportTitleParams, SetPolicyPreventSelfApprovalParams} from '@libs/API/parameters'; +import type SetPolicyAutomaticApprovalAuditRateParams from '@libs/API/parameters/SetPolicyAutomaticApprovalAuditRate'; import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams'; import {WRITE_COMMANDS} from '@libs/API/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -203,4 +204,53 @@ function setPolicyAutomaticApprovalLimit(limit: number, policyID: string) { }); } -export {modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, setPolicyAutomaticApprovalLimit}; +/** + * Call the API to deactivate the card and request a new one + * @param auditRate - percentage of the reports to be qualified for a random audit + * @param policyID - id of the policy to apply the limit to + */ +function setPolicyAutomaticApprovalAuditRate(auditRate: number, policyID: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM, + value: { + isLoading: true, + errors: null, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const parameters: SetPolicyAutomaticApprovalAuditRateParams = { + auditRate, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE, parameters, { + optimisticData, + successData, + failureData, + }); +} + +export {modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, setPolicyAutomaticApprovalLimit, setPolicyAutomaticApprovalAuditRate}; diff --git a/src/pages/workspace/rules/PolicyRulesPage.tsx b/src/pages/workspace/rules/PolicyRulesPage.tsx index 27f2095745ef..f17fa5dab572 100644 --- a/src/pages/workspace/rules/PolicyRulesPage.tsx +++ b/src/pages/workspace/rules/PolicyRulesPage.tsx @@ -21,7 +21,6 @@ function PolicyRulesPage({route}: PolicyRulesPageProps) { const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - console.log('POLICY RULES PAGE'); return ( ; @@ -48,9 +47,9 @@ function RulesRandomReportAuditPage({route}: RulesRandomReportAuditPageProps) { /> WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(parseInt(maxExpenseAutoApprovalAmount, 10), policyID)} + onSubmit={({auditRatePercentage}) => WorkspaceRulesActions.setPolicyAutomaticApprovalAuditRate(parseInt(auditRatePercentage, 10), policyID)} submitButtonText={translate('common.save')} enabledWhenOffline > @@ -58,7 +57,7 @@ function RulesRandomReportAuditPage({route}: RulesRandomReportAuditPageProps) { {translate('workspace.rules.expenseReportRules.randomReportAuditDescription')} diff --git a/src/types/form/RulesRandomReportAuditModalForm.ts b/src/types/form/RulesRandomReportAuditModalForm.ts new file mode 100644 index 000000000000..2362c06e37bc --- /dev/null +++ b/src/types/form/RulesRandomReportAuditModalForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + AUDIT_RATE_PERCENTAGE: 'auditRatePercentage', +} as const; + +type InputID = ValueOf; + +type RulesRandomReportAuditModalForm = Form< + InputID, + { + [INPUT_IDS.AUDIT_RATE_PERCENTAGE]: string; + } +>; + +export type {RulesRandomReportAuditModalForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 1a8f3f57041d..d6c5910801c6 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -67,5 +67,6 @@ export type {SearchAdvancedFiltersForm} from './SearchAdvancedFiltersForm'; export type {EditExpensifyCardLimitForm} from './EditExpensifyCardLimitForm'; export type {RulesCustomNameModalForm} from './RulesCustomNameModalForm'; export type {RulesAutoApproveReportsUnderModalForm} from './RulesAutoApproveReportsUnderModalForm'; +export type {RulesRandomReportAuditModalForm} from './RulesRandomReportAuditModalForm'; export type {default as TextPickerModalForm} from './TextPickerModalForm'; export type {default as Form} from './Form'; From 45b46b4651c4d2b41ac06fe93605f7757ef60d44 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 15 Aug 2024 00:14:51 +0200 Subject: [PATCH 029/502] wire up auto pay --- src/ONYXKEYS.ts | 3 + src/languages/en.ts | 1 + .../SetPolicyAutoReimbursementLimit.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Workspace/Rules.ts | 61 +++++++++++++++++- .../rules/ExpenseReportRulesSection.tsx | 4 +- .../rules/RulesAutoPayReportsUnderPage.tsx | 64 ++++++++----------- .../form/RulesAutoPayReportsUnderModalForm.ts | 18 ++++++ src/types/form/index.ts | 1 + 10 files changed, 118 insertions(+), 43 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyAutoReimbursementLimit.ts create mode 100644 src/types/form/RulesAutoPayReportsUnderModalForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 65b7b604b4c6..d3348e292c74 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -618,6 +618,8 @@ const ONYXKEYS = { RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM_DRAFT: 'rulesAutoApproveReportsUnderModalFormDraft', RULES_RANDOM_REPORT_AUDIT_MODAL_FORM: 'rulesRandomReportAuditModalForm', RULES_RANDOM_REPORT_AUDIT_MODAL_FORM_DRAFT: 'rulesRandomReportAuditModalFormDraft', + RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM: 'rulesAutoPayReportsUnderModalForm', + RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM_DRAFT: 'rulesAutoPayReportsUnderModalFormDraft', }, } as const; @@ -696,6 +698,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM]: FormTypes.RulesCustomNameModalForm; [ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM]: FormTypes.RulesAutoApproveReportsUnderModalForm; [ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM]: FormTypes.RulesRandomReportAuditModalForm; + [ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM]: FormTypes.RulesAutoPayReportsUnderModalForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/languages/en.ts b/src/languages/en.ts index 371ae1932dbf..b5fbd51b4f70 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3556,6 +3556,7 @@ export default { autoPayApprovedReportsTitle: 'Auto-pay approved reports', autoPayApprovedReportsSubtitle: 'Configure which expense reports are eligible for auto-pay.', autoPayReportsUnderTitle: 'Auto-pay reports under', + autoPayReportsUnderDescription: 'Fully compliant expense reports under this amount will be automatically paid. ', }, }, }, diff --git a/src/libs/API/parameters/SetPolicyAutoReimbursementLimit.ts b/src/libs/API/parameters/SetPolicyAutoReimbursementLimit.ts new file mode 100644 index 000000000000..7c6a721e03b0 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyAutoReimbursementLimit.ts @@ -0,0 +1,6 @@ +type SetPolicyAutoReimbursementLimitParams = { + policyID: string; + autoReimbursement: {limit: number}; +}; + +export default SetPolicyAutoReimbursementLimitParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 72896113cde8..ccd7bc3de629 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -274,3 +274,4 @@ export type {default as SetPolicyDefaultReportTitleParams} from './SetPolicyDefa export type {default as SetPolicyPreventSelfApprovalParams} from './SetPolicyPreventSelfApproval'; export type {default as SetPolicyAutomaticApprovalLimitParams} from './SetPolicyAutomaticApprovalLimit'; export type {default as SetPolicyAutomaticApprovalAuditRateParams} from './SetPolicyAutomaticApprovalAuditRate'; +export type {default as SetPolicyAutoReimbursementLimitParams} from './SetPolicyAutoReimbursementLimit'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index e04807124ccc..8565bde7efbc 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -19,6 +19,7 @@ const WRITE_COMMANDS = { SET_POLICY_PREVENT_SELF_APPROVAL: 'SetPolicyPreventSelfApproval', SET_POLICY_AUTOMATIC_APPROVAL_LIMIT: 'SetPolicyAutomaticApprovalLimit', SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE: 'SetPolicyAutomaticApprovalAuditRate', + SET_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'SetPolicyAutoReimbursementLimit', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -517,6 +518,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL]: Parameters.SetPolicyPreventSelfApprovalParams; [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT]: Parameters.SetPolicyAutomaticApprovalLimitParams; [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE]: Parameters.SetPolicyAutomaticApprovalAuditRateParams; + [WRITE_COMMANDS.SET_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.SetPolicyAutoReimbursementLimitParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index fb8485dc4d52..c4e0ed2dfee0 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -1,7 +1,7 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {SetPolicyAutomaticApprovalLimitParams, SetPolicyDefaultReportTitleParams, SetPolicyPreventSelfApprovalParams} from '@libs/API/parameters'; +import type {SetPolicyAutomaticApprovalLimitParams, SetPolicyAutoReimbursementLimitParams, SetPolicyDefaultReportTitleParams, SetPolicyPreventSelfApprovalParams} from '@libs/API/parameters'; import type SetPolicyAutomaticApprovalAuditRateParams from '@libs/API/parameters/SetPolicyAutomaticApprovalAuditRate'; import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams'; import {WRITE_COMMANDS} from '@libs/API/types'; @@ -253,4 +253,61 @@ function setPolicyAutomaticApprovalAuditRate(auditRate: number, policyID: string }); } -export {modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, setPolicyAutomaticApprovalLimit, setPolicyAutomaticApprovalAuditRate}; +/** + * Call the API to deactivate the card and request a new one + * @param limit - max amount for auto-payment for the reports in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function setPolicyAutoReimbursementLimit(limit: number, policyID: string) { + console.log('LIMIT ', limit); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, + value: { + isLoading: true, + errors: null, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, + value: { + isLoading: false, + }, + }, + ]; + + const parameters: SetPolicyAutoReimbursementLimitParams = { + autoReimbursement: {limit}, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_AUTO_REIMBURSEMENT_LIMIT, parameters, { + optimisticData, + successData, + failureData, + }); +} + +export { + modifyPolicyDefaultReportTitle, + setPolicyPreventMemberCreatedTitle, + setPolicyPreventSelfApproval, + setPolicyAutomaticApprovalLimit, + setPolicyAutomaticApprovalAuditRate, + setPolicyAutoReimbursementLimit, +}; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index a00158cdf145..9a6890903285 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -58,7 +58,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { title: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), subtitle: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), - isActive: true, + isActive: false, onToggle: (isEnabled: boolean) => {}, subMenuItems: [ {}, - isActive: false, + isActive: true, subMenuItems: [ ; function RulesAutoPayReportsUnderPage({route}: RulesAutoPayReportsUnderPageProps) { const {policyID} = route.params; + const {inputCallbackRef} = useAutoFocusInput(); const {translate} = useLocalize(); const styles = useThemeStyles(); - const RULE_EXAMPLE_BULLET_POINTS = [ - translate('workspace.rules.expenseReportRules.customNameEmailPhoneExample'), - translate('workspace.rules.expenseReportRules.customNameStartDateExample'), - translate('workspace.rules.expenseReportRules.customNameWorkspaceNameExample'), - translate('workspace.rules.expenseReportRules.customNameReportIDExample'), - translate('workspace.rules.expenseReportRules.customNameTotalExample'), - ] as const satisfies string[]; + // const defaultValue = CurrencyUtils.convertToFrontendAmountAsString(policy?.maxExpenseAmountNoReceipt, policy?.outputCurrency); return ( Navigation.goBack()} /> - - - {translate('workspace.rules.expenseReportRules.customNameDescription')} - - {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} - - . - - WorkspaceRulesActions.modifyPolicyDefaultReportTitle(customName, policyID)} + onSubmit={({maxExpenseAutoPayAmount}) => WorkspaceRulesActions.setPolicyAutoReimbursementLimit(parseInt(maxExpenseAutoPayAmount, 10), policyID)} submitButtonText={translate('common.save')} enabledWhenOffline > - - - + + + {translate('workspace.rules.expenseReportRules.autoPayReportsUnderDescription')} + diff --git a/src/types/form/RulesAutoPayReportsUnderModalForm.ts b/src/types/form/RulesAutoPayReportsUnderModalForm.ts new file mode 100644 index 000000000000..a2edbac6a294 --- /dev/null +++ b/src/types/form/RulesAutoPayReportsUnderModalForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + MAX_EXPENSE_AUTO_PAY_AMOUNT: 'maxExpenseAutoPayAmount', +} as const; + +type InputID = ValueOf; + +type RulesAutoPayReportsUnderModalForm = Form< + InputID, + { + [INPUT_IDS.MAX_EXPENSE_AUTO_PAY_AMOUNT]: string; + } +>; + +export type {RulesAutoPayReportsUnderModalForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index d6c5910801c6..8e2e5afeab83 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -68,5 +68,6 @@ export type {EditExpensifyCardLimitForm} from './EditExpensifyCardLimitForm'; export type {RulesCustomNameModalForm} from './RulesCustomNameModalForm'; export type {RulesAutoApproveReportsUnderModalForm} from './RulesAutoApproveReportsUnderModalForm'; export type {RulesRandomReportAuditModalForm} from './RulesRandomReportAuditModalForm'; +export type {RulesAutoPayReportsUnderModalForm} from './RulesAutoPayReportsUnderModalForm'; export type {default as TextPickerModalForm} from './TextPickerModalForm'; export type {default as Form} from './Form'; From 72c38a48bdd8663684931c396d2a774d6e50831d Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 15 Aug 2024 00:50:53 +0200 Subject: [PATCH 030/502] rule lock conditions wip --- src/languages/en.ts | 1 + .../workspace/rules/ExpenseReportRulesSection.tsx | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index b5fbd51b4f70..2839d0f0fc07 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3555,6 +3555,7 @@ export default { randomReportAuditDescription: 'Require that some reports be manually approved, even if eligible for auto-approval.', autoPayApprovedReportsTitle: 'Auto-pay approved reports', autoPayApprovedReportsSubtitle: 'Configure which expense reports are eligible for auto-pay.', + autoPayApprovedReportsLockedSubtitle: 'Go to more features and enable workflows, then add payments to unlock this feature.', autoPayReportsUnderTitle: 'Auto-pay reports under', autoPayReportsUnderDescription: 'Fully compliant expense reports under this amount will be automatically paid. ', }, diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 9a6890903285..ed2e65e39e68 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -7,6 +7,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -19,6 +20,8 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const styles = useThemeStyles(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const autoPayApprovedReportsUnavailable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO; + const optionItems = [ { title: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), @@ -79,10 +82,14 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { }, { title: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), - subtitle: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsSubtitle'), + subtitle: autoPayApprovedReportsUnavailable + ? translate('workspace.rules.expenseReportRules.autoPayApprovedReportsLockedSubtitle') + : translate('workspace.rules.expenseReportRules.autoPayApprovedReportsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), onToggle: (isEnabled: boolean) => {}, - isActive: true, + disabled: autoPayApprovedReportsUnavailable, + showLockIcon: autoPayApprovedReportsUnavailable, + // isActive: true, subMenuItems: [ - {optionItems.map(({title, subtitle, isActive, subMenuItems}, index) => { + {optionItems.map(({title, subtitle, isActive, subMenuItems, showLockIcon, disabled}, index) => { const showBorderBottom = index !== optionItems.length - 1; return ( @@ -116,6 +123,8 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { titleStyle={styles.pv2} subtitleStyle={styles.pt1} isActive={!!isActive} + showLockIcon={showLockIcon} + disabled={disabled} subMenuItems={subMenuItems} onToggle={() => {}} /> From 4c41990a93ec26fd840c2974e2e7af3fec615652 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 16 Aug 2024 22:41:19 +0530 Subject: [PATCH 031/502] Migrating notification preference into participants --- src/libs/OptionsListUtils.ts | 6 +- src/libs/ReportUtils.ts | 85 +++++++++++-------- src/libs/SidebarUtils.ts | 4 +- src/libs/UnreadIndicatorUpdater/index.ts | 4 +- src/libs/actions/IOU.ts | 8 +- src/libs/actions/Report.ts | 73 +++++++++------- src/libs/actions/Task.ts | 19 +++-- src/libs/actions/User.ts | 5 +- src/pages/ProfilePage.tsx | 7 +- src/pages/ReportDetailsPage.tsx | 2 +- src/pages/RoomInvitePage.tsx | 2 +- src/pages/home/ReportScreen.tsx | 6 +- .../Report/NotificationPreferencePage.tsx | 7 +- .../settings/Report/ReportSettingsPage.tsx | 7 +- src/types/onyx/Report.ts | 5 +- tests/actions/IOUTest.ts | 31 ++++--- tests/actions/PolicyTest.ts | 2 +- tests/actions/ReportTest.ts | 18 +++- tests/ui/PaginationTest.tsx | 4 +- tests/ui/UnreadIndicatorsTest.tsx | 4 +- tests/unit/ReportUtilsTest.ts | 26 ++++-- tests/unit/UnreadIndicatorUpdaterTest.ts | 18 +++- 22 files changed, 213 insertions(+), 130 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 98274e829001..c0e3396cd57c 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -805,7 +805,7 @@ function createOption( result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.policyID = report.policyID; result.isSelfDM = ReportUtils.isSelfDM(report); - result.notificationPreference = report.notificationPreference; + result.notificationPreference = ReportUtils.getReportNotificationPreference(report); const visibleParticipantAccountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(report, true); @@ -899,7 +899,7 @@ function getPolicyExpenseReportOption(participant: Participant | ReportUtils.Opt const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? getReportOrDraftReport(participant.reportID) : null; const visibleParticipantAccountIDs = Object.entries(expenseReport?.participants ?? {}) - .filter(([, reportParticipant]) => reportParticipant && !reportParticipant.hidden) + .filter(([, reportParticipant]) => reportParticipant && reportParticipant.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) .map(([accountID]) => Number(accountID)); const option = createOption( @@ -2578,7 +2578,7 @@ function getEmptyOptions(): Options { } function shouldUseBoldText(report: ReportUtils.OptionData): boolean { - return report.isUnread === true && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; + return report.isUnread === true && ReportUtils.getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; } export { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 28ede28bf06b..34fcbbef259b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -154,10 +154,10 @@ type OptimisticExpenseReport = Pick< | 'statusNum' | 'total' | 'nonReimbursableTotal' - | 'notificationPreference' | 'parentReportID' | 'lastVisibleActionCreated' | 'parentReportActionID' + | 'participants' | 'fieldList' >; @@ -274,7 +274,6 @@ type OptimisticChatReport = Pick< | 'lastMessageText' | 'lastReadTime' | 'lastVisibleActionCreated' - | 'notificationPreference' | 'oldPolicyName' | 'ownerAccountID' | 'pendingFields' @@ -355,7 +354,6 @@ type OptimisticTaskReport = Pick< | 'policyID' | 'stateNum' | 'statusNum' - | 'notificationPreference' | 'parentReportActionID' | 'lastVisibleActionCreated' | 'hasParentAccess' @@ -395,7 +393,6 @@ type OptimisticIOUReport = Pick< | 'statusNum' | 'total' | 'reportName' - | 'notificationPreference' | 'parentReportID' | 'lastVisibleActionCreated' | 'fieldList' @@ -1153,6 +1150,32 @@ function isSystemChat(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.SYSTEM; } +function getDefaultNotificationPreferenceForReport(report: OnyxEntry) { + if (isAnnounceRoom(report)) { + return CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; + } + if (isPublicRoom(report)) { + return CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY; + } + if (!getChatType(report) || isGroupChat(report)) { + return CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; + } + if (isAdminRoom(report) || isPolicyExpenseChat(report) || isInvoiceRoom(report)) { + return CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; + } + if (isSelfDM(report)) { + return CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; + } + return CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY; +} + +/** + * Get the notification preference given a report + */ +function getReportNotificationPreference(report: OnyxEntry) { + return report?.participants?.[currentUserAccountID ?? -1]?.notificationPreference ?? getDefaultNotificationPreferenceForReport(report); +} + const CONCIERGE_ACCOUNT_ID_STRING = CONST.ACCOUNT_ID.CONCIERGE.toString(); /** * Only returns true if this is our main 1:1 DM report with Concierge. @@ -1258,7 +1281,7 @@ function hasExpensifyGuidesEmails(accountIDs: number[]): boolean { function getMostRecentlyVisitedReport(reports: Array>, reportMetadata: OnyxCollection): OnyxEntry { const filteredReports = reports.filter((report) => { - const shouldKeep = !isChatThread(report) || report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + const shouldKeep = !isChatThread(report) || getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; return shouldKeep && !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime); }); return lodashMaxBy(filteredReports, (a) => new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${a?.reportID}`]?.lastVisitTime ?? a?.lastReadTime ?? '').valueOf()); @@ -1634,13 +1657,6 @@ function isPayer(session: OnyxEntry, iouReport: OnyxEntry) { return isAdmin || (isMoneyRequestReport(iouReport) && isManager); } -/** - * Get the notification preference given a report - */ -function getReportNotificationPreference(report: OnyxEntry): string | number { - return report?.notificationPreference ?? ''; -} - /** * Checks if the current user is the action's author */ @@ -2070,7 +2086,7 @@ function getParticipantsAccountIDsForDisplay(report: OnyxEntry, shouldEx return false; } - if (shouldExcludeHidden && participant.hidden) { + if (shouldExcludeHidden && participant.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { return false; } @@ -2120,7 +2136,7 @@ function buildParticipantsFromAccountIDs(accountIDs: number[]): Participants { const finalParticipants: Participants = {}; return accountIDs.reduce((participants, accountID) => { // eslint-disable-next-line no-param-reassign - participants[accountID] = {hidden: false}; + participants[accountID] = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}; return participants; }, finalParticipants); } @@ -4224,8 +4240,8 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number const policy = getPolicy(policyID); const participants: Participants = { - [payeeAccountID]: {hidden: true}, - [payerAccountID]: {hidden: true}, + [payeeAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + [payerAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, }; return { @@ -4243,7 +4259,6 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number // We don't translate reportName because the server response is always in English reportName: `${payerEmail} owes ${formattedTotal}`, - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, parentReportID: chatReportID, lastVisibleActionCreated: DateUtils.getDBTime(), fieldList: policy?.fieldList, @@ -4298,7 +4313,11 @@ function buildOptimisticInvoiceReport(chatReportID: string, policyID: string, re stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.OPEN, total, - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + participants: { + [currentUserAccountID ?? -1]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, parentReportID: chatReportID, lastVisibleActionCreated: DateUtils.getDBTime(), }; @@ -4348,7 +4367,11 @@ function buildOptimisticExpenseReport( statusNum, total: storedTotal, nonReimbursableTotal: reimbursable ? 0 : storedTotal, - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + participants: { + [payeeAccountID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, parentReportID: chatReportID, lastVisibleActionCreated: DateUtils.getDBTime(), parentReportActionID, @@ -4977,11 +5000,10 @@ function buildOptimisticChatReport( description = '', avatarUrl = '', optimisticReportID = '', - shouldShowParticipants = true, ): OptimisticChatReport { const participants = participantList.reduce((reportParticipants: Participants, accountID: number) => { const participant: ReportParticipant = { - hidden: !shouldShowParticipants, + notificationPreference, role: accountID === currentUserAccountID ? CONST.REPORT.ROLE.ADMIN : CONST.REPORT.ROLE.MEMBER, }; // eslint-disable-next-line no-param-reassign @@ -5002,7 +5024,6 @@ function buildOptimisticChatReport( lastMessageText: undefined, lastReadTime: currentTime, lastVisibleActionCreated: currentTime, - notificationPreference, oldPolicyName, ownerAccountID: ownerAccountID || CONST.REPORT.OWNER_ACCOUNT_ID_FAKE, parentReportActionID, @@ -5509,12 +5530,12 @@ function buildOptimisticTaskReport( ): OptimisticTaskReport { const participants: Participants = { [ownerAccountID]: { - hidden: false, + notificationPreference, }, }; if (assigneeAccountID) { - participants[assigneeAccountID] = {hidden: false}; + participants[assigneeAccountID] = {notificationPreference}; } return { @@ -5529,7 +5550,6 @@ function buildOptimisticTaskReport( policyID, stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS_NUM.OPEN, - notificationPreference, lastVisibleActionCreated: DateUtils.getDBTime(), hasParentAccess: true, }; @@ -5607,10 +5627,6 @@ function buildTransactionThread( CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, reportAction?.reportActionID, moneyRequestReport?.reportID, - '', - '', - '', - false, ); } @@ -5975,7 +5991,7 @@ function shouldReportBeInOptionList({ // All unread chats (even archived ones) in GSD mode will be shown. This is because GSD mode is specifically for focusing the user on the most relevant chats, primarily, the unread ones if (isInFocusMode) { - return isUnread(report) && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; + return isUnread(report) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; } // Archived reports should always be shown when in default (most recent) mode. This is because you should still be able to access and search for the chats to find them. @@ -7362,7 +7378,7 @@ function isAdminOwnerApproverOrReportOwner(report: OnyxEntry, policy: On /** * Whether the user can join a report */ -function canJoinChat(report: OnyxInputOrEntry, parentReportAction: OnyxInputOrEntry, policy: OnyxInputOrEntry): boolean { +function canJoinChat(report: OnyxEntry, parentReportAction: OnyxInputOrEntry, policy: OnyxInputOrEntry): boolean { // We disabled thread functions for whisper action // So we should not show join option for existing thread on whisper message that has already been left, or manually leave it if (ReportActionsUtils.isWhisperAction(parentReportAction)) { @@ -7370,7 +7386,7 @@ function canJoinChat(report: OnyxInputOrEntry, parentReportAction: OnyxI } // If the notification preference of the chat is not hidden that means we have already joined the chat - if (report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { + if (getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { return false; } @@ -7404,7 +7420,7 @@ function canLeaveChat(report: OnyxEntry, policy: OnyxEntry): boo return false; } - if (report?.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { + if (getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { return false; } @@ -7422,7 +7438,7 @@ function canLeaveChat(report: OnyxEntry, policy: OnyxEntry): boo return canLeaveInvoiceRoom(report); } - return (isChatThread(report) && !!report?.notificationPreference?.length) || isUserCreatedPolicyRoom(report) || isNonAdminOrOwnerOfPolicyExpenseChat(report, policy); + return (isChatThread(report) && !!getReportNotificationPreference(report)) || isUserCreatedPolicyRoom(report) || isNonAdminOrOwnerOfPolicyExpenseChat(report, policy); } function getReportActionActorAccountID(reportAction: OnyxInputOrEntry, iouReport: OnyxInputOrEntry | undefined): number | undefined { @@ -7933,6 +7949,7 @@ export { isInvoiceRoomWithID, isInvoiceReport, isOpenInvoiceReport, + getDefaultNotificationPreferenceForReport, canWriteInReport, navigateToDetailsPage, navigateToPrivateNotes, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 9ce8feb26868..033b947d7dc4 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -103,7 +103,7 @@ function getOrderedReportIDs( const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}; const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); const doesReportHaveViolations = OptionsListUtils.shouldShowViolations(report, transactionViolations); - const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + const isHidden = ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; const transactionReportActions = ReportActionsUtils.getAllReportActions(report.reportID); @@ -340,7 +340,7 @@ function getOptionData({ result.hasOutstandingChildRequest = report.hasOutstandingChildRequest; result.parentReportID = report.parentReportID ?? '-1'; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; - result.notificationPreference = report.notificationPreference; + result.notificationPreference = ReportUtils.getReportNotificationPreference(report); result.isAllowedToComment = ReportUtils.canUserPerformWriteAction(report); result.chatType = report.chatType; result.isDeletedParentAction = report.isDeletedParentAction; diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index d6a65ee85aac..40af0759f06a 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -29,8 +29,8 @@ function getUnreadReportsForUnreadIndicator(reports: OnyxCollection, cur * Furthermore, muted reports may or may not appear in the LHN depending on priority mode, * but they should not be considered in the unread indicator count. */ - report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN && - report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE, + ReportUtils.getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN && + ReportUtils.getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE, ); } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9cc844849c89..c433d9922f4b 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3942,7 +3942,7 @@ function createSplitsAndOnyxData( splitChatReport.lastActorAccountID = currentUserAccountID; splitChatReport.lastVisibleActionCreated = splitIOUReportAction.created; - let splitChatReportNotificationPreference = splitChatReport.notificationPreference; + let splitChatReportNotificationPreference = ReportUtils.getReportNotificationPreference(splitChatReport); if (splitChatReportNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { splitChatReportNotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; } @@ -3962,7 +3962,11 @@ function createSplitsAndOnyxData( key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`, value: { ...splitChatReport, - notificationPreference: splitChatReportNotificationPreference, + participants: { + [currentUserAccountID]: { + notificationPreference: splitChatReportNotificationPreference, + }, + }, }, }, { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 1ab622adcdc7..9dd000c6ab5f 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -102,7 +102,7 @@ import type { } from '@src/types/onyx'; import type {Decision} from '@src/types/onyx/OriginalMessage'; import type {ConnectionName} from '@src/types/onyx/Policy'; -import type {NotificationPreference, Participants, Participant as ReportParticipant, RoomVisibility, WriteCapability} from '@src/types/onyx/Report'; +import type {NotificationPreference, Participant, Participants, Participant as ReportParticipant, RoomVisibility, WriteCapability} from '@src/types/onyx/Report'; import type Report from '@src/types/onyx/Report'; import type {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -490,7 +490,9 @@ function addActions(reportID: string, text = '', file?: FileObject) { const shouldUpdateNotificationPrefernece = !isEmptyObject(report) && ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; if (shouldUpdateNotificationPrefernece) { - optimisticReport.notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; + optimisticReport.participants = { + [currentUserAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }; } // Optimistically add the new actions to the store before waiting to save them to the server @@ -550,7 +552,9 @@ function addActions(reportID: string, text = '', file?: FileObject) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + participants: { + [currentUserAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, }, }); } @@ -1688,7 +1692,6 @@ function updateNotificationPreference( parentReportID?: string, parentReportActionID?: string, report?: OnyxEntry, - isJoiningRoom?: boolean, ) { if (previousValue === newValue) { if (navigate && !isEmptyObject(report) && report.reportID) { @@ -1701,7 +1704,13 @@ function updateNotificationPreference( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: {notificationPreference: newValue}, + value: { + participants: { + [currentUserAccountID]: { + notificationPreference: newValue, + }, + }, + }, }, ]; @@ -1709,7 +1718,13 @@ function updateNotificationPreference( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: {notificationPreference: previousValue}, + value: { + participants: { + [currentUserAccountID]: { + notificationPreference: previousValue, + }, + }, + }, }, ]; @@ -1726,20 +1741,6 @@ function updateNotificationPreference( }); } - if (isJoiningRoom) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - participants: { - [currentUserAccountID]: { - hidden: false, - }, - }, - }, - }); - } - const parameters: UpdateReportNotificationPreferenceParams = {reportID, notificationPreference: newValue}; API.write(WRITE_COMMANDS.UPDATE_REPORT_NOTIFICATION_PREFERENCE, parameters, {optimisticData, failureData}); @@ -2424,7 +2425,7 @@ function shouldShowReportActionNotification(reportID: string, action: ReportActi } // We don't want to send a local notification if the user preference is daily, mute or hidden. - const notificationPreference = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]?.notificationPreference ?? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; + const notificationPreference = ReportUtils.getReportNotificationPreference(ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]); if (notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS) { Log.info(`${tag} No notification because user preference is to be notified: ${notificationPreference}`); return false; @@ -2747,13 +2748,12 @@ function joinRoom(report: OnyxEntry) { } updateNotificationPreference( report.reportID, - report.notificationPreference, + CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, report.parentReportID, report.parentReportActionID, report, - true, ); } @@ -2808,10 +2808,9 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal value: isWorkspaceMemberLeavingWorkspaceRoom || isChatThread ? { - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, participants: { [currentUserAccountID]: { - hidden: true, + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }, }, } @@ -2819,7 +2818,11 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal reportID: null, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.CLOSED, - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + participants: { + [currentUserAccountID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, }, }, ]; @@ -2830,7 +2833,13 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: isWorkspaceMemberLeavingWorkspaceRoom || isChatThread - ? {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN} + ? { + participants: { + [currentUserAccountID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, + } : Object.keys(report).reduce>((acc, key) => { acc[key] = null; return acc; @@ -2860,7 +2869,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, - value: {[report.parentReportActionID]: {childReportNotificationPreference: report.notificationPreference}}, + value: {[report.parentReportActionID]: {childReportNotificationPreference: ReportUtils.getReportNotificationPreference(report)}}, }); } @@ -2886,6 +2895,8 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails return; } + const defaultNotificationPreference = ReportUtils.getDefaultNotificationPreferenceForReport(report); + const inviteeEmails = Object.keys(inviteeEmailsToAccountIDs); const inviteeAccountIDs = Object.values(inviteeEmailsToAccountIDs); @@ -2895,7 +2906,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails const participantsAfterInvitation = inviteeAccountIDs.reduce( (reportParticipants: Participants, accountID: number) => { const participant: ReportParticipant = { - hidden: false, + notificationPreference: defaultNotificationPreference, role: CONST.REPORT.ROLE.MEMBER, }; // eslint-disable-next-line no-param-reassign @@ -2998,8 +3009,8 @@ function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { function updateGroupChatMemberRoles(reportID: string, accountIDList: number[], role: ValueOf) { const memberRoles: Record = {}; - const optimisticParticipants: Participants = {}; - const successParticipants: Participants = {}; + const optimisticParticipants: Record> = {}; + const successParticipants: Record> = {}; accountIDList.forEach((accountID) => { memberRoles[accountID] = role; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 6105b7cffd9e..a32437f2039b 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -25,7 +25,7 @@ import type ReportAction from '@src/types/onyx/ReportAction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import * as Report from './Report'; -type OptimisticReport = Pick; +type OptimisticReport = Pick; type Assignee = { icons: Icon[]; displayName: string; @@ -581,9 +581,13 @@ function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assi pendingFields: { ...(assigneeAccountID && {managerID: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, - notificationPreference: [assigneeAccountID, ownerAccountID].includes(currentUserAccountID) - ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS - : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + participants: { + [currentUserAccountID]: { + notificationPreference: [assigneeAccountID, ownerAccountID].includes(currentUserAccountID) + ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS + : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, }; const successReport: NullishDeep = {pendingFields: {...(assigneeAccountID && {managerID: null})}}; @@ -662,7 +666,12 @@ function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assi // If we make a change to the assignee, we want to add a comment to the assignee's chat // Check if the assignee actually changed if (assigneeAccountID && assigneeAccountID !== report.managerID && assigneeAccountID !== ownerAccountID && assigneeChatReport) { - optimisticReport.participants = {[assigneeAccountID]: {hidden: false}}; + optimisticReport.participants = { + ...(optimisticReport.participants ?? {}), + [assigneeAccountID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + }, + }; assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( currentUserAccountID, diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 5e0b83f8521d..49bea8e058a8 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -556,11 +556,10 @@ const isChannelMuted = (reportId: string) => key: `${ONYXKEYS.COLLECTION.REPORT}${reportId}`, callback: (report) => { Onyx.disconnect(connectionId); + const notificationPreference = report?.participants?.[currentUserAccountID]?.notificationPreference; resolve( - !report?.notificationPreference || - report?.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE || - report?.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + !notificationPreference || notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE || notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); }, }); diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index d172e2089983..b414afb887cc 100755 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -155,10 +155,11 @@ function ProfilePage({route}: ProfilePageProps) { const navigateBackTo = route?.params?.backTo; - const shouldShowNotificationPreference = - !isEmptyObject(report) && !isCurrentUser && !!report.notificationPreference && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + const notificationPreferenceValue = ReportUtils.getReportNotificationPreference(report); + + const shouldShowNotificationPreference = !isEmptyObject(report) && !isCurrentUser && notificationPreferenceValue !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const notificationPreference = shouldShowNotificationPreference - ? translate(`notificationPreferencesPage.notificationPreferences.${report.notificationPreference}` as TranslationPaths) + ? translate(`notificationPreferencesPage.notificationPreferences.${notificationPreferenceValue}` as TranslationPaths) : ''; // eslint-disable-next-line rulesdir/prefer-early-return diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index de81d661cfff..500619bfb9c7 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -257,7 +257,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD roomDescription = translate('newRoomPage.roomName'); } - const shouldShowNotificationPref = !isMoneyRequestReport && report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + const shouldShowNotificationPref = !isMoneyRequestReport && ReportUtils.getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const shouldShowWriteCapability = !isMoneyRequestReport; const shouldShowMenuItem = shouldShowNotificationPref || shouldShowWriteCapability || (!!report?.visibility && report.chatType !== CONST.REPORT.CHAT_TYPE.INVOICE); diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 17239e6d4fb5..5dc281d8013e 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -66,7 +66,7 @@ function RoomInvitePage({ // Any existing participants and Expensify emails should not be eligible for invitation const excludedUsers = useMemo(() => { const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) - .filter(([, participant]) => participant && !participant.hidden) + .filter(([, participant]) => participant && participant.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) .map(([accountID]) => Number(accountID)); return [...PersonalDetailsUtils.getLoginsByAccountIDs(visibleParticipantAccountIDs), ...CONST.EXPENSIFY_EMAILS].map((participant) => PhoneNumber.addSMSDomainIfPhoneNumber(participant), diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 979dd6166c78..4fe54385d97b 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -196,7 +196,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro isWaitingOnBankAccount: reportOnyx?.isWaitingOnBankAccount, iouReportID: reportOnyx?.iouReportID, isOwnPolicyExpenseChat: reportOnyx?.isOwnPolicyExpenseChat, - notificationPreference: reportOnyx?.notificationPreference, isPinned: reportOnyx?.isPinned, chatReportID: reportOnyx?.chatReportID, visibility: reportOnyx?.visibility, @@ -239,7 +238,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro reportOnyx?.isWaitingOnBankAccount, reportOnyx?.iouReportID, reportOnyx?.isOwnPolicyExpenseChat, - reportOnyx?.notificationPreference, reportOnyx?.isPinned, reportOnyx?.chatReportID, reportOnyx?.visibility, @@ -556,7 +554,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro !isFocused || prevIsFocused || !ReportUtils.isChatThread(report) || - report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN || + ReportUtils.getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN || isSingleTransactionView ) { return; @@ -566,7 +564,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro // We don't want to run this useEffect every time `report` is changed // Excluding shouldUseNarrowLayout from the dependency list to prevent re-triggering on screen resize events. // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [prevIsFocused, report.notificationPreference, isFocused, isSingleTransactionView]); + }, [prevIsFocused, report.participants, isFocused, isSingleTransactionView]); useEffect(() => { // We don't want this effect to run on the first render. diff --git a/src/pages/settings/Report/NotificationPreferencePage.tsx b/src/pages/settings/Report/NotificationPreferencePage.tsx index 3a149e1ed377..fd256d685139 100644 --- a/src/pages/settings/Report/NotificationPreferencePage.tsx +++ b/src/pages/settings/Report/NotificationPreferencePage.tsx @@ -22,17 +22,18 @@ function NotificationPreferencePage({report}: NotificationPreferencePageProps) { const {translate} = useLocalize(); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID || -1}`); const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); + const currentNotificationPreference = ReportUtils.getReportNotificationPreference(report); const shouldDisableNotificationPreferences = ReportUtils.isArchivedRoom(report, reportNameValuePairs) || ReportUtils.isSelfDM(report) || - (!isMoneyRequestReport && report?.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + (!isMoneyRequestReport && currentNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); const notificationPreferenceOptions = Object.values(CONST.REPORT.NOTIFICATION_PREFERENCE) .filter((pref) => pref !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) .map((preference) => ({ value: preference, text: translate(`notificationPreferencesPage.notificationPreferences.${preference}`), keyForList: preference, - isSelected: preference === report?.notificationPreference, + isSelected: preference === currentNotificationPreference, })); return ( @@ -49,7 +50,7 @@ function NotificationPreferencePage({report}: NotificationPreferencePageProps) { sections={[{data: notificationPreferenceOptions}]} ListItem={RadioListItem} onSelectRow={(option) => - report && ReportActions.updateNotificationPreference(report.reportID, report.notificationPreference, option.value, true, undefined, undefined, report) + report && ReportActions.updateNotificationPreference(report.reportID, currentNotificationPreference, option.value, true, undefined, undefined, report) } shouldSingleExecuteRowSelect initiallyFocusedOptionKey={notificationPreferenceOptions.find((locale) => locale.isSelected)?.keyForList} diff --git a/src/pages/settings/Report/ReportSettingsPage.tsx b/src/pages/settings/Report/ReportSettingsPage.tsx index 6e1ab9a61737..6a9986b5550f 100644 --- a/src/pages/settings/Report/ReportSettingsPage.tsx +++ b/src/pages/settings/Report/ReportSettingsPage.tsx @@ -33,9 +33,10 @@ function ReportSettingsPage({report, policies}: ReportSettingsPageProps) { const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); const shouldDisableSettings = isEmptyObject(report) || ReportUtils.isArchivedRoom(report, reportNameValuePairs) || ReportUtils.isSelfDM(report); + const notificationPreferenceValue = ReportUtils.getReportNotificationPreference(report); const notificationPreference = - report?.notificationPreference && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN - ? translate(`notificationPreferencesPage.notificationPreferences.${report.notificationPreference}`) + notificationPreferenceValue && notificationPreferenceValue !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN + ? translate(`notificationPreferencesPage.notificationPreferences.${notificationPreferenceValue}`) : ''; const writeCapability = ReportUtils.isAdminRoom(report) ? CONST.REPORT.WRITE_CAPABILITIES.ADMINS : report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL; @@ -43,7 +44,7 @@ function ReportSettingsPage({report, policies}: ReportSettingsPageProps) { const shouldAllowWriteCapabilityEditing = useMemo(() => ReportUtils.canEditWriteCapability(report, linkedWorkspace), [report, linkedWorkspace]); const shouldAllowChangeVisibility = useMemo(() => ReportUtils.canEditRoomVisibility(report, linkedWorkspace), [report, linkedWorkspace]); - const shouldShowNotificationPref = !isMoneyRequestReport && report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + const shouldShowNotificationPref = !isMoneyRequestReport && notificationPreferenceValue !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const shouldShowWriteCapability = !isMoneyRequestReport; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 14fd2bd3ac81..4f781897375b 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -39,7 +39,7 @@ type PendingChatMember = { /** Report participant properties */ type Participant = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Whether the participant is visible in the report */ - hidden?: boolean; + notificationPreference: NotificationPreference; /** What is the role of the participant in the report */ role?: 'admin' | 'member'; @@ -113,9 +113,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** The time of the last mention of the report */ lastMentionedTime?: string | null; - /** The current user's notification preference for this report */ - notificationPreference?: NotificationPreference; - /** The policy name to use */ policyName?: string | null; diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 405760435d14..45179f3ba79d 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -36,16 +36,16 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ const CARLOS_EMAIL = 'cmartins@expensifail.com'; const CARLOS_ACCOUNT_ID = 1; -const CARLOS_PARTICIPANT: Participant = {hidden: false, role: 'member'}; +const CARLOS_PARTICIPANT: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'member'}; const JULES_EMAIL = 'jules@expensifail.com'; const JULES_ACCOUNT_ID = 2; -const JULES_PARTICIPANT: Participant = {hidden: false, role: 'member'}; +const JULES_PARTICIPANT: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'member'}; const RORY_EMAIL = 'rory@expensifail.com'; const RORY_ACCOUNT_ID = 3; -const RORY_PARTICIPANT: Participant = {hidden: false, role: 'admin'}; +const RORY_PARTICIPANT: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'admin'}; const VIT_EMAIL = 'vit@expensifail.com'; const VIT_ACCOUNT_ID = 4; -const VIT_PARTICIPANT: Participant = {hidden: false, role: 'member'}; +const VIT_PARTICIPANT: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'member'}; OnyxUpdateManager(); describe('actions/IOU', () => { @@ -100,7 +100,10 @@ describe('actions/IOU', () => { iouReportID = iouReport?.reportID; transactionThread = transactionThreadReport; - expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(iouReport?.participants).toBe({ + [RORY_ACCOUNT_ID]: {notification: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + [CARLOS_ACCOUNT_ID]: {notification: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + }); // They should be linked together expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); @@ -293,7 +296,10 @@ describe('actions/IOU', () => { const iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU); iouReportID = iouReport?.reportID; - expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(iouReport?.participants).toBe({ + [RORY_ACCOUNT_ID]: {notification: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + [CARLOS_ACCOUNT_ID]: {notification: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + }); // They should be linked together expect(chatReport.iouReportID).toBe(iouReportID); @@ -638,7 +644,7 @@ describe('actions/IOU', () => { const iouReport = iouReports[0]; iouReportID = iouReport?.reportID; - expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); // They should be linked together expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); @@ -1159,14 +1165,13 @@ describe('actions/IOU', () => { // The 1:1 chat reports and the IOU reports should be linked together expect(carlosChatReport?.iouReportID).toBe(carlosIOUReport?.reportID); expect(carlosIOUReport?.chatReportID).toBe(carlosChatReport?.reportID); - expect(carlosIOUReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(carlosIOUReport?.participants).toBe({[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); expect(julesChatReport?.iouReportID).toBe(julesIOUReport?.reportID); expect(julesIOUReport?.chatReportID).toBe(julesChatReport?.reportID); expect(vitChatReport?.iouReportID).toBe(vitIOUReport?.reportID); expect(vitIOUReport?.chatReportID).toBe(vitChatReport?.reportID); - expect(carlosIOUReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); resolve(); }, @@ -2437,7 +2442,7 @@ describe('actions/IOU', () => { // Given a transaction thread thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(thread.participants).toBe({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, @@ -2626,7 +2631,7 @@ describe('actions/IOU', () => { // Given a transaction thread thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(thread.participants).toBe({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); const participantAccountIDs = Object.keys(thread.participants ?? {}).map(Number); const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs); @@ -2714,7 +2719,7 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(thread.participants).toBe({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, @@ -2954,7 +2959,7 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(thread.participants).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 17e9c09acfd3..2bbd19ce9dea 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -12,7 +12,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const ESH_EMAIL = 'eshgupta1217@gmail.com'; const ESH_ACCOUNT_ID = 1; -const ESH_PARTICIPANT: Participant = {hidden: false, role: 'admin'}; +const ESH_PARTICIPANT: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, role: 'admin'}; const WORKSPACE_NAME = "Esh's Workspace"; OnyxUpdateManager(); diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index e6ab31334bb1..a099348257f0 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -103,7 +103,11 @@ describe('actions/Report', () => { key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, value: { reportID: REPORT_ID, - notificationPreference: 'always', + participants: { + [TEST_USER_ACCOUNT_ID]: { + notificationPreference: 'always', + }, + }, lastVisibleActionCreated: '2022-11-22 03:48:27.267', lastMessageText: 'Testing a comment', lastActorAccountID: TEST_USER_ACCOUNT_ID, @@ -230,7 +234,11 @@ describe('actions/Report', () => { key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, value: { reportID: REPORT_ID, - notificationPreference: 'always', + participants: { + [USER_1_ACCOUNT_ID]: { + notificationPreference: 'always', + }, + }, lastMessageText: 'Comment 1', lastActorAccountID: USER_2_ACCOUNT_ID, lastVisibleActionCreated: reportActionCreatedDate, @@ -380,7 +388,11 @@ describe('actions/Report', () => { key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, value: { reportID: REPORT_ID, - notificationPreference: 'always', + participants: { + [USER_1_ACCOUNT_ID]: { + notificationPreference: 'always', + }, + }, lastMessageText: 'Current User Comment 3', lastActorAccountID: 1, lastVisibleActionCreated: reportActionCreatedDate, diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx index 277f6b88e78f..250264235172 100644 --- a/tests/ui/PaginationTest.tsx +++ b/tests/ui/PaginationTest.tsx @@ -198,7 +198,7 @@ async function signInAndGetApp(): Promise { reportID: REPORT_ID, reportName: CONST.REPORT.DEFAULT_REPORT_NAME, lastMessageText: 'Test', - participants: {[USER_B_ACCOUNT_ID]: {hidden: false}}, + participants: {[USER_B_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}}, lastActorAccountID: USER_B_ACCOUNT_ID, type: CONST.REPORT.TYPE.CHAT, }); @@ -212,7 +212,7 @@ async function signInAndGetApp(): Promise { reportID: COMMENT_LINKING_REPORT_ID, reportName: CONST.REPORT.DEFAULT_REPORT_NAME, lastMessageText: 'Test', - participants: {[USER_A_ACCOUNT_ID]: {hidden: false}}, + participants: {[USER_A_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}}, lastActorAccountID: USER_A_ACCOUNT_ID, type: CONST.REPORT.TYPE.CHAT, }); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 1d31a707d81d..011f1e01668f 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -140,7 +140,7 @@ function signInAndGetAppWithUnreadChat(): Promise { lastReadTime: reportAction3CreatedDate, lastVisibleActionCreated: reportAction9CreatedDate, lastMessageText: 'Test', - participants: {[USER_B_ACCOUNT_ID]: {hidden: false}}, + participants: {[USER_B_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}}, lastActorAccountID: USER_B_ACCOUNT_ID, type: CONST.REPORT.TYPE.CHAT, }); @@ -301,7 +301,7 @@ describe('Unread Indicators', () => { lastVisibleActionCreated: DateUtils.getDBTime(utcToZonedTime(NEW_REPORT_FIST_MESSAGE_CREATED_DATE, 'UTC').valueOf()), lastMessageText: 'Comment 1', lastActorAccountID: USER_C_ACCOUNT_ID, - participants: {[USER_C_ACCOUNT_ID]: {hidden: false}}, + participants: {[USER_C_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}}, type: CONST.REPORT.TYPE.CHAT, }, }, diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 12bcabc92b19..a8fecd11e87f 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -946,28 +946,44 @@ describe('ReportUtils', () => { const invoiceReport: Report = { reportID: '1', type: CONST.REPORT.TYPE.INVOICE, - participants: {[userAccountID]: {hidden: false}, [currentUserAccountID]: {hidden: false}}, + participants: { + [userAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [currentUserAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, }; const taskReport: Report = { reportID: '2', type: CONST.REPORT.TYPE.TASK, - participants: {[userAccountID]: {hidden: false}, [currentUserAccountID]: {hidden: false}}, + participants: { + [userAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [currentUserAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, }; const iouReport: Report = { reportID: '3', type: CONST.REPORT.TYPE.IOU, - participants: {[userAccountID]: {hidden: false}, [currentUserAccountID]: {hidden: false}}, + participants: { + [userAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [currentUserAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, }; groupChatReport = { reportID: '4', type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.GROUP, - participants: {[userAccountID]: {hidden: false}, [userAccountID2]: {hidden: false}, [currentUserAccountID]: {hidden: false}}, + participants: { + [userAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [userAccountID2]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [currentUserAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, }; oneOnOneChatReport = { reportID: '5', type: CONST.REPORT.TYPE.CHAT, - participants: {[userAccountID]: {hidden: false}, [currentUserAccountID]: {hidden: false}}, + participants: { + [userAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [currentUserAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, }; const reportCollectionDataSet = toCollectionDataSet( ONYXKEYS.COLLECTION.REPORT, diff --git a/tests/unit/UnreadIndicatorUpdaterTest.ts b/tests/unit/UnreadIndicatorUpdaterTest.ts index 9cf65bcb69d4..cf8119f3784a 100644 --- a/tests/unit/UnreadIndicatorUpdaterTest.ts +++ b/tests/unit/UnreadIndicatorUpdaterTest.ts @@ -1,6 +1,10 @@ /* eslint-disable @typescript-eslint/naming-convention */ import CONST from '../../src/CONST'; import * as UnreadIndicatorUpdater from '../../src/libs/UnreadIndicatorUpdater'; +import * as TestHelper from '../utils/TestHelper'; + +const TEST_USER_ACCOUNT_ID = 1; +const TEST_USER_LOGIN = 'test@test.com'; describe('UnreadIndicatorUpdaterTest', () => { describe('should return correct number of unread reports', () => { @@ -24,7 +28,9 @@ describe('UnreadIndicatorUpdaterTest', () => { }, 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastMessageText: 'test'}, }; - expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(2); + TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID).then(() => { + expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(2); + }); }); it('given some reports are incomplete', () => { @@ -42,7 +48,11 @@ describe('UnreadIndicatorUpdaterTest', () => { reportID: '1', reportName: 'test', type: CONST.REPORT.TYPE.EXPENSE, - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + participants: { + [TEST_USER_ACCOUNT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, lastReadTime: '2023-07-08 07:15:44.030', lastVisibleActionCreated: '2023-08-08 07:15:44.030', lastMessageText: 'test', @@ -57,7 +67,9 @@ describe('UnreadIndicatorUpdaterTest', () => { }, 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastMessageText: 'test'}, }; - expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(1); + TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID).then(() => { + expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(1); + }); }); }); }); From 8504a1df1f3480852fd96a716482309d76565131 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 18 Aug 2024 23:57:55 +0530 Subject: [PATCH 032/502] fix/tests --- src/libs/ReportUtils.ts | 4 ++-- src/libs/actions/Report.ts | 4 ++-- tests/actions/IOUTest.ts | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 34fcbbef259b..e4d8c29d7323 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1150,7 +1150,7 @@ function isSystemChat(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.SYSTEM; } -function getDefaultNotificationPreferenceForReport(report: OnyxEntry) { +function getDefaultNotificationPreferenceForReport(report: OnyxEntry): ValueOf { if (isAnnounceRoom(report)) { return CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; } @@ -1172,7 +1172,7 @@ function getDefaultNotificationPreferenceForReport(report: OnyxEntry) { /** * Get the notification preference given a report */ -function getReportNotificationPreference(report: OnyxEntry) { +function getReportNotificationPreference(report: OnyxEntry): ValueOf { return report?.participants?.[currentUserAccountID ?? -1]?.notificationPreference ?? getDefaultNotificationPreferenceForReport(report); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9dd000c6ab5f..26dc000fe6a1 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3009,8 +3009,8 @@ function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { function updateGroupChatMemberRoles(reportID: string, accountIDList: number[], role: ValueOf) { const memberRoles: Record = {}; - const optimisticParticipants: Record> = {}; - const successParticipants: Record> = {}; + const optimisticParticipants: Record> = {}; + const successParticipants: Record> = {}; accountIDList.forEach((accountID) => { memberRoles[accountID] = role; diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 45179f3ba79d..36ebf4684fae 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -101,8 +101,8 @@ describe('actions/IOU', () => { transactionThread = transactionThreadReport; expect(iouReport?.participants).toBe({ - [RORY_ACCOUNT_ID]: {notification: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, - [CARLOS_ACCOUNT_ID]: {notification: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + [RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + [CARLOS_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, }); // They should be linked together @@ -297,8 +297,8 @@ describe('actions/IOU', () => { iouReportID = iouReport?.reportID; expect(iouReport?.participants).toBe({ - [RORY_ACCOUNT_ID]: {notification: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, - [CARLOS_ACCOUNT_ID]: {notification: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + [RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, + [CARLOS_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, }); // They should be linked together @@ -644,10 +644,10 @@ describe('actions/IOU', () => { const iouReport = iouReports[0]; iouReportID = iouReport?.reportID; - expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); + expect(chatReport?.participants).toStrictEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); // They should be linked together - expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); + expect(chatReport?.participants).toStrictEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); expect(chatReport?.iouReportID).toBe(iouReport?.reportID); resolve(); @@ -1165,7 +1165,7 @@ describe('actions/IOU', () => { // The 1:1 chat reports and the IOU reports should be linked together expect(carlosChatReport?.iouReportID).toBe(carlosIOUReport?.reportID); expect(carlosIOUReport?.chatReportID).toBe(carlosChatReport?.reportID); - expect(carlosIOUReport?.participants).toBe({[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); + expect(carlosIOUReport?.participants).toStrictEqual({[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); expect(julesChatReport?.iouReportID).toBe(julesIOUReport?.reportID); expect(julesIOUReport?.chatReportID).toBe(julesChatReport?.reportID); @@ -2442,7 +2442,7 @@ describe('actions/IOU', () => { // Given a transaction thread thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.participants).toBe({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); + expect(thread.participants).toStrictEqual({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, @@ -2719,7 +2719,7 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.participants).toBe({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); + expect(thread.participants).toStrictEqual({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, @@ -2959,7 +2959,7 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.participants).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + expect(thread.participants).toStrictEqual({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, From 4d34462c094a86fd489fa082881659b16c94df01 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 19 Aug 2024 00:43:05 +0530 Subject: [PATCH 033/502] FIXES MORE TESTS --- tests/actions/IOUTest.ts | 6 +++--- tests/actions/PolicyTest.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 36ebf4684fae..0b599824dba9 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -100,7 +100,7 @@ describe('actions/IOU', () => { iouReportID = iouReport?.reportID; transactionThread = transactionThreadReport; - expect(iouReport?.participants).toBe({ + expect(iouReport?.participants).toEqual({ [RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, [CARLOS_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, }); @@ -296,7 +296,7 @@ describe('actions/IOU', () => { const iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU); iouReportID = iouReport?.reportID; - expect(iouReport?.participants).toBe({ + expect(iouReport?.participants).toEqual({ [RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, [CARLOS_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}, }); @@ -2631,7 +2631,7 @@ describe('actions/IOU', () => { // Given a transaction thread thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.participants).toBe({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); + expect(thread.participants).toEqual({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); const participantAccountIDs = Object.keys(thread.participants ?? {}).map(Number); const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs); diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 2bbd19ce9dea..2a3d82c8a8ec 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -12,7 +12,9 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const ESH_EMAIL = 'eshgupta1217@gmail.com'; const ESH_ACCOUNT_ID = 1; -const ESH_PARTICIPANT: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, role: 'admin'}; +const ESH_PARTICIPANT_ANNOUNCE_ROOM: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, role: 'admin'}; +const ESH_PARTICIPANT_ADMINS_ROOM: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'admin'}; +const ESH_PARTICIPANT_EXPENSE_CHAT = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'admin'} const WORKSPACE_NAME = "Esh's Workspace"; OnyxUpdateManager(); @@ -78,17 +80,19 @@ describe('actions/Policy', () => { expect(workspaceReports.length).toBe(3); workspaceReports.forEach((report) => { expect(report?.pendingFields?.addWorkspaceRoom).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - expect(report?.participants).toEqual({[ESH_ACCOUNT_ID]: ESH_PARTICIPANT}); switch (report?.chatType) { case CONST.REPORT.CHAT_TYPE.POLICY_ADMINS: { + expect(report?.participants).toEqual({ [ESH_ACCOUNT_ID]: ESH_PARTICIPANT_ADMINS_ROOM }); adminReportID = report.reportID; break; } case CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE: { + expect(report?.participants).toEqual({ [ESH_ACCOUNT_ID]: ESH_PARTICIPANT_ANNOUNCE_ROOM }); announceReportID = report.reportID; break; } case CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT: { + expect(report?.participants).toEqual({ [ESH_ACCOUNT_ID]: ESH_PARTICIPANT_EXPENSE_CHAT }); expenseReportID = report.reportID; break; } From 24fe82850deb9887a952fb45230c6595943d5c88 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:28:58 +0530 Subject: [PATCH 034/502] Apply suggestions from code review Co-authored-by: Ishpaul Singh <104348397+ishpaul777@users.noreply.github.com> --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 26dc000fe6a1..0044b40e1912 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -102,7 +102,7 @@ import type { } from '@src/types/onyx'; import type {Decision} from '@src/types/onyx/OriginalMessage'; import type {ConnectionName} from '@src/types/onyx/Policy'; -import type {NotificationPreference, Participant, Participants, Participant as ReportParticipant, RoomVisibility, WriteCapability} from '@src/types/onyx/Report'; +import type {NotificationPreference, Participants, Participant as ReportParticipant, RoomVisibility, WriteCapability} from '@src/types/onyx/Report'; import type Report from '@src/types/onyx/Report'; import type {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; From cc71ee510ab75833099eae9257d34f64d2956a78 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 19 Aug 2024 10:29:49 +0530 Subject: [PATCH 035/502] Fix lint --- tests/actions/PolicyTest.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 2a3d82c8a8ec..79906534eb2c 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -14,7 +14,7 @@ const ESH_EMAIL = 'eshgupta1217@gmail.com'; const ESH_ACCOUNT_ID = 1; const ESH_PARTICIPANT_ANNOUNCE_ROOM: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, role: 'admin'}; const ESH_PARTICIPANT_ADMINS_ROOM: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'admin'}; -const ESH_PARTICIPANT_EXPENSE_CHAT = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'admin'} +const ESH_PARTICIPANT_EXPENSE_CHAT = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: 'admin'}; const WORKSPACE_NAME = "Esh's Workspace"; OnyxUpdateManager(); @@ -82,17 +82,17 @@ describe('actions/Policy', () => { expect(report?.pendingFields?.addWorkspaceRoom).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); switch (report?.chatType) { case CONST.REPORT.CHAT_TYPE.POLICY_ADMINS: { - expect(report?.participants).toEqual({ [ESH_ACCOUNT_ID]: ESH_PARTICIPANT_ADMINS_ROOM }); + expect(report?.participants).toEqual({[ESH_ACCOUNT_ID]: ESH_PARTICIPANT_ADMINS_ROOM}); adminReportID = report.reportID; break; } case CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE: { - expect(report?.participants).toEqual({ [ESH_ACCOUNT_ID]: ESH_PARTICIPANT_ANNOUNCE_ROOM }); + expect(report?.participants).toEqual({[ESH_ACCOUNT_ID]: ESH_PARTICIPANT_ANNOUNCE_ROOM}); announceReportID = report.reportID; break; } case CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT: { - expect(report?.participants).toEqual({ [ESH_ACCOUNT_ID]: ESH_PARTICIPANT_EXPENSE_CHAT }); + expect(report?.participants).toEqual({[ESH_ACCOUNT_ID]: ESH_PARTICIPANT_EXPENSE_CHAT}); expenseReportID = report.reportID; break; } From 6f5954161c7eefa2175f2c63166be61b96899abd Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 19 Aug 2024 10:38:43 +0200 Subject: [PATCH 036/502] wire up enablePolicyAutoReimbursementLimit --- .../EnablePolicyAutoReimbursementLimit.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/ReportUtils.ts | 2 +- src/libs/actions/Workspace/Rules.ts | 56 ++++++++++++++++++- .../rules/ExpenseReportRulesSection.tsx | 12 ++-- src/types/onyx/Policy.ts | 12 +++- 7 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 src/libs/API/parameters/EnablePolicyAutoReimbursementLimit.ts diff --git a/src/libs/API/parameters/EnablePolicyAutoReimbursementLimit.ts b/src/libs/API/parameters/EnablePolicyAutoReimbursementLimit.ts new file mode 100644 index 000000000000..acbc2efade41 --- /dev/null +++ b/src/libs/API/parameters/EnablePolicyAutoReimbursementLimit.ts @@ -0,0 +1,6 @@ +type EnablePolicyAutoReimbursementLimitParams = { + policyID: string; + enabled: boolean; +}; + +export default EnablePolicyAutoReimbursementLimitParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index ccd7bc3de629..4732f3d9a13c 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -275,3 +275,4 @@ export type {default as SetPolicyPreventSelfApprovalParams} from './SetPolicyPre export type {default as SetPolicyAutomaticApprovalLimitParams} from './SetPolicyAutomaticApprovalLimit'; export type {default as SetPolicyAutomaticApprovalAuditRateParams} from './SetPolicyAutomaticApprovalAuditRate'; export type {default as SetPolicyAutoReimbursementLimitParams} from './SetPolicyAutoReimbursementLimit'; +export type {default as EnablePolicyAutoReimbursementLimitParams} from './EnablePolicyAutoReimbursementLimit'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 8565bde7efbc..c16cc26497a0 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -20,6 +20,7 @@ const WRITE_COMMANDS = { SET_POLICY_AUTOMATIC_APPROVAL_LIMIT: 'SetPolicyAutomaticApprovalLimit', SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE: 'SetPolicyAutomaticApprovalAuditRate', SET_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'SetPolicyAutoReimbursementLimit', + ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'EnablePolicyAutoReimbursementLimit', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -519,6 +520,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT]: Parameters.SetPolicyAutomaticApprovalLimitParams; [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE]: Parameters.SetPolicyAutomaticApprovalAuditRateParams; [WRITE_COMMANDS.SET_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.SetPolicyAutoReimbursementLimitParams; + [WRITE_COMMANDS.ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.EnablePolicyAutoReimbursementLimitParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4baf05773878..ce3c4b30440b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -7047,7 +7047,7 @@ function canBeAutoReimbursed(report: OnyxInputOrEntry, policy: OnyxInput } type CurrencyType = TupleToUnion; const reimbursableTotal = getMoneyRequestSpendBreakdown(report).totalDisplaySpend; - const autoReimbursementLimit = policy.autoReimbursementLimit ?? 0; + const autoReimbursementLimit = policy.autoReimbursement.limit ?? 0; const isAutoReimbursable = isReportInGroupPolicy(report) && policy.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES && diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index c4e0ed2dfee0..65d212446eb9 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -1,10 +1,17 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {SetPolicyAutomaticApprovalLimitParams, SetPolicyAutoReimbursementLimitParams, SetPolicyDefaultReportTitleParams, SetPolicyPreventSelfApprovalParams} from '@libs/API/parameters'; +import type { + EnablePolicyAutoReimbursementLimitParams, + SetPolicyAutomaticApprovalLimitParams, + SetPolicyAutoReimbursementLimitParams, + SetPolicyDefaultReportTitleParams, + SetPolicyPreventSelfApprovalParams, +} from '@libs/API/parameters'; import type SetPolicyAutomaticApprovalAuditRateParams from '@libs/API/parameters/SetPolicyAutomaticApprovalAuditRate'; import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams'; import {WRITE_COMMANDS} from '@libs/API/types'; +import {getPolicy} from '@libs/PolicyUtils'; import ONYXKEYS from '@src/ONYXKEYS'; /** @@ -259,7 +266,6 @@ function setPolicyAutomaticApprovalAuditRate(auditRate: number, policyID: string * @param policyID - id of the policy to apply the limit to */ function setPolicyAutoReimbursementLimit(limit: number, policyID: string) { - console.log('LIMIT ', limit); const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -303,6 +309,51 @@ function setPolicyAutoReimbursementLimit(limit: number, policyID: string) { }); } +/** + * Call the API to deactivate the card and request a new one + * @param enabled - whether auto-payment for the reports is enabled in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function enablePolicyAutoReimbursementLimit(enabled: boolean, policyID: string) { + const policy = getPolicy(policyID); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowAutoReimbursementLimitOption: enabled, + autoReimbursement: { + limit: enabled ? policy?.autoReimbursement?.limit : 10000, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowAutoReimbursementLimitOption: !enabled, + autoReimbursement: { + limit: policy?.autoReimbursement?.limit, + }, + }, + }, + ]; + + const parameters: EnablePolicyAutoReimbursementLimitParams = { + enabled, + policyID, + }; + + API.write(WRITE_COMMANDS.ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT, parameters, { + optimisticData, + failureData, + }); +} + export { modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, @@ -310,4 +361,5 @@ export { setPolicyAutomaticApprovalLimit, setPolicyAutomaticApprovalAuditRate, setPolicyAutoReimbursementLimit, + enablePolicyAutoReimbursementLimit, }; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index ed2e65e39e68..903255ab1851 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {useOnyx} from 'react-native-onyx'; +import {OnyxKey, useOnyx} from 'react-native-onyx'; import MenuItem from '@components/MenuItem'; import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; @@ -86,10 +86,12 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { ? translate('workspace.rules.expenseReportRules.autoPayApprovedReportsLockedSubtitle') : translate('workspace.rules.expenseReportRules.autoPayApprovedReportsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), - onToggle: (isEnabled: boolean) => {}, + onToggle: (isEnabled: boolean) => { + WorkspaceRulesActions.enablePolicyAutoReimbursementLimit(isEnabled, policyID); + }, disabled: autoPayApprovedReportsUnavailable, showLockIcon: autoPayApprovedReportsUnavailable, - // isActive: true, + isActive: policy?.shouldShowAutoReimbursementLimitOption, subMenuItems: [ - {optionItems.map(({title, subtitle, isActive, subMenuItems, showLockIcon, disabled}, index) => { + {optionItems.map(({title, subtitle, isActive, subMenuItems, showLockIcon, disabled, onToggle}, index) => { const showBorderBottom = index !== optionItems.length - 1; return ( @@ -126,7 +128,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { showLockIcon={showLockIcon} disabled={disabled} subMenuItems={subMenuItems} - onToggle={() => {}} + onToggle={onToggle} /> ); })} diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 1deeb37ca38e..3cc680c2733a 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1407,8 +1407,13 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** The reimbursement choice for policy */ reimbursementChoice?: ValueOf; - /** The maximum report total allowed to trigger auto reimbursement. */ - autoReimbursementLimit?: number; + /** Detailed settings for the autoReimbursement */ + autoReimbursement: { + /** + * The maximum report total allowed to trigger auto reimbursement. + */ + limit: number; + }; /** Whether to leave the calling account as an admin on the policy */ makeMeAdmin?: boolean; @@ -1539,6 +1544,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether GL codes are enabled */ glCodes?: boolean; + + /** Is the auto-pay option for the policy enabled */ + shouldShowAutoReimbursementLimitOption?: boolean; } & Partial, 'generalSettings' | 'addWorkspaceRoom' | keyof ACHAccount >; From daa7c9ca310ff3bd67f524575d78ac5692a8e499 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 19 Aug 2024 13:53:50 +0200 Subject: [PATCH 037/502] wire up enablePolicyAutoReimbursementLimit, validate wip --- src/CONST.ts | 1 + src/components/AmountForm.tsx | 2 +- src/libs/ReportUtils.ts | 2 +- src/libs/actions/Workspace/Rules.ts | 36 +++++++++++++++---- .../rules/ExpenseReportRulesSection.tsx | 7 ++-- .../rules/RulesAutoPayReportsUnderPage.tsx | 27 ++++++++++---- 6 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index a3e1c811882f..7c86676e7c1a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2027,6 +2027,7 @@ const CONST = { AUDITOR: 'auditor', USER: 'user', }, + AUTO_REIMBURSEMENT_MAX_LIMIT: 2000000, AUTO_REPORTING_FREQUENCIES: { INSTANT: 'instant', IMMEDIATE: 'immediate', diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 2545364aa701..bd83b1e67e4a 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -47,7 +47,7 @@ type AmountFormProps = { displayAsTextInput?: boolean; } & Pick & - Pick; + Pick; /** * Returns the new selection object based on the updated amount's length diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ce3c4b30440b..0d42b724adf1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -7047,7 +7047,7 @@ function canBeAutoReimbursed(report: OnyxInputOrEntry, policy: OnyxInput } type CurrencyType = TupleToUnion; const reimbursableTotal = getMoneyRequestSpendBreakdown(report).totalDisplaySpend; - const autoReimbursementLimit = policy.autoReimbursement.limit ?? 0; + const autoReimbursementLimit = policy?.autoReimbursement?.limit ?? 0; const isAutoReimbursable = isReportInGroupPolicy(report) && policy.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES && diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 65d212446eb9..cccd07067ec1 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -11,6 +11,7 @@ import type { import type SetPolicyAutomaticApprovalAuditRateParams from '@libs/API/parameters/SetPolicyAutomaticApprovalAuditRate'; import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams'; import {WRITE_COMMANDS} from '@libs/API/types'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import {getPolicy} from '@libs/PolicyUtils'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -265,14 +266,25 @@ function setPolicyAutomaticApprovalAuditRate(auditRate: number, policyID: string * @param limit - max amount for auto-payment for the reports in the given policy * @param policyID - id of the policy to apply the limit to */ -function setPolicyAutoReimbursementLimit(limit: number, policyID: string) { +function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { + const policy = getPolicy(policyID); + const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(limit)); + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, value: { - isLoading: true, - errors: null, + maxExpenseAutoPayAmount: limit, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoReimbursement: { + limit: parsedLimit, + }, }, }, ]; @@ -282,7 +294,16 @@ function setPolicyAutoReimbursementLimit(limit: number, policyID: string) { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, value: { - isLoading: false, + maxExpenseAutoPayAmount: limit, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoReimbursement: { + limit: parsedLimit, + }, }, }, ]; @@ -290,15 +311,16 @@ function setPolicyAutoReimbursementLimit(limit: number, policyID: string) { const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + autoReimbursement: policy?.autoReimbursement, + errors }, }, ]; const parameters: SetPolicyAutoReimbursementLimitParams = { - autoReimbursement: {limit}, + autoReimbursement: {limit: parsedLimit}, policyID, }; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 903255ab1851..3a30cb935e02 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -4,6 +4,7 @@ import MenuItem from '@components/MenuItem'; import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; @@ -94,11 +95,13 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { isActive: policy?.shouldShowAutoReimbursementLimitOption, subMenuItems: [ Navigation.navigate(ROUTES.RULES_AUTO_PAY_REPORTS_UNDER.getRoute(policyID))} + shouldShowBasicTitle + shouldShowDescriptionOnTop />, ], }, diff --git a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx index 3fdff778b1be..977eeee89b1a 100644 --- a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx @@ -1,9 +1,11 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -24,12 +26,23 @@ type RulesAutoPayReportsUnderPageProps = StackScreenProps) => { + // const errors: FormInputErrors = {}; + // if (CurrencyUtils.convertToBackendAmount(parseFloat(maxExpenseAutoPayAmount)) > CONST.POLICY.AUTO_REIMBURSEMENT_MAX_LIMIT) { + // console.log('ERROR'); + // errors[INPUT_IDS.MAX_EXPENSE_AUTO_PAY_AMOUNT] = translate('common.error.fieldRequired'); + // } + // console.log('ERRORS ', errors); + // return errors; + // }; return ( WorkspaceRulesActions.setPolicyAutoReimbursementLimit(parseInt(maxExpenseAutoPayAmount, 10), policyID)} + // validate={validateLimit} + onSubmit={({maxExpenseAutoPayAmount}) => { + WorkspaceRulesActions.setPolicyAutoReimbursementLimit(maxExpenseAutoPayAmount, policyID); + Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); + }} submitButtonText={translate('common.save')} enabledWhenOffline > @@ -59,9 +75,8 @@ function RulesAutoPayReportsUnderPage({route}: RulesAutoPayReportsUnderPageProps label={translate('iou.amount')} InputComponent={AmountForm} inputID={INPUT_IDS.MAX_EXPENSE_AUTO_PAY_AMOUNT} - // currency={policy?.outputCurrency ?? CONST.CURRENCY.USD} - currency={CurrencyUtils.getCurrencySymbol(CONST.CURRENCY.USD)} - // defaultValue="0" + currency={CurrencyUtils.getCurrencySymbol(policy?.outputCurrency ?? CONST.CURRENCY.USD)} + defaultValue={defaultValue} isCurrencyPressable={false} ref={inputCallbackRef} amountMaxLength={7} From b1883395976d52f0a081e3858c034490b495888e Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 19 Aug 2024 14:18:57 +0200 Subject: [PATCH 038/502] wire up enableAutoApprovalOptions --- .../EnablePolicyAutoApprovalOptions.ts | 6 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Workspace/Rules.ts | 43 ++++++++++++++++++- .../rules/ExpenseReportRulesSection.tsx | 8 ++-- .../rules/RulesAutoPayReportsUnderPage.tsx | 1 - src/types/onyx/Policy.ts | 9 +++- 7 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 src/libs/API/parameters/EnablePolicyAutoApprovalOptions.ts diff --git a/src/libs/API/parameters/EnablePolicyAutoApprovalOptions.ts b/src/libs/API/parameters/EnablePolicyAutoApprovalOptions.ts new file mode 100644 index 000000000000..4c80b1bf6d7d --- /dev/null +++ b/src/libs/API/parameters/EnablePolicyAutoApprovalOptions.ts @@ -0,0 +1,6 @@ +type EnablePolicyAutoApprovalOptionsParams = { + policyID: string; + enabled: boolean; +}; + +export default EnablePolicyAutoApprovalOptionsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 4732f3d9a13c..18cfb8bb8033 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -276,3 +276,4 @@ export type {default as SetPolicyAutomaticApprovalLimitParams} from './SetPolicy export type {default as SetPolicyAutomaticApprovalAuditRateParams} from './SetPolicyAutomaticApprovalAuditRate'; export type {default as SetPolicyAutoReimbursementLimitParams} from './SetPolicyAutoReimbursementLimit'; export type {default as EnablePolicyAutoReimbursementLimitParams} from './EnablePolicyAutoReimbursementLimit'; +export type {default as EnablePolicyAutoApprovalOptionsParams} from './EnablePolicyAutoApprovalOptions'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index c16cc26497a0..305258c0de72 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -21,6 +21,7 @@ const WRITE_COMMANDS = { SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE: 'SetPolicyAutomaticApprovalAuditRate', SET_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'SetPolicyAutoReimbursementLimit', ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'EnablePolicyAutoReimbursementLimit', + ENABLE_POLICY_AUTO_APPROVAL_OPTIONS: 'EnablePolicyAutoApprovalOptions', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -521,6 +522,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE]: Parameters.SetPolicyAutomaticApprovalAuditRateParams; [WRITE_COMMANDS.SET_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.SetPolicyAutoReimbursementLimitParams; [WRITE_COMMANDS.ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.EnablePolicyAutoReimbursementLimitParams; + [WRITE_COMMANDS.ENABLE_POLICY_AUTO_APPROVAL_OPTIONS]: Parameters.EnablePolicyAutoApprovalOptionsParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index cccd07067ec1..4c1518940606 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -2,6 +2,7 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; import type { + EnablePolicyAutoApprovalOptionsParams, EnablePolicyAutoReimbursementLimitParams, SetPolicyAutomaticApprovalLimitParams, SetPolicyAutoReimbursementLimitParams, @@ -261,6 +262,43 @@ function setPolicyAutomaticApprovalAuditRate(auditRate: number, policyID: string }); } +/** + * Call the API to enable auto-approval for the reports in the given policy + * @param enabled - whether auto-approve for the reports is enabled in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function enableAutoApprovalOptions(enabled: boolean, policyID: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowAutoApprovalOptions: enabled, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowAutoApprovalOptions: !enabled, + }, + }, + ]; + + const parameters: EnablePolicyAutoApprovalOptionsParams = { + enabled, + policyID, + }; + + API.write(WRITE_COMMANDS.ENABLE_POLICY_AUTO_APPROVAL_OPTIONS, parameters, { + optimisticData, + failureData, + }); +} + /** * Call the API to deactivate the card and request a new one * @param limit - max amount for auto-payment for the reports in the given policy @@ -314,7 +352,7 @@ function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { autoReimbursement: policy?.autoReimbursement, - errors + errors, }, }, ]; @@ -332,7 +370,7 @@ function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { } /** - * Call the API to deactivate the card and request a new one + * Call the API to enable auto-payment for the reports in the given policy * @param enabled - whether auto-payment for the reports is enabled in the given policy * @param policyID - id of the policy to apply the limit to */ @@ -384,4 +422,5 @@ export { setPolicyAutomaticApprovalAuditRate, setPolicyAutoReimbursementLimit, enablePolicyAutoReimbursementLimit, + enableAutoApprovalOptions, }; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 3a30cb935e02..15b9063d3e30 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {OnyxKey, useOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import MenuItem from '@components/MenuItem'; import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; @@ -62,8 +62,10 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { title: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), subtitle: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), - isActive: false, - onToggle: (isEnabled: boolean) => {}, + isActive: policy?.shouldShowAutoApprovalOptions, + onToggle: (isEnabled: boolean) => { + WorkspaceRulesActions.enableAutoApprovalOptions(isEnabled, policyID); + }, subMenuItems: [ ; /** Detailed settings for the autoReimbursement */ - autoReimbursement: { + autoReimbursement?: { /** * The maximum report total allowed to trigger auto reimbursement. */ - limit: number; + limit?: number; }; + /** + * Whether the auto-approval options are enabled in the policy rules + */ + shouldShowAutoApprovalOptions?: boolean; + /** Whether to leave the calling account as an admin on the policy */ makeMeAdmin?: boolean; From 19b5a38ac361bbb3a2ba06fef43efdcd039dc1cf Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:34:35 +0100 Subject: [PATCH 039/502] Make the members view of rooms and expense chats consistent with groups --- src/pages/ReportParticipantsPage.tsx | 52 +++++++++++++++++++++++++--- src/pages/RoomInvitePage.tsx | 4 +-- src/pages/RoomMembersPage.tsx | 5 +-- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index d93156f45e77..8a56522b7496 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -25,6 +25,7 @@ import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import * as Report from '@libs/actions/Report'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -32,6 +33,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; import withReportOrNotFound from './home/report/withReportOrNotFound'; +import SearchInputManager from './workspace/SearchInputManager'; type MemberOption = Omit & {accountID: number}; @@ -55,6 +57,13 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { const isFocused = useIsFocused(); const canSelectMultiple = isGroupChat && isCurrentUserAdmin && (isSmallScreenWidth ? selectionMode?.isEnabled : true); + const [searchValue, setSearchValue] = useState(''); + const [shouldShowTextInput, setShouldShowTextInput] = useState(false); + + useEffect(() => { + setSearchValue(SearchInputManager.searchInput); + }, [isFocused]); + useEffect(() => { if (isFocused) { return; @@ -62,6 +71,20 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { setSelectedMembers([]); }, [isFocused]); + useEffect(() => { + const shouldExcludeHiddenParticipants = !isGroupChat && !isIOUReport; + const chatParticipants = ReportUtils.getParticipantsAccountIDsForDisplay(report, shouldExcludeHiddenParticipants); + const shouldShowInput = chatParticipants.length > CONST.SHOULD_SHOW_MEMBERS_SEARCH_INPUT_BREAKPOINT; + + if (shouldShowTextInput !== shouldShowInput) { + setShouldShowTextInput(shouldShowInput); + } + + if (!shouldShowInput) { + setSearchValue(''); + } + }, [report, isGroupChat, isIOUReport, shouldShowTextInput]); + const getUsers = useCallback((): MemberOption[] => { let result: MemberOption[] = []; const shouldExcludeHiddenParticipants = !isGroupChat && !isIOUReport; @@ -74,6 +97,11 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { return; } + // If search value is provided, filter out members that don't match the search value + if (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue)) { + return; + } + const pendingChatMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); const isSelected = selectedMembers.includes(accountID) && canSelectMultiple; const isAdmin = role === CONST.REPORT.ROLE.ADMIN; @@ -107,7 +135,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { result = result.sort((a, b) => (a.text ?? '').toLowerCase().localeCompare((b.text ?? '').toLowerCase())); return result; - }, [formatPhoneNumber, personalDetails, report, selectedMembers, currentUserAccountID, translate, isGroupChat, isIOUReport, canSelectMultiple]); + }, [searchValue, formatPhoneNumber, personalDetails, report, selectedMembers, currentUserAccountID, translate, isGroupChat, isIOUReport, canSelectMultiple]); const participants = useMemo(() => getUsers(), [getUsers]); @@ -145,6 +173,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { * Open the modal to invite a user */ const inviteUser = useCallback(() => { + setSearchValue(''); Navigation.navigate(ROUTES.REPORT_PARTICIPANTS_INVITE.getRoute(report.reportID)); }, [report]); @@ -156,6 +185,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { // Remove the admin from the list const accountIDsToRemove = selectedMembers.filter((id) => id !== currentUserAccountID); Report.removeFromGroupChat(report.reportID, accountIDsToRemove); + setSearchValue(''); setSelectedMembers([]); setRemoveMembersConfirmModalVisible(false); }; @@ -193,7 +223,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { return; } - return {translate('groupChat.groupMembersListTitle')}; + return {translate('groupChat.groupMembersListTitle')}; }, [styles, translate, isGroupChat]); const customListHeader = useMemo(() => { @@ -268,6 +298,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { customText={translate('workspace.common.selected', {selectedNumber: selectedMembers.length})} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} onPress={() => null} + isSplitButton={false} options={bulkActionsButtonOptions} style={[shouldUseNarrowLayout && styles.flexGrow1]} isDisabled={!selectedMembers.length} @@ -314,6 +345,8 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { const selectionModeHeader = selectionMode?.isEnabled && isSmallScreenWidth; + const headerMessage = searchValue.trim() && !participants.length ? translate('roomMembersPage.memberNotFound') : ''; + return ( - + item && toggleUser(item)} sections={[{data: participants}]} + shouldShowTextInput={shouldShowTextInput} + textInputLabel={translate('selectionList.findMember')} + textInputValue={searchValue} + onChangeText={(value) => { + SearchInputManager.searchInput = value; + setSearchValue(value); + }} + headerMessage={headerMessage} + headerContent={!shouldShowTextInput ? headerContent : undefined} ListItem={TableListItem} - headerContent={headerContent} onSelectRow={openMemberDetails} shouldSingleExecuteRowSelect={!(isGroupChat && isCurrentUserAdmin)} onCheckboxPress={(item) => toggleUser(item)} @@ -371,7 +413,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { showScrollIndicator textInputRef={textInputRef} customListHeader={customListHeader} - listHeaderWrapperStyle={[styles.ph9]} + listHeaderWrapperStyle={[styles.ph9, styles.mt4]} /> diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 17239e6d4fb5..f6d748c82834 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -10,8 +10,8 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useOptionsList} from '@components/OptionListContextProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; +import InviteMemberListItem from '@components/SelectionList/InviteMemberListItem'; import type {Section} from '@components/SelectionList/types'; -import UserListItem from '@components/SelectionList/UserListItem'; import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd'; import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd'; import useDebouncedState from '@hooks/useDebouncedState'; @@ -261,7 +261,7 @@ function RoomInvitePage({ { diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index c8af45c948bd..a4bdd3bfc459 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -135,9 +135,9 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { if (report) { Report.removeFromRoom(report.reportID, selectedMembers); } + setSearchValue(''); setSelectedMembers([]); setRemoveMembersConfirmModalVisible(false); - setSearchValue(''); }; /** @@ -283,6 +283,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} onPress={() => null} options={bulkActionsButtonOptions} + isSplitButton={false} style={[shouldUseNarrowLayout && styles.flexGrow1]} isDisabled={!selectedMembers.length} /> @@ -376,7 +377,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { showLoadingPlaceholder={!OptionsListUtils.isPersonalDetailsReady(personalDetails) || !didLoadRoomMembers} showScrollIndicator shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} - listHeaderWrapperStyle={[styles.ph9]} + listHeaderWrapperStyle={[styles.ph9, styles.mt4]} ListItem={TableListItem} onDismissError={dismissError} /> From 352c58771b216e710b5fa56b5ba0943b2ef36b71 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:46:41 +0100 Subject: [PATCH 040/502] Fixes after merge --- src/pages/ReportParticipantsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index f86618ca3b04..378dabc296d3 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -70,7 +70,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { }, [isFocused]); useEffect(() => { - const chatParticipants = ReportUtils.getParticipantsAccountIDsForDisplay(report, true); + const chatParticipants = ReportUtils.getParticipantsList(report, personalDetails); const shouldShowInput = chatParticipants.length > CONST.SHOULD_SHOW_MEMBERS_SEARCH_INPUT_BREAKPOINT; if (shouldShowTextInput !== shouldShowInput) { @@ -90,7 +90,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { const details = personalDetails?.[accountID]; // If search value is provided, filter out members that don't match the search value - if (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue)) { + if (!details || (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue))) { return; } From bc43dd557097d1878d50152f8391268b0bf691be Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:57:28 +0100 Subject: [PATCH 041/502] fix lint warning --- src/pages/ReportParticipantsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index 378dabc296d3..cae4faa14c79 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -80,7 +80,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { if (!shouldShowInput) { setSearchValue(''); } - }, [report, shouldShowTextInput]); + }, [report, shouldShowTextInput, personalDetails]); const getUsers = useCallback((): MemberOption[] => { let result: MemberOption[] = []; From 36160d32b4ef8c6781632f52f150c6a00f43016f Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 19 Aug 2024 16:05:29 +0200 Subject: [PATCH 042/502] wire up auto approve reports under --- src/libs/actions/Workspace/Rules.ts | 6 ++++-- .../rules/ExpenseReportRulesSection.tsx | 13 ++++++------- .../rules/RulesAutoApproveReportsUnderPage.tsx | 16 +++++++++------- src/types/onyx/Policy.ts | 8 ++++++++ 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 4c1518940606..ba114185a452 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -169,7 +169,9 @@ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: st * @param limit - max amount for auto-approval of the reports in the given policy * @param policyID - id of the policy to apply the limit to */ -function setPolicyAutomaticApprovalLimit(limit: number, policyID: string) { +function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { + const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(limit)); + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -202,7 +204,7 @@ function setPolicyAutomaticApprovalLimit(limit: number, policyID: string) { ]; const parameters: SetPolicyAutomaticApprovalLimitParams = { - limit, + limit: parsedLimit, policyID, }; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 15b9063d3e30..d328df62402b 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {useOnyx} from 'react-native-onyx'; import MenuItem from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -67,14 +68,14 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { WorkspaceRulesActions.enableAutoApprovalOptions(isEnabled, policyID); }, subMenuItems: [ - Navigation.navigate(ROUTES.RULES_AUTO_APPROVE_REPORTS_UNDER.getRoute(policyID))} />, - Navigation.navigate(ROUTES.RULES_AUTO_PAY_REPORTS_UNDER.getRoute(policyID))} - shouldShowBasicTitle - shouldShowDescriptionOnTop />, ], }, diff --git a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx index 4328e6e7ea64..89965bce5548 100644 --- a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx @@ -1,6 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -24,12 +25,13 @@ type RulesAutoApproveReportsUnderPageProps = StackScreenProps WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(parseInt(maxExpenseAutoApprovalAmount, 10), policyID)} + onSubmit={({maxExpenseAutoApprovalAmount}) => { + WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(parseInt(maxExpenseAutoApprovalAmount, 10), policyID); + Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); + }} submitButtonText={translate('common.save')} enabledWhenOffline > @@ -59,13 +64,10 @@ function RulesAutoApproveReportsUnderPage({route}: RulesAutoApproveReportsUnderP label={translate('iou.amount')} InputComponent={AmountForm} inputID={INPUT_IDS.MAX_EXPENSE_AUTO_APPROVAL_AMOUNT} - // currency={policy?.outputCurrency ?? CONST.CURRENCY.USD} - currency={CurrencyUtils.getCurrencySymbol(CONST.CURRENCY.USD)} - // defaultValue="0" + currency={CurrencyUtils.getCurrencySymbol(policy?.outputCurrency ?? CONST.CURRENCY.USD)} + defaultValue={defaultValue} isCurrencyPressable={false} ref={inputCallbackRef} - // @TODO Replace it when maxLength of this field is known - amountMaxLength={20} displayAsTextInput /> {translate('workspace.rules.expenseReportRules.autoApproveReportsUnderDescription')} diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index d482a5a28617..d6adc993fc23 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1420,6 +1420,14 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< */ shouldShowAutoApprovalOptions?: boolean; + /** Detailed settings for the autoApproval */ + autoApproval?: { + /** + * The maximum report total allowed to trigger auto approval. + */ + limit?: number; + }; + /** Whether to leave the calling account as an admin on the policy */ makeMeAdmin?: boolean; From 7e725244d2352d3481134f3af7aaf6301c3970c9 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 20 Aug 2024 10:23:42 +0200 Subject: [PATCH 043/502] wire up random report audit --- src/libs/actions/Workspace/Rules.ts | 57 +++++++------------ .../rules/ExpenseReportRulesSection.tsx | 4 +- .../RulesAutoApproveReportsUnderPage.tsx | 2 +- .../rules/RulesRandomReportAuditPage.tsx | 11 +++- src/types/onyx/Policy.ts | 4 ++ 5 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index ba114185a452..522207f81606 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -171,24 +171,16 @@ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: st */ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(limit)); + const policy = getPolicy(policyID); const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM, - value: { - isLoading: true, - errors: null, - }, - }, - ]; - - const successData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + autoApproval: { + limit: parsedLimit, + }, }, }, ]; @@ -196,9 +188,11 @@ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + autoApproval: { + limit: policy?.autoApproval?.limit, + }, }, }, ]; @@ -210,7 +204,6 @@ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT, parameters, { optimisticData, - successData, failureData, }); } @@ -220,24 +213,18 @@ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { * @param auditRate - percentage of the reports to be qualified for a random audit * @param policyID - id of the policy to apply the limit to */ -function setPolicyAutomaticApprovalAuditRate(auditRate: number, policyID: string) { - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM, - value: { - isLoading: true, - errors: null, - }, - }, - ]; +function setPolicyAutomaticApprovalAuditRate(auditRate: string, policyID: string) { + const parsedAuditRate = parseInt(auditRate, 10); + const policy = getPolicy(policyID); - const successData: OnyxUpdate[] = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + autoApproval: { + auditRate: parsedAuditRate, + }, }, }, ]; @@ -245,21 +232,22 @@ function setPolicyAutomaticApprovalAuditRate(auditRate: number, policyID: string const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + autoApproval: { + auditRate: policy?.autoApproval?.auditRate, + }, }, }, ]; const parameters: SetPolicyAutomaticApprovalAuditRateParams = { - auditRate, + auditRate: parsedAuditRate, policyID, }; API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE, parameters, { optimisticData, - successData, failureData, }); } @@ -354,7 +342,6 @@ function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { autoReimbursement: policy?.autoReimbursement, - errors, }, }, ]; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index d328df62402b..5475963ba026 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -76,8 +76,8 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { onPress={() => Navigation.navigate(ROUTES.RULES_AUTO_APPROVE_REPORTS_UNDER.getRoute(policyID))} />, Navigation.navigate(ROUTES.RULES_RANDOM_REPORT_AUDIT.getRoute(policyID))} diff --git a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx index 89965bce5548..c632bd138d2d 100644 --- a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx @@ -53,7 +53,7 @@ function RulesAutoApproveReportsUnderPage({route}: RulesAutoApproveReportsUnderP formID={ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM} // validate={validator} onSubmit={({maxExpenseAutoApprovalAmount}) => { - WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(parseInt(maxExpenseAutoApprovalAmount, 10), policyID); + WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(maxExpenseAutoApprovalAmount, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} diff --git a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx index ee11a05e19af..2631f9c2ff68 100644 --- a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx +++ b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx @@ -1,6 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -23,12 +24,13 @@ type RulesRandomReportAuditPageProps = StackScreenProps WorkspaceRulesActions.setPolicyAutomaticApprovalAuditRate(parseInt(auditRatePercentage, 10), policyID)} + onSubmit={({auditRatePercentage}) => { + WorkspaceRulesActions.setPolicyAutomaticApprovalAuditRate(auditRatePercentage, policyID); + Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); + }} submitButtonText={translate('common.save')} enabledWhenOffline > @@ -57,6 +61,7 @@ function RulesRandomReportAuditPage({route}: RulesRandomReportAuditPageProps) { diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index d6adc993fc23..bf72b4940d17 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1426,6 +1426,10 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< * The maximum report total allowed to trigger auto approval. */ limit?: number; + /** + * Percentage of the reports that should be selected for a random audit + */ + auditRate?: number; }; /** Whether to leave the calling account as an admin on the policy */ From 5b756eec8b89f281db4986dd7c885171c21a076c Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 20 Aug 2024 14:41:50 +0530 Subject: [PATCH 044/502] Implemented chat type in search --- ...mple-illustration__commentbubbles_blue.svg | 22 ++++ src/CONST.ts | 9 ++ src/ROUTES.ts | 9 +- src/components/Icon/Illustrations.ts | 2 + src/components/Search/SearchPageHeader.tsx | 7 +- src/components/Search/SearchStatusBar.tsx | 44 ++++++- src/components/Search/index.tsx | 38 +++--- src/components/Search/types.ts | 4 +- src/components/SelectionList/ChatListItem.tsx | 105 +++++++++++++++++ .../SelectionList/SearchTableHeader.tsx | 13 ++- src/components/SelectionList/types.ts | 31 ++++- src/languages/en.ts | 5 + src/languages/es.ts | 5 + src/libs/Navigation/types.ts | 1 + src/libs/SearchUtils.ts | 109 +++++++++++++++--- src/pages/Search/EmptySearchView.tsx | 1 + src/pages/Search/SearchTypeMenu.tsx | 6 + src/types/onyx/SearchResults.ts | 54 ++++++++- 18 files changed, 420 insertions(+), 45 deletions(-) create mode 100644 assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg create mode 100644 src/components/SelectionList/ChatListItem.tsx diff --git a/assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg b/assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg new file mode 100644 index 000000000000..9c0711fcaedc --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index b31bcc424c84..ad9529b2fc34 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5253,6 +5253,7 @@ const CONST = { EXPENSE: 'expense', INVOICE: 'invoice', TRIP: 'trip', + CHAT: 'chat', }, ACTION_TYPES: { VIEW: 'view', @@ -5295,6 +5296,14 @@ const CONST = { APPROVED: 'approved', PAID: 'paid', }, + CHAT: { + ALL: 'all', + UNREAD: 'unread', + DRAFTS: 'drafts', + SENT: 'sent', + ATTACHMENTS: 'attachments', + LINKS: 'links', + }, }, TABLE_COLUMNS: { RECEIPT: 'receipt', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 893fd59e38b4..4d410ed4b76d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -54,8 +54,13 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_TO: 'search/filters/to', SEARCH_REPORT: { - route: 'search/view/:reportID', - getRoute: (reportID: string) => `search/view/${reportID}` as const, + route: 'search/view/:reportID/:reportActionID?', + getRoute: (reportID: string, reportActionID?: string) => { + if (reportActionID) { + return `search/view/${reportID}/${reportActionID}` as const; + } + return `search/view/${reportID}` as const; + }, }, TRANSACTION_HOLD_REASON_RHP: 'search/hold', diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 64058a495231..0325c6624b30 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -51,6 +51,7 @@ import CheckmarkCircle from '@assets/images/simple-illustrations/simple-illustra import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__coffeemug.svg'; import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg'; import CommentBubbles from '@assets/images/simple-illustrations/simple-illustration__commentbubbles.svg'; +import CommentBubblesBlue from '@assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg'; import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__company-card.svg'; import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg'; import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg'; @@ -173,6 +174,7 @@ export { SmartScan, Hourglass, CommentBubbles, + CommentBubblesBlue, TrashCan, TeleScope, Profile, diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index 2b59e2046484..c2035d6f6a3c 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -9,7 +9,7 @@ import type HeaderWithBackButtonProps from '@components/HeaderWithBackButton/typ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; -import type {ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import Text from '@components/Text'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; @@ -95,7 +95,7 @@ type SearchPageHeaderProps = { isCustomQuery: boolean; setOfflineModalOpen?: () => void; setDownloadErrorModalOpen?: () => void; - data?: TransactionListItemType[] | ReportListItemType[]; + data?: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[]; }; type SearchHeaderOptionValue = DeepValueOf | undefined; @@ -111,6 +111,8 @@ function getHeaderContent(type: SearchDataTypes): HeaderContent { return {icon: Illustrations.EnvelopeReceipt, titleText: 'workspace.common.invoices'}; case CONST.SEARCH.DATA_TYPES.TRIP: return {icon: Illustrations.Luggage, titleText: 'travel.trips'}; + case CONST.SEARCH.DATA_TYPES.CHAT: + return {icon: Illustrations.CommentBubblesBlue, titleText: 'common.chats'}; case CONST.SEARCH.DATA_TYPES.EXPENSE: default: return {icon: Illustrations.MoneyReceipts, titleText: 'common.expenses'}; @@ -135,6 +137,7 @@ function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModa .filter( (item) => !SearchUtils.isTransactionListItemType(item) && + !SearchUtils.isReportActionListItemType(item) && item.reportID && item.transactions.every((transaction: {keyForList: string | number}) => selectedTransactions[transaction.keyForList]?.isSelected), ) diff --git a/src/components/Search/SearchStatusBar.tsx b/src/components/Search/SearchStatusBar.tsx index 7c1ffeff1818..9ef1f2abb84a 100644 --- a/src/components/Search/SearchStatusBar.tsx +++ b/src/components/Search/SearchStatusBar.tsx @@ -13,7 +13,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type IconAsset from '@src/types/utils/IconAsset'; -import type {ExpenseSearchStatus, InvoiceSearchStatus, SearchQueryString, SearchStatus, TripSearchStatus} from './types'; +import type {ChatSearchStatus, ExpenseSearchStatus, InvoiceSearchStatus, SearchQueryString, SearchStatus, TripSearchStatus} from './types'; type SearchStatusBarProps = { type: SearchDataTypes; @@ -107,12 +107,54 @@ const tripOptions: Array<{key: TripSearchStatus; icon: IconAsset; text: Translat }, ]; +const chatOptions: Array<{key: ChatSearchStatus; icon: IconAsset; text: TranslationPaths; query: SearchQueryString}> = [ + { + key: CONST.SEARCH.STATUS.CHAT.ALL, + icon: Expensicons.All, + text: 'common.all', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.ALL), + }, + { + key: CONST.SEARCH.STATUS.CHAT.UNREAD, + icon: Expensicons.ChatBubbleUnread, + text: 'common.unread', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.UNREAD), + }, + // This will be added back in a future PR when we sync the draft across all platforms + // { + // key: CONST.SEARCH.STATUS.CHAT.DRAFTS, + // icon: Expensicons.Pencil, + // text: 'common.drafts', + // query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.DRAFTS), + // }, + { + key: CONST.SEARCH.STATUS.CHAT.SENT, + icon: Expensicons.Send, + text: 'common.sent', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.SENT), + }, + { + key: CONST.SEARCH.STATUS.CHAT.ATTACHMENTS, + icon: Expensicons.Document, + text: 'common.attachments', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.ATTACHMENTS), + }, + { + key: CONST.SEARCH.STATUS.CHAT.LINKS, + icon: Expensicons.Paperclip, + text: 'common.links', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.LINKS), + }, +]; + function getOptions(type: SearchDataTypes) { switch (type) { case CONST.SEARCH.DATA_TYPES.INVOICE: return invoiceOptions; case CONST.SEARCH.DATA_TYPES.TRIP: return tripOptions; + case CONST.SEARCH.DATA_TYPES.CHAT: + return chatOptions; case CONST.SEARCH.DATA_TYPES.EXPENSE: default: return expenseOptions; diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index db729a9aa77d..6973284c5c59 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -6,7 +6,7 @@ import {useOnyx} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import SearchTableHeader from '@components/SelectionList/SearchTableHeader'; -import type {ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import SelectionListWithModal from '@components/SelectionListWithModal'; import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; import useLocalize from '@hooks/useLocalize'; @@ -54,7 +54,10 @@ function mapToTransactionItemWithSelectionInfo(item: TransactionListItemType, se return {...item, isSelected: selectedTransactions[item.keyForList]?.isSelected && canSelectMultiple}; } -function mapToItemWithSelectionInfo(item: TransactionListItemType | ReportListItemType, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean) { +function mapToItemWithSelectionInfo(item: TransactionListItemType | ReportListItemType | ReportActionListItemType, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean) { + if (SearchUtils.isReportActionListItemType(item)) { + return item; + } return SearchUtils.isTransactionListItemType(item) ? mapToTransactionItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple) : { @@ -142,8 +145,8 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { }; const getItemHeight = useCallback( - (item: TransactionListItemType | ReportListItemType) => { - if (SearchUtils.isTransactionListItemType(item)) { + (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { + if (SearchUtils.isTransactionListItemType(item) || SearchUtils.isReportActionListItemType(item)) { return isLargeScreenWidth ? variables.optionRowHeight + listItemPadding : transactionItemMobileHeight + listItemPadding; } @@ -161,7 +164,7 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { [isLargeScreenWidth], ); - const getItemHeightMemoized = memoize((item: TransactionListItemType | ReportListItemType) => getItemHeight(item), { + const getItemHeightMemoized = memoize((item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => getItemHeight(item), { transformKey: ([item]) => { // List items are displayed differently on "L"arge and "N"arrow screens so the height will differ // in addition the same items might be displayed as part of different Search screens ("Expenses", "All", "Finished") @@ -210,9 +213,9 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { return null; } - const ListItem = SearchUtils.getListItem(status); - const data = SearchUtils.getSections(status, searchResults.data, searchResults.search); - const sortedData = SearchUtils.getSortedSections(status, data, sortBy, sortOrder); + const ListItem = SearchUtils.getListItem(type, status); + const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search); + const sortedData = SearchUtils.getSortedSections(type, status, data, sortBy, sortOrder); const sortedSelectedData = sortedData.map((item) => mapToItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple)); const shouldShowEmptyState = !isDataLoaded || data.length === 0; @@ -234,7 +237,10 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { ); } - const toggleTransaction = (item: TransactionListItemType | ReportListItemType) => { + const toggleTransaction = (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { + if (SearchUtils.isReportActionListItemType(item)) { + return; + } if (SearchUtils.isTransactionListItemType(item)) { if (!item.keyForList) { return; @@ -261,7 +267,7 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { }); }; - const openReport = (item: TransactionListItemType | ReportListItemType) => { + const openReport = (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { let reportID = SearchUtils.isTransactionListItemType(item) && !item.isFromOneTransactionReport ? item.transactionThreadReportID : item.reportID; if (!reportID) { @@ -274,6 +280,12 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { SearchActions.createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID); } + if (SearchUtils.isReportActionListItemType(item)) { + const reportActionID = item.reportActionID; + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(reportID, reportActionID)); + return; + } + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(reportID)); }; @@ -326,9 +338,9 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { type={type} status={status} /> - + sections={[{data: sortedSelectedData, isDisabled: false}]} - turnOnSelectionModeOnLongPress + turnOnSelectionModeOnLongPress={type !== CONST.SEARCH.DATA_TYPES.CHAT} onTurnOnSelectionMode={(item) => item && toggleTransaction(item)} onCheckboxPress={toggleTransaction} onSelectAll={toggleAllTransactions} @@ -345,7 +357,7 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { /> ) } - canSelectMultiple={canSelectMultiple} + canSelectMultiple={type !== CONST.SEARCH.DATA_TYPES.CHAT && canSelectMultiple} customListHeaderHeight={searchHeaderHeight} // To enhance the smoothness of scrolling and minimize the risk of encountering blank spaces during scrolling, // we have configured a larger windowSize and a longer delay between batch renders. diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index c933deb8ee03..50564a9977e8 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -28,7 +28,8 @@ type SearchColumnType = ValueOf; type ExpenseSearchStatus = ValueOf; type InvoiceSearchStatus = ValueOf; type TripSearchStatus = ValueOf; -type SearchStatus = ExpenseSearchStatus | InvoiceSearchStatus | TripSearchStatus; +type ChatSearchStatus = ValueOf; +type SearchStatus = ExpenseSearchStatus | InvoiceSearchStatus | TripSearchStatus | ChatSearchStatus; type SearchContext = { currentSearchHash: number; @@ -87,4 +88,5 @@ export type { ExpenseSearchStatus, InvoiceSearchStatus, TripSearchStatus, + ChatSearchStatus, }; diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx new file mode 100644 index 000000000000..553fd7f0ec09 --- /dev/null +++ b/src/components/SelectionList/ChatListItem.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import {View} from 'react-native'; +import MultipleAvatars from '@components/MultipleAvatars'; +import TextWithTooltip from '@components/TextWithTooltip'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import ReportActionItemDate from '@pages/home/report/ReportActionItemDate'; +import ReportActionItemFragment from '@pages/home/report/ReportActionItemFragment'; +import CONST from '@src/CONST'; +import BaseListItem from './BaseListItem'; +import type {ChatListItemProps, ListItem, ReportActionListItemType} from './types'; + +function ChatListItem({ + item, + isFocused, + showTooltip, + isDisabled, + canSelectMultiple, + onSelectRow, + onDismissError, + onFocus, + onLongPressRow, + shouldSyncFocus, +}: ChatListItemProps) { + const reportActionItem = item as unknown as ReportActionListItemType; + const from = reportActionItem.from; + const icons = [ + { + type: CONST.ICON_TYPE_AVATAR, + source: from.avatar, + name: reportActionItem.formattedFrom, + id: from.accountID, + }, + ]; + const fragment = { + ...reportActionItem.message, + type: 'COMMENT', + }; + const styles = useThemeStyles(); + const theme = useTheme(); + const StyleUtils = useStyleUtils(); + + const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; + const hoveredBackgroundColor = styles.sidebarLinkHover?.backgroundColor ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; + + return ( + + {(hovered) => ( + <> + + + + + + + + + + + + )} + + ); +} + +ChatListItem.displayName = 'ChatListItem'; + +export default ChatListItem; diff --git a/src/components/SelectionList/SearchTableHeader.tsx b/src/components/SelectionList/SearchTableHeader.tsx index a5547eb9e710..d9565697a87f 100644 --- a/src/components/SelectionList/SearchTableHeader.tsx +++ b/src/components/SelectionList/SearchTableHeader.tsx @@ -85,6 +85,13 @@ const expenseHeaders: SearchColumnConfig[] = [ }, ]; +const SearchColumns = { + [CONST.SEARCH.DATA_TYPES.EXPENSE]: expenseHeaders, + [CONST.SEARCH.DATA_TYPES.INVOICE]: expenseHeaders, + [CONST.SEARCH.DATA_TYPES.TRIP]: expenseHeaders, + [CONST.SEARCH.DATA_TYPES.CHAT]: null, +}; + type SearchTableHeaderProps = { data: OnyxTypes.SearchResults['data']; metadata: OnyxTypes.SearchResults['search']; @@ -102,6 +109,10 @@ function SearchTableHeader({data, metadata, sortBy, sortOrder, onSortPress, shou const {translate} = useLocalize(); const displayNarrowVersion = isMediumScreenWidth || isSmallScreenWidth; + if (SearchColumns[metadata.type] === null) { + return; + } + if (displayNarrowVersion) { return; } @@ -109,7 +120,7 @@ function SearchTableHeader({data, metadata, sortBy, sortOrder, onSortPress, shou return ( - {expenseHeaders.map(({columnName, translationKey, shouldShow, isColumnSortable}) => { + {SearchColumns[metadata.type]?.map(({columnName, translationKey, shouldShow, isColumnSortable}) => { if (!shouldShow(data, metadata)) { return null; } diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index d1997aedfdf7..ab4193503f37 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -5,10 +5,11 @@ import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; import type CursorStyles from '@styles/utils/cursor/types'; import type CONST from '@src/CONST'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type {SearchPersonalDetails, SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults'; +import type {SearchPersonalDetails, SearchReport, SearchReportAction, SearchTransaction} from '@src/types/onyx/SearchResults'; import type {ReceiptErrors} from '@src/types/onyx/Transaction'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type IconAsset from '@src/types/utils/IconAsset'; +import type ChatListItem from './ChatListItem'; import type InviteMemberListItem from './InviteMemberListItem'; import type RadioListItem from './RadioListItem'; import type ReportListItem from './Search/ReportListItem'; @@ -197,6 +198,21 @@ type TransactionListItemType = ListItem & keyForList: string; }; +type ReportActionListItemType = ListItem & + SearchReportAction & { + /** The personal details of the user posting comment */ + from: SearchPersonalDetails; + + /** final and formatted "from" value used for displaying and sorting */ + formattedFrom: string; + + /** final "date" value used for sorting */ + date: string; + + /** Key used internally by React */ + keyForList: string; + }; + type ReportListItemType = ListItem & SearchReport & { /** The personal details of the user requesting money */ @@ -268,7 +284,16 @@ type TransactionListItemProps = ListItemProps; type ReportListItemProps = ListItemProps; -type ValidListItem = typeof RadioListItem | typeof UserListItem | typeof TableListItem | typeof InviteMemberListItem | typeof TransactionListItem | typeof ReportListItem; +type ChatListItemProps = ListItemProps; + +type ValidListItem = + | typeof RadioListItem + | typeof UserListItem + | typeof TableListItem + | typeof InviteMemberListItem + | typeof TransactionListItem + | typeof ReportListItem + | typeof ChatListItem; type Section = { /** Title of the section */ @@ -544,4 +569,6 @@ export type { TransactionListItemType, UserListItemProps, ValidListItem, + ReportActionListItemType, + ChatListItemProps, }; diff --git a/src/languages/en.ts b/src/languages/en.ts index baba4bebd653..7715d87bae2b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -144,6 +144,7 @@ export default { buttonConfirm: 'Got it', name: 'Name', attachment: 'Attachment', + attachments: 'Attachments', center: 'Center', from: 'From', to: 'To', @@ -376,6 +377,10 @@ export default { network: 'Network', reportID: 'Report ID', outstanding: 'Outstanding', + chats: 'Chats', + unread: 'Unread', + sent: 'Sent', + links: 'Links', }, location: { useCurrent: 'Use current location', diff --git a/src/languages/es.ts b/src/languages/es.ts index 877783a84725..8dd069f1fa6f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -134,6 +134,7 @@ export default { buttonConfirm: 'Ok, entendido', name: 'Nombre', attachment: 'Archivo adjunto', + attachments: 'Archivos adjuntos', from: 'De', to: 'A', optional: 'Opcional', @@ -366,6 +367,10 @@ export default { network: 'La red', reportID: 'ID del informe', outstanding: 'Pendiente', + chats: 'Chats', + unread: 'No leído', + sent: 'Enviado', + links: 'Enlaces', }, connectionComplete: { title: 'Conexión completa', diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b689f36d8a35..4098a0212aa4 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1301,6 +1301,7 @@ type AuthScreensParamList = CentralPaneScreensParamList & type SearchReportParamList = { [SCREENS.SEARCH.REPORT_RHP]: { reportID: string; + reportActionID?: string; }; }; diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 48af780eab5b..8a7e1ac2603c 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -1,8 +1,9 @@ import type {ValueOf} from 'type-fest'; import type {ASTNode, QueryFilter, QueryFilters, SearchColumnType, SearchQueryJSON, SearchQueryString, SearchStatus, SortOrder} from '@components/Search/types'; +import ChatListItem from '@components/SelectionList/ChatListItem'; import ReportListItem from '@components/SelectionList/Search/ReportListItem'; import TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; -import type {ListItem, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import type {ListItem, ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -76,10 +77,32 @@ function getTransactionItemCommonFormattedProperties( }; } +type ReportKey = `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`; + +type TransactionKey = `${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`; + +type ReportActionKey = `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS}${string}`; + +function isReportEntry(key: string): key is ReportKey { + return key.startsWith(ONYXKEYS.COLLECTION.REPORT); +} + +function isTransactionEntry(key: string): key is TransactionKey { + return key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION); +} + +function isReportActionEntry(key: string): key is ReportActionKey { + return key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS); +} + function getShouldShowMerchant(data: OnyxTypes.SearchResults['data']): boolean { - return Object.values(data).some((item) => { - const merchant = item.modifiedMerchant ? item.modifiedMerchant : item.merchant ?? ''; - return merchant !== '' && merchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && merchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; + return Object.keys(data).some((key) => { + if (isTransactionEntry(key)) { + const item = data[key]; + const merchant = item.modifiedMerchant ? item.modifiedMerchant : item.merchant ?? ''; + return merchant !== '' && merchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && merchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; + } + return false; }); } @@ -89,11 +112,16 @@ function isReportListItemType(item: ListItem): item is ReportListItemType { return 'transactions' in item; } -function isTransactionListItemType(item: TransactionListItemType | ReportListItemType): item is TransactionListItemType { +function isTransactionListItemType(item: TransactionListItemType | ReportListItemType | ReportActionListItemType): item is TransactionListItemType { const transactionListItem = item as TransactionListItemType; return transactionListItem.transactionID !== undefined; } +function isReportActionListItemType(item: TransactionListItemType | ReportListItemType | ReportActionListItemType): item is ReportActionListItemType { + const reportActionListItem = item as ReportActionListItemType; + return reportActionListItem.reportActionID !== undefined; +} + function shouldShowYear(data: TransactionListItemType[] | ReportListItemType[] | OnyxTypes.SearchResults['data']): boolean { if (Array.isArray(data)) { return data.some((item: TransactionListItemType | ReportListItemType) => { @@ -110,14 +138,23 @@ function shouldShowYear(data: TransactionListItemType[] | ReportListItemType[] | }); } - for (const [key, transactionItem] of Object.entries(data)) { - if (key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)) { - const item = transactionItem as SearchTransaction; + for (const key in data) { + if (isTransactionEntry(key)) { + const item = data[key]; const date = TransactionUtils.getCreated(item); if (DateUtils.doesDateBelongToAPastYear(date)) { return true; } + } else if (isReportActionEntry(key)) { + const item = data[key]; + for (const action of Object.values(item)) { + const date = action.created; + + if (DateUtils.doesDateBelongToAPastYear(date)) { + return true; + } + } } } return false; @@ -128,9 +165,10 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata const doesDataContainAPastYearTransaction = shouldShowYear(data); - return Object.entries(data) - .filter(([key]) => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)) - .map(([, transactionItem]) => { + return Object.keys(data) + .filter(isTransactionEntry) + .map((key) => { + const transactionItem = data[key]; const from = data.personalDetailsList?.[transactionItem.accountID]; const to = data.personalDetailsList?.[transactionItem.managerID]; @@ -155,6 +193,31 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata }); } +function getReportActionsSections(data: OnyxTypes.SearchResults['data']): ReportActionListItemType[] { + const reportActionItems: ReportActionListItemType[] = []; + for (const key in data) { + if (isReportActionEntry(key)) { + const reportActions = data[key]; + for (const reportAction of Object.values(reportActions)) { + const from = data.personalDetailsList?.[reportAction.accountID] ?? { + accountID: 16802863, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_8.png', + displayName: 'Shubham Agrawal', + login: 'work.sa1206+sdm3@gmail.com', + }; + reportActionItems.push({ + ...reportAction, + from, + formattedFrom: from?.displayName ?? from?.login ?? '', + date: reportAction.created, + keyForList: reportAction.reportActionID, + }); + } + } + } + return reportActionItems; +} + function getIOUReportName(data: OnyxTypes.SearchResults['data'], reportItem: SearchReport) { const payerPersonalDetails = data.personalDetailsList?.[reportItem.managerID ?? 0]; const payerName = payerPersonalDetails?.displayName ?? payerPersonalDetails?.login ?? translateLocal('common.hidden'); @@ -183,7 +246,7 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx const reportIDToTransactions: Record = {}; for (const key in data) { - if (key.startsWith(ONYXKEYS.COLLECTION.REPORT)) { + if (isReportEntry(key)) { const reportItem = {...data[key]}; const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportItem.reportID}`; const transactions = reportIDToTransactions[reportKey]?.transactions ?? []; @@ -192,12 +255,12 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx reportIDToTransactions[reportKey] = { ...reportItem, keyForList: reportItem.reportID, - from: data.personalDetailsList?.[reportItem.accountID], - to: data.personalDetailsList?.[reportItem.managerID], + from: data.personalDetailsList?.[reportItem.accountID ?? -1], + to: data.personalDetailsList?.[reportItem.managerID ?? -1], transactions, reportName: isIOUReport ? getIOUReportName(data, reportItem) : reportItem.reportName, }; - } else if (key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)) { + } else if (isTransactionEntry(key)) { const transactionItem = {...data[key]}; const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`; @@ -233,15 +296,24 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx return Object.values(reportIDToTransactions); } -function getListItem(status: SearchStatus): ListItemType { +function getListItem(type: SearchDataTypes, status: SearchStatus): ListItemType { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return ChatListItem; + } return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? TransactionListItem : ReportListItem; } -function getSections(status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) { +function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return getReportActionsSections(data); + } return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? getTransactionsSections(data, metadata) : getReportSections(data, metadata); } -function getSortedSections(status: SearchStatus, data: ListItemDataType, sortBy?: SearchColumnType, sortOrder?: SortOrder) { +function getSortedSections(type: SearchDataTypes, status: SearchStatus, data: ListItemDataType, sortBy?: SearchColumnType, sortOrder?: SortOrder) { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return data; + } return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? getSortedTransactionData(data as TransactionListItemType[], sortBy, sortOrder) : getSortedReportData(data as ReportListItemType[]); } @@ -544,6 +616,7 @@ export { isReportListItemType, isSearchResultsEmpty, isTransactionListItemType, + isReportActionListItemType, normalizeQuery, shouldShowYear, buildCannedSearchQuery, diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index 88b2a38b3603..1f259c96d625 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -32,6 +32,7 @@ function EmptySearchView({type}: EmptySearchViewProps) { buttonText: translate('search.searchResults.emptyTripResults.buttonText'), buttonAction: () => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS), }; + case CONST.SEARCH.DATA_TYPES.CHAT: case CONST.SEARCH.DATA_TYPES.EXPENSE: case CONST.SEARCH.DATA_TYPES.INVOICE: default: diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index a2d84209114b..45d136fe5f25 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -43,6 +43,12 @@ function SearchTypeMenu({queryJSON, isCustomQuery}: SearchTypeMenuProps) { icon: Expensicons.Receipt, route: ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery()}), }, + { + title: translate('common.chats'), + type: CONST.SEARCH.DATA_TYPES.CHAT, + icon: Expensicons.ChatBubbles, + route: ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.TRIP.ALL)}), + }, { title: translate('workspace.common.invoices'), type: CONST.SEARCH.DATA_TYPES.INVOICE, diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index e67b5b30f1d6..332d74b8a9bb 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -1,18 +1,28 @@ import type {ValueOf} from 'type-fest'; import type {SearchStatus} from '@components/Search/types'; +import type ChatListItem from '@components/SelectionList/ChatListItem'; import type ReportListItem from '@components/SelectionList/Search/ReportListItem'; import type TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; -import type {ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import type CONST from '@src/CONST'; +import type ONYXKEYS from '@src/ONYXKEYS'; /** Types of search data */ type SearchDataTypes = ValueOf; /** Model of search result list item */ -type ListItemType = T extends typeof CONST.SEARCH.STATUS.EXPENSE.ALL ? typeof TransactionListItem : typeof ReportListItem; +type ListItemType = C extends typeof CONST.SEARCH.DATA_TYPES.CHAT + ? typeof ChatListItem + : T extends typeof CONST.SEARCH.STATUS.EXPENSE.ALL + ? typeof TransactionListItem + : typeof ReportListItem; /** Model of search list item data type */ -type ListItemDataType = T extends typeof CONST.SEARCH.STATUS.EXPENSE.ALL ? TransactionListItemType[] : ReportListItemType[]; +type ListItemDataType = C extends typeof CONST.SEARCH.DATA_TYPES.CHAT + ? ReportActionListItemType[] + : T extends typeof CONST.SEARCH.STATUS.EXPENSE.ALL + ? TransactionListItemType[] + : ReportListItemType[]; /** Model of columns to show for search results */ type ColumnsToShow = { @@ -98,6 +108,30 @@ type SearchReport = { action?: SearchTransactionAction; }; +/** Model of report action search result */ +type SearchReportAction = { + /** The report action sender ID */ + accountID: number; + + /** The report action created date */ + created: string; + + /** report action message */ + message: { + /** The text content of the fragment. */ + text: string; + + /** The html content of the fragment. */ + html: string; + }; + + /** The ID of the report action */ + reportActionID: string; + + /** The ID of the report */ + reportID: string; +}; + /** Model of transaction search result */ type SearchTransaction = { /** The ID of the transaction */ @@ -212,13 +246,23 @@ type SearchTransaction = { /** Types of searchable transactions */ type SearchTransactionType = ValueOf; +/** + * + */ +type PrefixedRecord = { + [Key in `${Prefix}${string}`]: ValueType; +}; + /** Model of search results */ type SearchResults = { /** Current search results state */ search: SearchResultsInfo; /** Search results data */ - data: Record> & Record; + data: PrefixedRecord & + Record> & + PrefixedRecord> & + PrefixedRecord; /** Whether search data is being fetched from server */ isLoading?: boolean; @@ -226,4 +270,4 @@ type SearchResults = { export default SearchResults; -export type {ListItemType, ListItemDataType, SearchTransaction, SearchTransactionType, SearchTransactionAction, SearchPersonalDetails, SearchDataTypes, SearchReport}; +export type {ListItemType, ListItemDataType, SearchTransaction, SearchTransactionType, SearchTransactionAction, SearchPersonalDetails, SearchDataTypes, SearchReport, SearchReportAction}; From 6c5c25f8ce02c9ffce72664fad772a1c9f207913 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 20 Aug 2024 14:47:05 +0530 Subject: [PATCH 045/502] Added docstring --- src/types/onyx/SearchResults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 332d74b8a9bb..744d3289beca 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -247,7 +247,7 @@ type SearchTransaction = { type SearchTransactionType = ValueOf; /** - * + * A utility type that creates a record where all keys are strings that start with a specified prefix. */ type PrefixedRecord = { [Key in `${Prefix}${string}`]: ValueType; From 21549c60a01334e51a8fb84c9ffe57188d247d33 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 20 Aug 2024 11:19:12 +0200 Subject: [PATCH 046/502] wire up enable custom report names --- src/CONST.ts | 1 + .../EnablePolicyDefaultReportTitle.ts | 6 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Workspace/Rules.ts | 41 +++++++++++++++++++ .../rules/ExpenseReportRulesSection.tsx | 4 +- src/types/onyx/Policy.ts | 5 +++ 7 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/libs/API/parameters/EnablePolicyDefaultReportTitle.ts diff --git a/src/CONST.ts b/src/CONST.ts index 7c86676e7c1a..f864575f7002 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2028,6 +2028,7 @@ const CONST = { USER: 'user', }, AUTO_REIMBURSEMENT_MAX_LIMIT: 2000000, + DEFAULT_REPORT_NAME_PATTERN: '{report:type} {report:startdate}', AUTO_REPORTING_FREQUENCIES: { INSTANT: 'instant', IMMEDIATE: 'immediate', diff --git a/src/libs/API/parameters/EnablePolicyDefaultReportTitle.ts b/src/libs/API/parameters/EnablePolicyDefaultReportTitle.ts new file mode 100644 index 000000000000..2852640ac70a --- /dev/null +++ b/src/libs/API/parameters/EnablePolicyDefaultReportTitle.ts @@ -0,0 +1,6 @@ +type EnablePolicyDefaultReportTitleParams = { + policyID: string; + enable: boolean; +}; + +export default EnablePolicyDefaultReportTitleParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 18cfb8bb8033..e9f1acb4799e 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -277,3 +277,4 @@ export type {default as SetPolicyAutomaticApprovalAuditRateParams} from './SetPo export type {default as SetPolicyAutoReimbursementLimitParams} from './SetPolicyAutoReimbursementLimit'; export type {default as EnablePolicyAutoReimbursementLimitParams} from './EnablePolicyAutoReimbursementLimit'; export type {default as EnablePolicyAutoApprovalOptionsParams} from './EnablePolicyAutoApprovalOptions'; +export type {default as EnablePolicyDefaultReportTitleParams} from './EnablePolicyDefaultReportTitle'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 305258c0de72..c1c0672ba4ed 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -22,6 +22,7 @@ const WRITE_COMMANDS = { SET_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'SetPolicyAutoReimbursementLimit', ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'EnablePolicyAutoReimbursementLimit', ENABLE_POLICY_AUTO_APPROVAL_OPTIONS: 'EnablePolicyAutoApprovalOptions', + ENABLE_POLICY_DEFAULT_REPORT_TITLE: 'EnablePolicyDefaultReportTitle', DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', OPEN_APP: 'OpenApp', @@ -523,6 +524,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.SetPolicyAutoReimbursementLimitParams; [WRITE_COMMANDS.ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.EnablePolicyAutoReimbursementLimitParams; [WRITE_COMMANDS.ENABLE_POLICY_AUTO_APPROVAL_OPTIONS]: Parameters.EnablePolicyAutoApprovalOptionsParams; + [WRITE_COMMANDS.ENABLE_POLICY_DEFAULT_REPORT_TITLE]: Parameters.EnablePolicyDefaultReportTitleParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; [WRITE_COMMANDS.TRACK_EXPENSE]: Parameters.TrackExpenseParams; [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 522207f81606..7207f2db4774 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -4,6 +4,7 @@ import * as API from '@libs/API'; import type { EnablePolicyAutoApprovalOptionsParams, EnablePolicyAutoReimbursementLimitParams, + EnablePolicyDefaultReportTitleParams, SetPolicyAutomaticApprovalLimitParams, SetPolicyAutoReimbursementLimitParams, SetPolicyDefaultReportTitleParams, @@ -16,6 +17,45 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import {getPolicy} from '@libs/PolicyUtils'; import ONYXKEYS from '@src/ONYXKEYS'; +/** + * Call the API to enable custom report title for the reports in the given policy + * @param enabled - whether custom report title for the reports is enabled in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { + const policy = getPolicy(policyID); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowCustomReportTitleOption: enabled, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowCustomReportTitleOption: !!policy?.shouldShowCustomReportTitleOption, + }, + }, + ]; + + const parameters: EnablePolicyDefaultReportTitleParams = { + enable: enabled, + policyID, + }; + + API.write(WRITE_COMMANDS.ENABLE_POLICY_DEFAULT_REPORT_TITLE, parameters, { + optimisticData, + failureData, + }); +} + /** * Call the API to deactivate the card and request a new one * @param customName - name pattern to be used for the reports @@ -404,6 +444,7 @@ function enablePolicyAutoReimbursementLimit(enabled: boolean, policyID: string) } export { + enablePolicyDefaultReportTitle, modifyPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 5475963ba026..2b65cada24af 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -29,8 +29,8 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { title: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), subtitle: translate('workspace.rules.expenseReportRules.customReportNamesSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), - isActive: false, - onToggle: (isEnabled: boolean) => {}, + isActive: policy?.shouldShowCustomReportTitleOption, + onToggle: (isEnabled: boolean) => WorkspaceRulesActions.enablePolicyDefaultReportTitle(isEnabled, policyID), subMenuItems: [ Date: Tue, 20 Aug 2024 12:06:39 +0200 Subject: [PATCH 047/502] wire up custom report name --- src/CONST.ts | 3 +- src/libs/actions/Workspace/Rules.ts | 40 +++++++++---------- .../rules/ExpenseReportRulesSection.tsx | 6 +-- .../workspace/rules/RulesCustomNamePage.tsx | 11 ++++- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index f864575f7002..e8e3939e225c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2022,13 +2022,14 @@ const CONST = { // Often referred to as "collect" workspaces TEAM: 'team', }, + FIELD_LIST_TITLE_FIELD_ID: 'text_title', + DEFAULT_REPORT_NAME_PATTERN: '{report:type} {report:startdate}', ROLE: { ADMIN: 'admin', AUDITOR: 'auditor', USER: 'user', }, AUTO_REIMBURSEMENT_MAX_LIMIT: 2000000, - DEFAULT_REPORT_NAME_PATTERN: '{report:type} {report:startdate}', AUTO_REPORTING_FREQUENCIES: { INSTANT: 'instant', IMMEDIATE: 'immediate', diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 7207f2db4774..9aa6db4d8870 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -15,6 +15,7 @@ import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/ import {WRITE_COMMANDS} from '@libs/API/types'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import {getPolicy} from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; /** @@ -61,24 +62,18 @@ function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { * @param customName - name pattern to be used for the reports * @param policyID - id of the policy to apply the naming pattern to */ -function modifyPolicyDefaultReportTitle(customName: string, policyID: string) { - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, - value: { - isLoading: true, - errors: null, - }, - }, - ]; +function setPolicyDefaultReportTitle(customName: string, policyID: string) { + const policy = getPolicy(policyID); + const previousFieldList = policy?.fieldList ?? {}; - const successData: OnyxUpdate[] = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: {...previousFieldList, defaultValue: customName}, + }, }, }, ]; @@ -86,9 +81,11 @@ function modifyPolicyDefaultReportTitle(customName: string, policyID: string) { const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, + }, }, }, ]; @@ -98,11 +95,10 @@ function modifyPolicyDefaultReportTitle(customName: string, policyID: string) { policyID, }; - // API.write(WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE, parameters, { - // optimisticData, - // successData, - // failureData, - // }); + API.write(WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE, parameters, { + optimisticData, + failureData, + }); } /** @@ -445,7 +441,7 @@ function enablePolicyAutoReimbursementLimit(enabled: boolean, policyID: string) export { enablePolicyDefaultReportTitle, - modifyPolicyDefaultReportTitle, + setPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, setPolicyAutomaticApprovalLimit, diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 2b65cada24af..2c3d8d5a2f77 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -32,9 +32,9 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { isActive: policy?.shouldShowCustomReportTitleOption, onToggle: (isEnabled: boolean) => WorkspaceRulesActions.enablePolicyDefaultReportTitle(isEnabled, policyID), subMenuItems: [ - Navigation.navigate(ROUTES.RULES_CUSTOM_NAME.getRoute(policyID))} diff --git a/src/pages/workspace/rules/RulesCustomNamePage.tsx b/src/pages/workspace/rules/RulesCustomNamePage.tsx index 847c4e439c60..48504e0e3f93 100644 --- a/src/pages/workspace/rules/RulesCustomNamePage.tsx +++ b/src/pages/workspace/rules/RulesCustomNamePage.tsx @@ -1,6 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import BulletList from '@components/BulletList'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -24,6 +25,7 @@ type RulesCustomNamePageProps = StackScreenProps WorkspaceRulesActions.modifyPolicyDefaultReportTitle(customName, policyID)} + onSubmit={({customName}) => { + WorkspaceRulesActions.setPolicyDefaultReportTitle(customName, policyID); + Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); + }} submitButtonText={translate('common.save')} enabledWhenOffline > - Date: Tue, 20 Aug 2024 12:31:44 +0200 Subject: [PATCH 048/502] wire up custom report names --- src/libs/actions/Workspace/Rules.ts | 36 ++++++++----------- .../rules/ExpenseReportRulesSection.tsx | 13 +++---- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 9aa6db4d8870..177d7a8d9b92 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -107,24 +107,17 @@ function setPolicyDefaultReportTitle(customName: string, policyID: string) { * @param policyID - id of the policy to apply the naming pattern to */ function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) { - console.log('setPolicyPreventMemberCreatedTitle ', enforced); - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, - value: { - isLoading: true, - errors: null, - }, - }, - ]; + const policy = getPolicy(policyID); + const previousFieldList = policy?.fieldList ?? {}; - const successData: OnyxUpdate[] = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: {...previousFieldList, deletable: !enforced}, + }, }, }, ]; @@ -132,9 +125,11 @@ function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, + }, }, }, ]; @@ -144,11 +139,10 @@ function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) policyID, }; - // API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_MEMBER_CREATED_TITLE, parameters, { - // optimisticData, - // successData, - // failureData, - // }); + API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_MEMBER_CREATED_TITLE, parameters, { + optimisticData, + failureData, + }); } /** diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 2c3d8d5a2f77..4c8e14496e09 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -1,6 +1,5 @@ import React from 'react'; import {useOnyx} from 'react-native-onyx'; -import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; @@ -42,14 +41,10 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { WorkspaceRulesActions.setPolicyPreventMemberCreatedTitle(!isOn, policyID)} - // disabled={!!policy?.fieldList?.deletable} - // isOn={false} + wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt6]} + titleStyle={styles.pv2} + isActive={!policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID].deletable} + onToggle={(isEnabled) => WorkspaceRulesActions.setPolicyPreventMemberCreatedTitle(isEnabled, policyID)} />, ], }, From 34a5c4ba62509c7c824b5457bf8be16579ef0451 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 20 Aug 2024 12:44:55 +0200 Subject: [PATCH 049/502] wire up prevent self approval --- src/libs/actions/Workspace/Rules.ts | 22 +++++-------------- .../rules/ExpenseReportRulesSection.tsx | 4 ++++ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 177d7a8d9b92..633757025c64 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -151,23 +151,14 @@ function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) * @param policyID - id of the policy to apply the naming pattern to */ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: string) { - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, - value: { - isLoading: true, - errors: null, - }, - }, - ]; + const policy = getPolicy(policyID); - const successData: OnyxUpdate[] = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + preventSelfApproval, }, }, ]; @@ -175,9 +166,9 @@ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: st const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - isLoading: false, + preventSelfApproval: policy?.preventSelfApproval, }, }, ]; @@ -189,7 +180,6 @@ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: st API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL, parameters, { optimisticData, - successData, failureData, }); } diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 4c8e14496e09..da28faa8cf16 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -21,6 +21,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const styles = useThemeStyles(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const preventSelfApprovalsUnavailable = policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.BASIC || policy?.errorFields?.approvalMode; const autoPayApprovedReportsUnavailable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO; const optionItems = [ @@ -52,6 +53,9 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { title: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), subtitle: translate('workspace.rules.expenseReportRules.preventSelfApprovalsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), + isActive: policy?.preventSelfApproval, + disabled: preventSelfApprovalsUnavailable, + showLockIcon: preventSelfApprovalsUnavailable, onToggle: (isEnabled: boolean) => WorkspaceRulesActions.setPolicyPreventSelfApproval(isEnabled, policyID), }, { From 197d74eee004e80297aa6095ea41262d215cd3be Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:25:19 +0100 Subject: [PATCH 050/502] Make the members view of rooms and expense chats consistent with groups --- src/languages/en.ts | 2 -- src/languages/es.ts | 8 +++----- src/pages/ReportParticipantsPage.tsx | 9 --------- src/pages/RoomMembersPage.tsx | 6 ------ 4 files changed, 3 insertions(+), 22 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index c12650f6f3f2..458b57d02e4c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1470,7 +1470,6 @@ export default { groupName: 'Group name', }, groupChat: { - groupMembersListTitle: 'Directory of all group members.', lastMemberTitle: 'Heads up!', lastMemberWarning: "Since you're the last person here, leaving will make this chat inaccessible to all users. Are you sure you want to leave?", defaultReportName: ({displayName}: {displayName: string}) => `${displayName}'s group chat`, @@ -3623,7 +3622,6 @@ export default { }, }, roomMembersPage: { - roomMembersListTitle: 'Directory of all room members.', memberNotFound: 'Member not found. To invite a new member to the room, please use the invite button above.', notAuthorized: `You don't have access to this page. If you're trying to join this room, just ask a room member to add you. Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`, removeMembersPrompt: 'Are you sure you want to remove the selected members from the room?', diff --git a/src/languages/es.ts b/src/languages/es.ts index 77099f917d60..24ba827835a3 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1482,7 +1482,6 @@ export default { groupName: 'Nombre del grupo', }, groupChat: { - groupMembersListTitle: 'Directorio de los miembros del grupo.', lastMemberTitle: '¡Atención!', lastMemberWarning: 'Ya que eres la última persona aquí, si te vas, este chat quedará inaccesible para todos los miembros. ¿Estás seguro de que quieres salir del chat?', defaultReportName: ({displayName}: {displayName: string}) => `Chat de grupo de ${displayName}`, @@ -3086,9 +3085,9 @@ export default { removeMembersWarningPrompt: ({memberName, ownerName}: RemoveMembersWarningPrompt) => `${memberName} es un aprobador en este espacio de trabajo. Cuando lo elimine de este espacio de trabajo, los sustituiremos en el flujo de trabajo de aprobación por el propietario del espacio de trabajo, ${ownerName}`, removeMembersTitle: 'Eliminar miembros', - removeMemberButtonTitle: 'Quitar del espacio de trabajo', - removeMemberGroupButtonTitle: 'Quitar del grupo', - removeMemberRoomButtonTitle: 'Quitar de la sala', + removeMemberButtonTitle: 'Eliminar del espacio de trabajo', + removeMemberGroupButtonTitle: 'Eliminar del grupo', + removeMemberRoomButtonTitle: 'Eliminar de la sala', removeMemberPrompt: ({memberName}: {memberName: string}) => `¿Estás seguro de que deseas eliminar a ${memberName}?`, removeMemberTitle: 'Eliminar miembro', transferOwner: 'Transferir la propiedad', @@ -3677,7 +3676,6 @@ export default { }, }, roomMembersPage: { - roomMembersListTitle: 'Directorio de los miembros de la sala.', memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro a la sala de chat, por favor, utiliza el botón invitar que está más arriba.', notAuthorized: `No tienes acceso a esta página. Si estás intentando unirte a esta sala, pide a un miembro de la sala que te añada. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, removeMembersPrompt: '¿Estás seguro de que quieres eliminar a los miembros seleccionados de la sala de chat?', diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index cae4faa14c79..63a87e0a9bcb 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -210,14 +210,6 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { [selectedMembers, addUser, removeUser, currentUserAccountID], ); - const headerContent = useMemo(() => { - if (!isGroupChat) { - return; - } - - return {translate('groupChat.groupMembersListTitle')}; - }, [styles, translate, isGroupChat]); - const customListHeader = useMemo(() => { if (!isGroupChat) { return; @@ -396,7 +388,6 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { setSearchValue(value); }} headerMessage={headerMessage} - headerContent={!shouldShowTextInput ? headerContent : undefined} ListItem={TableListItem} onSelectRow={openMemberDetails} shouldSingleExecuteRowSelect={!(isGroupChat && isCurrentUserAdmin)} diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 470f2fa416be..6fcc807c768d 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -16,7 +16,6 @@ import ScreenWrapper from '@components/ScreenWrapper'; import TableListItem from '@components/SelectionList/TableListItem'; import type {ListItem} from '@components/SelectionList/types'; import SelectionListWithModal from '@components/SelectionListWithModal'; -import Text from '@components/Text'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; @@ -300,10 +299,6 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { ); const selectionModeHeader = selectionMode?.isEnabled && isSmallScreenWidth; - const headerContent = useMemo(() => { - return {translate('roomMembersPage.roomMembersListTitle')}; - }, [styles, translate]); - return ( Date: Tue, 20 Aug 2024 13:57:57 +0200 Subject: [PATCH 051/502] add fallback subtitle, fix translations --- src/languages/en.ts | 4 +- src/languages/es.ts | 23 ++++++++-- .../rules/ExpenseReportRulesSection.tsx | 42 +++++++++++++++---- .../workflows/ToggleSettingsOptionRow.tsx | 23 ++++++---- 4 files changed, 70 insertions(+), 22 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 2839d0f0fc07..c4a193de8e37 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -173,6 +173,7 @@ export default { profile: 'Profile', referral: 'Referral', payments: 'Payments', + approvals: 'Approvals', wallet: 'Wallet', preferences: 'Preferences', view: 'View', @@ -3543,7 +3544,6 @@ export default { customNameWorkspaceNameExample: 'Workspace name: {report:policyname}', customNameReportIDExample: 'Report ID: {report:id}', customNameTotalExample: 'Total: {report:total}.', - preventMembersFromChangingCustomNamesTitle: 'Prevent members from changing custom report names', preventSelfApprovalsTitle: 'Prevent self-approvals', preventSelfApprovalsSubtitle: 'Prevent workspace members from approving their own expense reports.', @@ -3558,6 +3558,8 @@ export default { autoPayApprovedReportsLockedSubtitle: 'Go to more features and enable workflows, then add payments to unlock this feature.', autoPayReportsUnderTitle: 'Auto-pay reports under', autoPayReportsUnderDescription: 'Fully compliant expense reports under this amount will be automatically paid. ', + unlockFeatureGoToSubtitle: 'Go to', + unlockFeatureEnableWorkflowsSubtitle: (featureName: string) => `and enable workflows, then add ${featureName} to unlock this feature.`, }, }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index d7d4a8c6cf3a..9c7c24905062 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3569,17 +3569,34 @@ export default { }, expenseReportRules: { title: 'Informes de gastos', - subtitle: 'Automatiza la conformidad, aprobaciones y pagos de informes de gastos.', + subtitle: 'Automatiza el cumplimiento, las aprobaciones y los pagos de los informes de gastos.', customReportNamesTitle: 'Nombres personalizados de informes', - customReportNamesSubtitle: 'Nombres personalizados de informes', + customReportNamesSubtitle: 'Crea nombres personalizados usando nuestras fórmulas extensas.', customNameTitle: 'Nombre personalizado', - preventMembersFromChangingCustomNamesTitle: 'Impedir que los miembros cambien los nombres de los informes personalizados', + customNameDescription: 'Elige un nombre personalizado para los informes de gastos usando nuestras ', + customNameDescriptionLink: 'fórmulas extensas', + customNameInputLabel: 'Nombre', + customNameEmailPhoneExample: 'Correo electrónico o teléfono del miembro: {report:submit:from}', + customNameStartDateExample: 'Fecha de inicio del informe: {report:startdate}', + customNameWorkspaceNameExample: 'Nombre del espacio de trabajo: {report:policyname}', + customNameReportIDExample: 'ID del informe: {report:id}', + customNameTotalExample: 'Total: {report:total}.', + preventMembersFromChangingCustomNamesTitle: 'Evitar que los miembros cambien los nombres personalizados de los informes', preventSelfApprovalsTitle: 'Evitar autoaprobaciones', preventSelfApprovalsSubtitle: 'Evita que los miembros del espacio de trabajo aprueben sus propios informes de gastos.', autoApproveCompliantReportsTitle: 'Aprobación automática de informes conformes', autoApproveCompliantReportsSubtitle: 'Configura qué informes de gastos son elegibles para la aprobación automática.', + autoApproveReportsUnderTitle: 'Aprobar automáticamente informes por debajo de', + autoApproveReportsUnderDescription: 'Los informes de gastos totalmente conformes por debajo de esta cantidad se aprobarán automáticamente.', + randomReportAuditTitle: 'Auditoría aleatoria de informes', + randomReportAuditDescription: 'Requiere que algunos informes sean aprobados manualmente, incluso si son elegibles para la aprobación automática.', autoPayApprovedReportsTitle: 'Pago automático de informes aprobados', autoPayApprovedReportsSubtitle: 'Configura qué informes de gastos son elegibles para el pago automático.', + autoPayApprovedReportsLockedSubtitle: 'Ve a más funciones y habilita flujos de trabajo, luego agrega pagos para desbloquear esta función.', + autoPayReportsUnderTitle: 'Pagar automáticamente informes por debajo de', + autoPayReportsUnderDescription: 'Los informes de gastos totalmente conformes por debajo de esta cantidad se pagarán automáticamente.', + unlockFeatureGoToSubtitle: 'Ir a', + unlockFeatureEnableWorkflowsSubtitle: (featureName: string) => `y habilita flujos de trabajo, luego agrega ${featureName} para desbloquear esta función.`, }, }, }, diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index da28faa8cf16..da12d916ce85 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -2,6 +2,8 @@ import React from 'react'; import {useOnyx} from 'react-native-onyx'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import Section from '@components/Section'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; @@ -21,9 +23,25 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const styles = useThemeStyles(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); - const preventSelfApprovalsUnavailable = policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.BASIC || policy?.errorFields?.approvalMode; + // Auto-approvals and self-approvals are unavailable due to the policy workflows settings + const workflowApprovalsUnavailable = policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.BASIC || !!policy?.errorFields?.approvalMode; const autoPayApprovedReportsUnavailable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO; + const renderFallbackSubtitle = (featureName: string) => { + return ( + + {translate('workspace.rules.expenseReportRules.unlockFeatureGoToSubtitle')}{' '} + Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID))} + > + {translate('workspace.common.moreFeatures').toLowerCase()} + {' '} + {translate('workspace.rules.expenseReportRules.unlockFeatureEnableWorkflowsSubtitle', featureName)} + + ); + }; + const optionItems = [ { title: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), @@ -51,18 +69,24 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { }, { title: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), - subtitle: translate('workspace.rules.expenseReportRules.preventSelfApprovalsSubtitle'), + subtitle: workflowApprovalsUnavailable + ? renderFallbackSubtitle(translate('common.approvals').toLowerCase()) + : translate('workspace.rules.expenseReportRules.preventSelfApprovalsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), - isActive: policy?.preventSelfApproval, - disabled: preventSelfApprovalsUnavailable, - showLockIcon: preventSelfApprovalsUnavailable, + isActive: policy?.preventSelfApproval && !workflowApprovalsUnavailable, + disabled: workflowApprovalsUnavailable, + showLockIcon: workflowApprovalsUnavailable, onToggle: (isEnabled: boolean) => WorkspaceRulesActions.setPolicyPreventSelfApproval(isEnabled, policyID), }, { title: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), - subtitle: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsSubtitle'), + subtitle: workflowApprovalsUnavailable + ? renderFallbackSubtitle(translate('common.approvals').toLowerCase()) + : translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), - isActive: policy?.shouldShowAutoApprovalOptions, + isActive: policy?.shouldShowAutoApprovalOptions && !workflowApprovalsUnavailable, + disabled: workflowApprovalsUnavailable, + showLockIcon: workflowApprovalsUnavailable, onToggle: (isEnabled: boolean) => { WorkspaceRulesActions.enableAutoApprovalOptions(isEnabled, policyID); }, @@ -86,7 +110,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { { title: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), subtitle: autoPayApprovedReportsUnavailable - ? translate('workspace.rules.expenseReportRules.autoPayApprovedReportsLockedSubtitle') + ? renderFallbackSubtitle(translate('common.payments').toLowerCase()) : translate('workspace.rules.expenseReportRules.autoPayApprovedReportsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.autoPayApprovedReportsTitle'), onToggle: (isEnabled: boolean) => { @@ -94,7 +118,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { }, disabled: autoPayApprovedReportsUnavailable, showLockIcon: autoPayApprovedReportsUnavailable, - isActive: policy?.shouldShowAutoReimbursementLimitOption, + isActive: policy?.shouldShowAutoReimbursementLimitOption && !autoPayApprovedReportsUnavailable, subMenuItems: [ { - if (!subtitle || !shouldParseSubtitle) { + if (!subtitle || !shouldParseSubtitle || typeof subtitle !== 'string') { return ''; } return Parser.replace(subtitle, {shouldEscapeText}); @@ -116,14 +117,18 @@ function ToggleSettingOptionRow({ }, [shouldParseSubtitle, subtitleHtml]); const subTitleView = useMemo(() => { - if (!!subtitle && shouldParseSubtitle) { - return ( - - - - ); + if (typeof subtitle === 'string') { + if (!!subtitle && shouldParseSubtitle) { + return ( + + + + ); + } + return {subtitle}; } - return {subtitle}; + + return subtitle; }, [ subtitle, shouldParseSubtitle, From b881703e3b336669e6f5b04fa4ee8d6381b46fc4 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 20 Aug 2024 14:22:49 +0200 Subject: [PATCH 052/502] add custom name validation --- src/pages/workspace/rules/RulesCustomNamePage.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/rules/RulesCustomNamePage.tsx b/src/pages/workspace/rules/RulesCustomNamePage.tsx index 48504e0e3f93..380657ea6194 100644 --- a/src/pages/workspace/rules/RulesCustomNamePage.tsx +++ b/src/pages/workspace/rules/RulesCustomNamePage.tsx @@ -5,6 +5,7 @@ import {useOnyx} from 'react-native-onyx'; import BulletList from '@components/BulletList'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -40,6 +41,14 @@ function RulesCustomNamePage({route}: RulesCustomNamePageProps) { const customNameDefaultValue = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID].defaultValue; + const validateCustomName = ({customName}: FormOnyxValues) => { + const errors: FormInputErrors = {}; + if (!customName) { + errors[INPUT_IDS.CUSTOM_NAME] = translate('common.error.fieldRequired'); + } + return errors; + }; + return ( { WorkspaceRulesActions.setPolicyDefaultReportTitle(customName, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); From b2cef7c03dbacbe41e9b33c6ab322f22e6ef76ed Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:14:13 +0100 Subject: [PATCH 053/502] Correct members list top margin --- src/pages/ReportParticipantsPage.tsx | 4 ++-- src/pages/RoomMembersPage.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index 63a87e0a9bcb..7873abb0258d 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -373,7 +373,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { }); }} /> - + diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 6fcc807c768d..71f425080ed4 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -339,7 +339,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { confirmText={translate('common.remove')} cancelText={translate('common.cancel')} /> - + From 215a25251f0acc53934f702e1e7700ff2776bb87 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 20 Aug 2024 21:59:37 +0200 Subject: [PATCH 054/502] add validation, fix amount form validation --- src/CONST.ts | 2 +- src/components/AmountForm.tsx | 5 ++--- .../RulesAutoApproveReportsUnderPage.tsx | 1 - .../rules/RulesAutoPayReportsUnderPage.tsx | 21 +++++++++---------- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index e8e3939e225c..132bf0360674 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2029,7 +2029,7 @@ const CONST = { AUDITOR: 'auditor', USER: 'user', }, - AUTO_REIMBURSEMENT_MAX_LIMIT: 2000000, + AUTO_REIMBURSEMENT_MAX_LIMIT_CENTS: 2000000, AUTO_REPORTING_FREQUENCIES: { INSTANT: 'instant', IMMEDIATE: 'immediate', diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index bd83b1e67e4a..c5f30243b5a7 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -67,7 +67,6 @@ function AmountForm( currency = CONST.CURRENCY.USD, extraDecimals = 0, amountMaxLength, - errorText, onInputChange, onCurrencyButtonPress, displayAsTextInput = false, @@ -280,11 +279,11 @@ function AmountForm( // eslint-disable-next-line react/jsx-props-no-spreading {...rest} /> - {!!errorText && ( + {!!rest.errorText && ( )} diff --git a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx index c632bd138d2d..ef6ae70a9d39 100644 --- a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx @@ -51,7 +51,6 @@ function RulesAutoApproveReportsUnderPage({route}: RulesAutoApproveReportsUnderP { WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(maxExpenseAutoApprovalAmount, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); diff --git a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx index d039bd16cad6..c4faa55c8e80 100644 --- a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx @@ -5,6 +5,7 @@ import {useOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -33,15 +34,13 @@ function RulesAutoPayReportsUnderPage({route}: RulesAutoPayReportsUnderPageProps const defaultValue = CurrencyUtils.convertToFrontendAmountAsString(policy?.autoReimbursement?.limit, policy?.outputCurrency); - // const validateLimit = ({maxExpenseAutoPayAmount}: FormOnyxValues<'rulesAutoPayReportsUnderModalForm'>) => { - // const errors: FormInputErrors = {}; - // if (CurrencyUtils.convertToBackendAmount(parseFloat(maxExpenseAutoPayAmount)) > CONST.POLICY.AUTO_REIMBURSEMENT_MAX_LIMIT) { - // console.log('ERROR'); - // errors[INPUT_IDS.MAX_EXPENSE_AUTO_PAY_AMOUNT] = translate('common.error.fieldRequired'); - // } - // console.log('ERRORS ', errors); - // return errors; - // }; + const validateLimit = ({maxExpenseAutoPayAmount}: FormOnyxValues) => { + const errors: FormInputErrors = {}; + if (CurrencyUtils.convertToBackendAmount(parseFloat(maxExpenseAutoPayAmount)) > CONST.POLICY.AUTO_REIMBURSEMENT_MAX_LIMIT_CENTS) { + errors[INPUT_IDS.MAX_EXPENSE_AUTO_PAY_AMOUNT] = translate('common.error.invalidAmount'); + } + return errors; + }; return ( { WorkspaceRulesActions.setPolicyAutoReimbursementLimit(maxExpenseAutoPayAmount, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); @@ -78,7 +77,7 @@ function RulesAutoPayReportsUnderPage({route}: RulesAutoPayReportsUnderPageProps defaultValue={defaultValue} isCurrencyPressable={false} ref={inputCallbackRef} - amountMaxLength={7} + // amountMaxLength={7} displayAsTextInput /> {translate('workspace.rules.expenseReportRules.autoPayReportsUnderDescription')} From 49dfe7f2123dc4d8a054ad121afb2605b04ce1c3 Mon Sep 17 00:00:00 2001 From: shahid <105579118+Shahidullah-Muffakir@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:40:32 +0000 Subject: [PATCH 055/502] Fix issue with splitting distance in quick actions by ensuring distance and amount are calculated before splitting. Signed-off-by: GitHub --- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index b221a5d5be9d..9d00b9330525 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -262,7 +262,7 @@ function FloatingActionButtonAndPopover( selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), true); return; case CONST.QUICK_ACTIONS.SPLIT_DISTANCE: - selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, false), true); return; case CONST.QUICK_ACTIONS.SEND_MONEY: selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), false); From b1df1690bbeab78531551df0f25373ac15e8c585 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 20 Aug 2024 23:47:03 +0200 Subject: [PATCH 056/502] add fallbacks --- src/libs/actions/Workspace/Rules.ts | 9 ++++++--- .../workspace/rules/RulesAutoPayReportsUnderPage.tsx | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 633757025c64..578c2d60bb28 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -190,7 +190,8 @@ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: st * @param policyID - id of the policy to apply the limit to */ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { - const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(limit)); + const fallbackLimit = limit === '' ? '0' : limit; + const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(fallbackLimit)); const policy = getPolicy(policyID); const optimisticData: OnyxUpdate[] = [ @@ -234,7 +235,8 @@ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { * @param policyID - id of the policy to apply the limit to */ function setPolicyAutomaticApprovalAuditRate(auditRate: string, policyID: string) { - const parsedAuditRate = parseInt(auditRate, 10); + const fallbackAuditRate = auditRate === '' ? '0' : auditRate; + const parsedAuditRate = parseInt(fallbackAuditRate, 10); const policy = getPolicy(policyID); const optimisticData: OnyxUpdate[] = [ @@ -316,7 +318,8 @@ function enableAutoApprovalOptions(enabled: boolean, policyID: string) { */ function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { const policy = getPolicy(policyID); - const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(limit)); + const fallbackLimit = limit === '' ? '0' : limit; + const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(fallbackLimit)); const optimisticData: OnyxUpdate[] = [ { diff --git a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx index c4faa55c8e80..2092f8d6003e 100644 --- a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx @@ -77,7 +77,6 @@ function RulesAutoPayReportsUnderPage({route}: RulesAutoPayReportsUnderPageProps defaultValue={defaultValue} isCurrencyPressable={false} ref={inputCallbackRef} - // amountMaxLength={7} displayAsTextInput /> {translate('workspace.rules.expenseReportRules.autoPayReportsUnderDescription')} From b5c7486a4a47d9f1e2dcfeb99ca6a1949bcd9b23 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 20 Aug 2024 23:59:50 +0200 Subject: [PATCH 057/502] add side effects --- src/CONST.ts | 2 ++ src/libs/actions/Workspace/Rules.ts | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 132bf0360674..d874752e4a6f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2030,6 +2030,8 @@ const CONST = { USER: 'user', }, AUTO_REIMBURSEMENT_MAX_LIMIT_CENTS: 2000000, + AUTO_REIMBURSEMENT_DEFAULT_LIMIT_CENTS: 10000, + AUTO_REPORTING_FREQUENCIES: { INSTANT: 'instant', IMMEDIATE: 'immediate', diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 578c2d60bb28..51778ea926f1 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -25,6 +25,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; */ function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { const policy = getPolicy(policyID); + const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; + + const titleFieldValues = enabled ? previousFieldList : {...previousFieldList, defaultValue: CONST.POLICY.DEFAULT_REPORT_NAME_PATTERN}; const optimisticData: OnyxUpdate[] = [ { @@ -32,6 +35,9 @@ function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { shouldShowCustomReportTitleOption: enabled, + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: titleFieldValues, + }, }, }, ]; @@ -42,6 +48,9 @@ function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { shouldShowCustomReportTitleOption: !!policy?.shouldShowCustomReportTitleOption, + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, + }, }, }, ]; @@ -64,7 +73,7 @@ function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { */ function setPolicyDefaultReportTitle(customName: string, policyID: string) { const policy = getPolicy(policyID); - const previousFieldList = policy?.fieldList ?? {}; + const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; const optimisticData: OnyxUpdate[] = [ { @@ -108,7 +117,7 @@ function setPolicyDefaultReportTitle(customName: string, policyID: string) { */ function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) { const policy = getPolicy(policyID); - const previousFieldList = policy?.fieldList ?? {}; + const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; const optimisticData: OnyxUpdate[] = [ { @@ -396,7 +405,7 @@ function enablePolicyAutoReimbursementLimit(enabled: boolean, policyID: string) value: { shouldShowAutoReimbursementLimitOption: enabled, autoReimbursement: { - limit: enabled ? policy?.autoReimbursement?.limit : 10000, + limit: enabled ? policy?.autoReimbursement?.limit : CONST.POLICY.AUTO_REIMBURSEMENT_DEFAULT_LIMIT_CENTS, }, }, }, @@ -427,13 +436,13 @@ function enablePolicyAutoReimbursementLimit(enabled: boolean, policyID: string) } export { - enablePolicyDefaultReportTitle, setPolicyDefaultReportTitle, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, setPolicyAutomaticApprovalLimit, setPolicyAutomaticApprovalAuditRate, setPolicyAutoReimbursementLimit, + enablePolicyDefaultReportTitle, enablePolicyAutoReimbursementLimit, enableAutoApprovalOptions, }; From 0edff08a18bc0f0de0dd17c5d86a411749d3e6c6 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 21 Aug 2024 00:03:11 +0200 Subject: [PATCH 058/502] cleanup jsdoc --- src/libs/actions/Workspace/Rules.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts index 51778ea926f1..e8832cff5eb7 100644 --- a/src/libs/actions/Workspace/Rules.ts +++ b/src/libs/actions/Workspace/Rules.ts @@ -67,7 +67,7 @@ function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { } /** - * Call the API to deactivate the card and request a new one + * Call the API to set default report title pattern for the given policy * @param customName - name pattern to be used for the reports * @param policyID - id of the policy to apply the naming pattern to */ @@ -111,7 +111,7 @@ function setPolicyDefaultReportTitle(customName: string, policyID: string) { } /** - * Call the API to deactivate the card and request a new one + * Call the API to enable or disable enforcing the naming pattern for the reports * @param enforced - flag whether to enforce policy name * @param policyID - id of the policy to apply the naming pattern to */ @@ -155,7 +155,7 @@ function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) } /** - * Call the API to deactivate the card and request a new one + * Call the API to enable or disable self approvals for the reports * @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports * @param policyID - id of the policy to apply the naming pattern to */ @@ -194,7 +194,7 @@ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: st } /** - * Call the API to deactivate the card and request a new one + * Call the API to apply automatic approval limit for the given policy * @param limit - max amount for auto-approval of the reports in the given policy * @param policyID - id of the policy to apply the limit to */ @@ -239,7 +239,7 @@ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { } /** - * Call the API to deactivate the card and request a new one + * Call the API to set the audit rate for the given policy * @param auditRate - percentage of the reports to be qualified for a random audit * @param policyID - id of the policy to apply the limit to */ @@ -321,7 +321,7 @@ function enableAutoApprovalOptions(enabled: boolean, policyID: string) { } /** - * Call the API to deactivate the card and request a new one + * Call the API to set the limit for auto-payments in the given policy * @param limit - max amount for auto-payment for the reports in the given policy * @param policyID - id of the policy to apply the limit to */ From edc14fe10d1353c44bc7dc324273313331110be6 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 21 Aug 2024 00:16:22 +0200 Subject: [PATCH 059/502] move actions to policy --- src/libs/API/parameters/index.ts | 1 + src/libs/actions/Policy/Policy.ts | 440 ++++++++++++++++- src/libs/actions/Workspace/Rules.ts | 448 ------------------ .../rules/ExpenseReportRulesSection.tsx | 12 +- .../RulesAutoApproveReportsUnderPage.tsx | 4 +- .../rules/RulesAutoPayReportsUnderPage.tsx | 4 +- .../workspace/rules/RulesCustomNamePage.tsx | 4 +- .../rules/RulesRandomReportAuditPage.tsx | 4 +- 8 files changed, 453 insertions(+), 464 deletions(-) delete mode 100644 src/libs/actions/Workspace/Rules.ts diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index e9f1acb4799e..5757a0cba97f 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -274,6 +274,7 @@ export type {default as SetPolicyDefaultReportTitleParams} from './SetPolicyDefa export type {default as SetPolicyPreventSelfApprovalParams} from './SetPolicyPreventSelfApproval'; export type {default as SetPolicyAutomaticApprovalLimitParams} from './SetPolicyAutomaticApprovalLimit'; export type {default as SetPolicyAutomaticApprovalAuditRateParams} from './SetPolicyAutomaticApprovalAuditRate'; +export type {default as SetPolicyPreventMemberCreatedTitleParams} from './SetPolicyPreventMemberCreatedTitleParams'; export type {default as SetPolicyAutoReimbursementLimitParams} from './SetPolicyAutoReimbursementLimit'; export type {default as EnablePolicyAutoReimbursementLimitParams} from './EnablePolicyAutoReimbursementLimit'; export type {default as EnablePolicyAutoApprovalOptionsParams} from './EnablePolicyAutoApprovalOptions'; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 044777dbce7d..1e1f8f03b987 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -12,7 +12,10 @@ import type { CreateWorkspaceParams, DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, + EnablePolicyAutoApprovalOptionsParams, + EnablePolicyAutoReimbursementLimitParams, EnablePolicyConnectionsParams, + EnablePolicyDefaultReportTitleParams, EnablePolicyExpensifyCardsParams, EnablePolicyReportFieldsParams, EnablePolicyTaxesParams, @@ -29,20 +32,27 @@ import type { OpenWorkspaceParams, OpenWorkspaceReimburseViewParams, RequestExpensifyCardLimitIncreaseParams, + SetPolicyAutomaticApprovalAuditRateParams, + SetPolicyAutomaticApprovalLimitParams, + SetPolicyAutoReimbursementLimitParams, + SetPolicyDefaultReportTitleParams, + SetPolicyPreventMemberCreatedTitleParams, + SetPolicyPreventSelfApprovalParams, + SetPolicyRulesEnabledParams, SetWorkspaceApprovalModeParams, SetWorkspaceAutoReportingFrequencyParams, SetWorkspaceAutoReportingMonthlyOffsetParams, SetWorkspacePayerParams, SetWorkspaceReimbursementParams, + UpdatePolicyAddressParams, UpdateWorkspaceAvatarParams, UpdateWorkspaceCustomUnitAndRateParams, UpdateWorkspaceDescriptionParams, UpdateWorkspaceGeneralSettingsParams, UpgradeToCorporateParams, } from '@libs/API/parameters'; -import type SetPolicyRulesEnabledParams from '@libs/API/parameters/SetPolicyRulesEnabledParams'; -import type UpdatePolicyAddressParams from '@libs/API/parameters/UpdatePolicyAddressParams'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; @@ -3236,6 +3246,423 @@ function getAdminPoliciesConnectedToNetSuite(): Policy[] { return Object.values(allPolicies ?? {}).filter((policy): policy is Policy => !!policy && policy.role === CONST.POLICY.ROLE.ADMIN && !!policy?.connections?.netsuite); } +/** + * Call the API to enable custom report title for the reports in the given policy + * @param enabled - whether custom report title for the reports is enabled in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { + const policy = getPolicy(policyID); + const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; + + const titleFieldValues = enabled ? previousFieldList : {...previousFieldList, defaultValue: CONST.POLICY.DEFAULT_REPORT_NAME_PATTERN}; + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowCustomReportTitleOption: enabled, + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: titleFieldValues, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowCustomReportTitleOption: !!policy?.shouldShowCustomReportTitleOption, + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, + }, + }, + }, + ]; + + const parameters: EnablePolicyDefaultReportTitleParams = { + enable: enabled, + policyID, + }; + + API.write(WRITE_COMMANDS.ENABLE_POLICY_DEFAULT_REPORT_TITLE, parameters, { + optimisticData, + failureData, + }); +} + +/** + * Call the API to set default report title pattern for the given policy + * @param customName - name pattern to be used for the reports + * @param policyID - id of the policy to apply the naming pattern to + */ +function setPolicyDefaultReportTitle(customName: string, policyID: string) { + const policy = getPolicy(policyID); + const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: {...previousFieldList, defaultValue: customName}, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, + }, + }, + }, + ]; + + const parameters: SetPolicyDefaultReportTitleParams = { + value: customName, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE, parameters, { + optimisticData, + failureData, + }); +} + +/** + * Call the API to enable or disable enforcing the naming pattern for the reports + * @param enforced - flag whether to enforce policy name + * @param policyID - id of the policy to apply the naming pattern to + */ +function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) { + const policy = getPolicy(policyID); + const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: {...previousFieldList, deletable: !enforced}, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + fieldList: { + [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, + }, + }, + }, + ]; + + const parameters: SetPolicyPreventMemberCreatedTitleParams = { + enforced, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_MEMBER_CREATED_TITLE, parameters, { + optimisticData, + failureData, + }); +} + +/** + * Call the API to enable or disable self approvals for the reports + * @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports + * @param policyID - id of the policy to apply the naming pattern to + */ +function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: string) { + const policy = getPolicy(policyID); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + preventSelfApproval, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + preventSelfApproval: policy?.preventSelfApproval, + }, + }, + ]; + + const parameters: SetPolicyPreventSelfApprovalParams = { + preventSelfApproval, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL, parameters, { + optimisticData, + failureData, + }); +} + +/** + * Call the API to apply automatic approval limit for the given policy + * @param limit - max amount for auto-approval of the reports in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { + const fallbackLimit = limit === '' ? '0' : limit; + const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(fallbackLimit)); + const policy = getPolicy(policyID); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoApproval: { + limit: parsedLimit, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoApproval: { + limit: policy?.autoApproval?.limit, + }, + }, + }, + ]; + + const parameters: SetPolicyAutomaticApprovalLimitParams = { + limit: parsedLimit, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT, parameters, { + optimisticData, + failureData, + }); +} + +/** + * Call the API to set the audit rate for the given policy + * @param auditRate - percentage of the reports to be qualified for a random audit + * @param policyID - id of the policy to apply the limit to + */ +function setPolicyAutomaticApprovalAuditRate(auditRate: string, policyID: string) { + const fallbackAuditRate = auditRate === '' ? '0' : auditRate; + const parsedAuditRate = parseInt(fallbackAuditRate, 10); + const policy = getPolicy(policyID); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoApproval: { + auditRate: parsedAuditRate, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoApproval: { + auditRate: policy?.autoApproval?.auditRate, + }, + }, + }, + ]; + + const parameters: SetPolicyAutomaticApprovalAuditRateParams = { + auditRate: parsedAuditRate, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE, parameters, { + optimisticData, + failureData, + }); +} + +/** + * Call the API to enable auto-approval for the reports in the given policy + * @param enabled - whether auto-approve for the reports is enabled in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function enableAutoApprovalOptions(enabled: boolean, policyID: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowAutoApprovalOptions: enabled, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowAutoApprovalOptions: !enabled, + }, + }, + ]; + + const parameters: EnablePolicyAutoApprovalOptionsParams = { + enabled, + policyID, + }; + + API.write(WRITE_COMMANDS.ENABLE_POLICY_AUTO_APPROVAL_OPTIONS, parameters, { + optimisticData, + failureData, + }); +} + +/** + * Call the API to set the limit for auto-payments in the given policy + * @param limit - max amount for auto-payment for the reports in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { + const policy = getPolicy(policyID); + const fallbackLimit = limit === '' ? '0' : limit; + const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(fallbackLimit)); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, + value: { + maxExpenseAutoPayAmount: limit, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoReimbursement: { + limit: parsedLimit, + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, + value: { + maxExpenseAutoPayAmount: limit, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoReimbursement: { + limit: parsedLimit, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoReimbursement: policy?.autoReimbursement, + }, + }, + ]; + + const parameters: SetPolicyAutoReimbursementLimitParams = { + autoReimbursement: {limit: parsedLimit}, + policyID, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_AUTO_REIMBURSEMENT_LIMIT, parameters, { + optimisticData, + successData, + failureData, + }); +} + +/** + * Call the API to enable auto-payment for the reports in the given policy + * @param enabled - whether auto-payment for the reports is enabled in the given policy + * @param policyID - id of the policy to apply the limit to + */ +function enablePolicyAutoReimbursementLimit(enabled: boolean, policyID: string) { + const policy = getPolicy(policyID); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowAutoReimbursementLimitOption: enabled, + autoReimbursement: { + limit: enabled ? policy?.autoReimbursement?.limit : CONST.POLICY.AUTO_REIMBURSEMENT_DEFAULT_LIMIT_CENTS, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + shouldShowAutoReimbursementLimitOption: !enabled, + autoReimbursement: { + limit: policy?.autoReimbursement?.limit, + }, + }, + }, + ]; + + const parameters: EnablePolicyAutoReimbursementLimitParams = { + enabled, + policyID, + }; + + API.write(WRITE_COMMANDS.ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT, parameters, { + optimisticData, + failureData, + }); +} + export { leaveWorkspace, addBillingCardAndRequestPolicyOwnerChange, @@ -3310,6 +3737,15 @@ export { getAdminPoliciesConnectedToSageIntacct, hasInvoicingDetails, enablePolicyRules, + setPolicyDefaultReportTitle, + setPolicyPreventMemberCreatedTitle, + setPolicyPreventSelfApproval, + setPolicyAutomaticApprovalLimit, + setPolicyAutomaticApprovalAuditRate, + setPolicyAutoReimbursementLimit, + enablePolicyDefaultReportTitle, + enablePolicyAutoReimbursementLimit, + enableAutoApprovalOptions, }; export type {NewCustomUnit}; diff --git a/src/libs/actions/Workspace/Rules.ts b/src/libs/actions/Workspace/Rules.ts deleted file mode 100644 index e8832cff5eb7..000000000000 --- a/src/libs/actions/Workspace/Rules.ts +++ /dev/null @@ -1,448 +0,0 @@ -import type {OnyxUpdate} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; -import * as API from '@libs/API'; -import type { - EnablePolicyAutoApprovalOptionsParams, - EnablePolicyAutoReimbursementLimitParams, - EnablePolicyDefaultReportTitleParams, - SetPolicyAutomaticApprovalLimitParams, - SetPolicyAutoReimbursementLimitParams, - SetPolicyDefaultReportTitleParams, - SetPolicyPreventSelfApprovalParams, -} from '@libs/API/parameters'; -import type SetPolicyAutomaticApprovalAuditRateParams from '@libs/API/parameters/SetPolicyAutomaticApprovalAuditRate'; -import type SetPolicyPreventMemberCreatedTitleParams from '@libs/API/parameters/SetPolicyPreventMemberCreatedTitleParams'; -import {WRITE_COMMANDS} from '@libs/API/types'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; -import {getPolicy} from '@libs/PolicyUtils'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** - * Call the API to enable custom report title for the reports in the given policy - * @param enabled - whether custom report title for the reports is enabled in the given policy - * @param policyID - id of the policy to apply the limit to - */ -function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { - const policy = getPolicy(policyID); - const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; - - const titleFieldValues = enabled ? previousFieldList : {...previousFieldList, defaultValue: CONST.POLICY.DEFAULT_REPORT_NAME_PATTERN}; - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - shouldShowCustomReportTitleOption: enabled, - fieldList: { - [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: titleFieldValues, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - shouldShowCustomReportTitleOption: !!policy?.shouldShowCustomReportTitleOption, - fieldList: { - [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, - }, - }, - }, - ]; - - const parameters: EnablePolicyDefaultReportTitleParams = { - enable: enabled, - policyID, - }; - - API.write(WRITE_COMMANDS.ENABLE_POLICY_DEFAULT_REPORT_TITLE, parameters, { - optimisticData, - failureData, - }); -} - -/** - * Call the API to set default report title pattern for the given policy - * @param customName - name pattern to be used for the reports - * @param policyID - id of the policy to apply the naming pattern to - */ -function setPolicyDefaultReportTitle(customName: string, policyID: string) { - const policy = getPolicy(policyID); - const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - fieldList: { - [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: {...previousFieldList, defaultValue: customName}, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - fieldList: { - [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, - }, - }, - }, - ]; - - const parameters: SetPolicyDefaultReportTitleParams = { - value: customName, - policyID, - }; - - API.write(WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE, parameters, { - optimisticData, - failureData, - }); -} - -/** - * Call the API to enable or disable enforcing the naming pattern for the reports - * @param enforced - flag whether to enforce policy name - * @param policyID - id of the policy to apply the naming pattern to - */ -function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) { - const policy = getPolicy(policyID); - const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - fieldList: { - [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: {...previousFieldList, deletable: !enforced}, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - fieldList: { - [CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID]: previousFieldList, - }, - }, - }, - ]; - - const parameters: SetPolicyPreventMemberCreatedTitleParams = { - enforced, - policyID, - }; - - API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_MEMBER_CREATED_TITLE, parameters, { - optimisticData, - failureData, - }); -} - -/** - * Call the API to enable or disable self approvals for the reports - * @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports - * @param policyID - id of the policy to apply the naming pattern to - */ -function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: string) { - const policy = getPolicy(policyID); - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - preventSelfApproval, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - preventSelfApproval: policy?.preventSelfApproval, - }, - }, - ]; - - const parameters: SetPolicyPreventSelfApprovalParams = { - preventSelfApproval, - policyID, - }; - - API.write(WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL, parameters, { - optimisticData, - failureData, - }); -} - -/** - * Call the API to apply automatic approval limit for the given policy - * @param limit - max amount for auto-approval of the reports in the given policy - * @param policyID - id of the policy to apply the limit to - */ -function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { - const fallbackLimit = limit === '' ? '0' : limit; - const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(fallbackLimit)); - const policy = getPolicy(policyID); - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - autoApproval: { - limit: parsedLimit, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - autoApproval: { - limit: policy?.autoApproval?.limit, - }, - }, - }, - ]; - - const parameters: SetPolicyAutomaticApprovalLimitParams = { - limit: parsedLimit, - policyID, - }; - - API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT, parameters, { - optimisticData, - failureData, - }); -} - -/** - * Call the API to set the audit rate for the given policy - * @param auditRate - percentage of the reports to be qualified for a random audit - * @param policyID - id of the policy to apply the limit to - */ -function setPolicyAutomaticApprovalAuditRate(auditRate: string, policyID: string) { - const fallbackAuditRate = auditRate === '' ? '0' : auditRate; - const parsedAuditRate = parseInt(fallbackAuditRate, 10); - const policy = getPolicy(policyID); - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - autoApproval: { - auditRate: parsedAuditRate, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - autoApproval: { - auditRate: policy?.autoApproval?.auditRate, - }, - }, - }, - ]; - - const parameters: SetPolicyAutomaticApprovalAuditRateParams = { - auditRate: parsedAuditRate, - policyID, - }; - - API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE, parameters, { - optimisticData, - failureData, - }); -} - -/** - * Call the API to enable auto-approval for the reports in the given policy - * @param enabled - whether auto-approve for the reports is enabled in the given policy - * @param policyID - id of the policy to apply the limit to - */ -function enableAutoApprovalOptions(enabled: boolean, policyID: string) { - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - shouldShowAutoApprovalOptions: enabled, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - shouldShowAutoApprovalOptions: !enabled, - }, - }, - ]; - - const parameters: EnablePolicyAutoApprovalOptionsParams = { - enabled, - policyID, - }; - - API.write(WRITE_COMMANDS.ENABLE_POLICY_AUTO_APPROVAL_OPTIONS, parameters, { - optimisticData, - failureData, - }); -} - -/** - * Call the API to set the limit for auto-payments in the given policy - * @param limit - max amount for auto-payment for the reports in the given policy - * @param policyID - id of the policy to apply the limit to - */ -function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { - const policy = getPolicy(policyID); - const fallbackLimit = limit === '' ? '0' : limit; - const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(fallbackLimit)); - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, - value: { - maxExpenseAutoPayAmount: limit, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - autoReimbursement: { - limit: parsedLimit, - }, - }, - }, - ]; - - const successData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM, - value: { - maxExpenseAutoPayAmount: limit, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - autoReimbursement: { - limit: parsedLimit, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - autoReimbursement: policy?.autoReimbursement, - }, - }, - ]; - - const parameters: SetPolicyAutoReimbursementLimitParams = { - autoReimbursement: {limit: parsedLimit}, - policyID, - }; - - API.write(WRITE_COMMANDS.SET_POLICY_AUTO_REIMBURSEMENT_LIMIT, parameters, { - optimisticData, - successData, - failureData, - }); -} - -/** - * Call the API to enable auto-payment for the reports in the given policy - * @param enabled - whether auto-payment for the reports is enabled in the given policy - * @param policyID - id of the policy to apply the limit to - */ -function enablePolicyAutoReimbursementLimit(enabled: boolean, policyID: string) { - const policy = getPolicy(policyID); - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - shouldShowAutoReimbursementLimitOption: enabled, - autoReimbursement: { - limit: enabled ? policy?.autoReimbursement?.limit : CONST.POLICY.AUTO_REIMBURSEMENT_DEFAULT_LIMIT_CENTS, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - shouldShowAutoReimbursementLimitOption: !enabled, - autoReimbursement: { - limit: policy?.autoReimbursement?.limit, - }, - }, - }, - ]; - - const parameters: EnablePolicyAutoReimbursementLimitParams = { - enabled, - policyID, - }; - - API.write(WRITE_COMMANDS.ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT, parameters, { - optimisticData, - failureData, - }); -} - -export { - setPolicyDefaultReportTitle, - setPolicyPreventMemberCreatedTitle, - setPolicyPreventSelfApproval, - setPolicyAutomaticApprovalLimit, - setPolicyAutomaticApprovalAuditRate, - setPolicyAutoReimbursementLimit, - enablePolicyDefaultReportTitle, - enablePolicyAutoReimbursementLimit, - enableAutoApprovalOptions, -}; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index da12d916ce85..8bef4bdb0fff 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -9,7 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; -import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; +import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -48,7 +48,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { subtitle: translate('workspace.rules.expenseReportRules.customReportNamesSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), isActive: policy?.shouldShowCustomReportTitleOption, - onToggle: (isEnabled: boolean) => WorkspaceRulesActions.enablePolicyDefaultReportTitle(isEnabled, policyID), + onToggle: (isEnabled: boolean) => PolicyActions.enablePolicyDefaultReportTitle(isEnabled, policyID), subMenuItems: [ WorkspaceRulesActions.setPolicyPreventMemberCreatedTitle(isEnabled, policyID)} + onToggle={(isEnabled) => PolicyActions.setPolicyPreventMemberCreatedTitle(isEnabled, policyID)} />, ], }, @@ -76,7 +76,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { isActive: policy?.preventSelfApproval && !workflowApprovalsUnavailable, disabled: workflowApprovalsUnavailable, showLockIcon: workflowApprovalsUnavailable, - onToggle: (isEnabled: boolean) => WorkspaceRulesActions.setPolicyPreventSelfApproval(isEnabled, policyID), + onToggle: (isEnabled: boolean) => PolicyActions.setPolicyPreventSelfApproval(isEnabled, policyID), }, { title: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), @@ -88,7 +88,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { disabled: workflowApprovalsUnavailable, showLockIcon: workflowApprovalsUnavailable, onToggle: (isEnabled: boolean) => { - WorkspaceRulesActions.enableAutoApprovalOptions(isEnabled, policyID); + PolicyActions.enableAutoApprovalOptions(isEnabled, policyID); }, subMenuItems: [ { - WorkspaceRulesActions.enablePolicyAutoReimbursementLimit(isEnabled, policyID); + PolicyActions.enablePolicyAutoReimbursementLimit(isEnabled, policyID); }, disabled: autoPayApprovedReportsUnavailable, showLockIcon: autoPayApprovedReportsUnavailable, diff --git a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx index ef6ae70a9d39..11b73dd25214 100644 --- a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx @@ -15,7 +15,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; -import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; +import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -52,7 +52,7 @@ function RulesAutoApproveReportsUnderPage({route}: RulesAutoApproveReportsUnderP style={[styles.flexGrow1, styles.mh5, styles.mt5]} formID={ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM} onSubmit={({maxExpenseAutoApprovalAmount}) => { - WorkspaceRulesActions.setPolicyAutomaticApprovalLimit(maxExpenseAutoApprovalAmount, policyID); + PolicyActions.setPolicyAutomaticApprovalLimit(maxExpenseAutoApprovalAmount, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} diff --git a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx index 2092f8d6003e..265338d0c7f5 100644 --- a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx @@ -16,7 +16,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; -import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; +import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -62,7 +62,7 @@ function RulesAutoPayReportsUnderPage({route}: RulesAutoPayReportsUnderPageProps formID={ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM} validate={validateLimit} onSubmit={({maxExpenseAutoPayAmount}) => { - WorkspaceRulesActions.setPolicyAutoReimbursementLimit(maxExpenseAutoPayAmount, policyID); + PolicyActions.setPolicyAutoReimbursementLimit(maxExpenseAutoPayAmount, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} diff --git a/src/pages/workspace/rules/RulesCustomNamePage.tsx b/src/pages/workspace/rules/RulesCustomNamePage.tsx index 380657ea6194..bd4671a4857f 100644 --- a/src/pages/workspace/rules/RulesCustomNamePage.tsx +++ b/src/pages/workspace/rules/RulesCustomNamePage.tsx @@ -16,7 +16,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; -import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; +import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -81,7 +81,7 @@ function RulesCustomNamePage({route}: RulesCustomNamePageProps) { formID={ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM} validate={validateCustomName} onSubmit={({customName}) => { - WorkspaceRulesActions.setPolicyDefaultReportTitle(customName, policyID); + PolicyActions.setPolicyDefaultReportTitle(customName, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} diff --git a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx index 2631f9c2ff68..f57e63ab2c9f 100644 --- a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx +++ b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx @@ -14,7 +14,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; -import * as WorkspaceRulesActions from '@userActions/Workspace/Rules'; +import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -51,7 +51,7 @@ function RulesRandomReportAuditPage({route}: RulesRandomReportAuditPageProps) { style={[styles.flexGrow1, styles.mh5, styles.mt5]} formID={ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM} onSubmit={({auditRatePercentage}) => { - WorkspaceRulesActions.setPolicyAutomaticApprovalAuditRate(auditRatePercentage, policyID); + PolicyActions.setPolicyAutomaticApprovalAuditRate(auditRatePercentage, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} From b89de0ae2138c78d840a126a234d2fd87cc85299 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 21 Aug 2024 00:44:08 +0200 Subject: [PATCH 060/502] fix audit rate command --- .../parameters/SetPolicyAutomaticApprovalAuditRate.ts | 6 ------ .../API/parameters/SetPolicyAutomaticApprovalRate.ts | 6 ++++++ src/libs/API/parameters/index.ts | 2 +- src/libs/API/types.ts | 4 ++-- src/libs/actions/Policy/Policy.ts | 10 +++++----- .../workspace/rules/RulesRandomReportAuditPage.tsx | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 src/libs/API/parameters/SetPolicyAutomaticApprovalAuditRate.ts create mode 100644 src/libs/API/parameters/SetPolicyAutomaticApprovalRate.ts diff --git a/src/libs/API/parameters/SetPolicyAutomaticApprovalAuditRate.ts b/src/libs/API/parameters/SetPolicyAutomaticApprovalAuditRate.ts deleted file mode 100644 index 50cfb43d4a54..000000000000 --- a/src/libs/API/parameters/SetPolicyAutomaticApprovalAuditRate.ts +++ /dev/null @@ -1,6 +0,0 @@ -type SetPolicyAutomaticApprovalAuditRateParams = { - policyID: string; - auditRate: number; -}; - -export default SetPolicyAutomaticApprovalAuditRateParams; diff --git a/src/libs/API/parameters/SetPolicyAutomaticApprovalRate.ts b/src/libs/API/parameters/SetPolicyAutomaticApprovalRate.ts new file mode 100644 index 000000000000..14e331fcb27c --- /dev/null +++ b/src/libs/API/parameters/SetPolicyAutomaticApprovalRate.ts @@ -0,0 +1,6 @@ +type SetPolicyAutomaticApprovalRateParams = { + policyID: string; + auditRate: number; +}; + +export default SetPolicyAutomaticApprovalRateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 6e3938b30d2d..007bcd03e9c3 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -276,7 +276,7 @@ export type {default as SetPolicyRulesEnabledParams} from './SetPolicyRulesEnabl export type {default as SetPolicyDefaultReportTitleParams} from './SetPolicyDefaultReportTitle'; export type {default as SetPolicyPreventSelfApprovalParams} from './SetPolicyPreventSelfApproval'; export type {default as SetPolicyAutomaticApprovalLimitParams} from './SetPolicyAutomaticApprovalLimit'; -export type {default as SetPolicyAutomaticApprovalAuditRateParams} from './SetPolicyAutomaticApprovalAuditRate'; +export type {default as SetPolicyAutomaticApprovalRateParams} from './SetPolicyAutomaticApprovalRate'; export type {default as SetPolicyPreventMemberCreatedTitleParams} from './SetPolicyPreventMemberCreatedTitleParams'; export type {default as SetPolicyAutoReimbursementLimitParams} from './SetPolicyAutoReimbursementLimit'; export type {default as EnablePolicyAutoReimbursementLimitParams} from './EnablePolicyAutoReimbursementLimit'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 5837fd890f41..5f1bb9371de8 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -18,7 +18,7 @@ const WRITE_COMMANDS = { SET_POLICY_PREVENT_MEMBER_CREATED_TITLE: 'SetPolicyPreventMemberCreatedTitle', SET_POLICY_PREVENT_SELF_APPROVAL: 'SetPolicyPreventSelfApproval', SET_POLICY_AUTOMATIC_APPROVAL_LIMIT: 'SetPolicyAutomaticApprovalLimit', - SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE: 'SetPolicyAutomaticApprovalAuditRate', + SET_POLICY_AUTOMATIC_APPROVAL_RATE: 'SetPolicyAutomaticApprovalRate', SET_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'SetPolicyAutoReimbursementLimit', ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT: 'EnablePolicyAutoReimbursementLimit', ENABLE_POLICY_AUTO_APPROVAL_OPTIONS: 'EnablePolicyAutoApprovalOptions', @@ -532,7 +532,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_POLICY_DEFAULT_REPORT_TITLE]: Parameters.SetPolicyDefaultReportTitleParams; [WRITE_COMMANDS.SET_POLICY_PREVENT_SELF_APPROVAL]: Parameters.SetPolicyPreventSelfApprovalParams; [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_LIMIT]: Parameters.SetPolicyAutomaticApprovalLimitParams; - [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE]: Parameters.SetPolicyAutomaticApprovalAuditRateParams; + [WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_RATE]: Parameters.SetPolicyAutomaticApprovalRateParams; [WRITE_COMMANDS.SET_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.SetPolicyAutoReimbursementLimitParams; [WRITE_COMMANDS.ENABLE_POLICY_AUTO_REIMBURSEMENT_LIMIT]: Parameters.EnablePolicyAutoReimbursementLimitParams; [WRITE_COMMANDS.ENABLE_POLICY_AUTO_APPROVAL_OPTIONS]: Parameters.EnablePolicyAutoApprovalOptionsParams; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index e401e5955a82..1d1fa4ac0c5a 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -34,8 +34,8 @@ import type { OpenWorkspaceParams, OpenWorkspaceReimburseViewParams, RequestExpensifyCardLimitIncreaseParams, - SetPolicyAutomaticApprovalAuditRateParams, SetPolicyAutomaticApprovalLimitParams, + SetPolicyAutomaticApprovalRateParams, SetPolicyAutoReimbursementLimitParams, SetPolicyDefaultReportTitleParams, SetPolicyPreventMemberCreatedTitleParams, @@ -3585,7 +3585,7 @@ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { * @param auditRate - percentage of the reports to be qualified for a random audit * @param policyID - id of the policy to apply the limit to */ -function setPolicyAutomaticApprovalAuditRate(auditRate: string, policyID: string) { +function setPolicyAutomaticApprovalRate(auditRate: string, policyID: string) { const fallbackAuditRate = auditRate === '' ? '0' : auditRate; const parsedAuditRate = parseInt(fallbackAuditRate, 10); const policy = getPolicy(policyID); @@ -3614,12 +3614,12 @@ function setPolicyAutomaticApprovalAuditRate(auditRate: string, policyID: string }, ]; - const parameters: SetPolicyAutomaticApprovalAuditRateParams = { + const parameters: SetPolicyAutomaticApprovalRateParams = { auditRate: parsedAuditRate, policyID, }; - API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_AUDIT_RATE, parameters, { + API.write(WRITE_COMMANDS.SET_POLICY_AUTOMATIC_APPROVAL_RATE, parameters, { optimisticData, failureData, }); @@ -3858,7 +3858,7 @@ export { setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, setPolicyAutomaticApprovalLimit, - setPolicyAutomaticApprovalAuditRate, + setPolicyAutomaticApprovalRate, setPolicyAutoReimbursementLimit, enablePolicyDefaultReportTitle, enablePolicyAutoReimbursementLimit, diff --git a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx index f57e63ab2c9f..233c187d9459 100644 --- a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx +++ b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx @@ -51,7 +51,7 @@ function RulesRandomReportAuditPage({route}: RulesRandomReportAuditPageProps) { style={[styles.flexGrow1, styles.mh5, styles.mt5]} formID={ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM} onSubmit={({auditRatePercentage}) => { - PolicyActions.setPolicyAutomaticApprovalAuditRate(auditRatePercentage, policyID); + PolicyActions.setPolicyAutomaticApprovalRate(auditRatePercentage, policyID); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} From 00b81751cb96a24b68be57a6f870bd0f7a7a4f47 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 21 Aug 2024 00:50:20 +0200 Subject: [PATCH 061/502] add keys --- src/pages/workspace/rules/ExpenseReportRulesSection.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 8bef4bdb0fff..3b0763c13374 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -51,6 +51,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { onToggle: (isEnabled: boolean) => PolicyActions.enablePolicyDefaultReportTitle(isEnabled, policyID), subMenuItems: [ Navigation.navigate(ROUTES.RULES_CUSTOM_NAME.getRoute(policyID))} />, Navigation.navigate(ROUTES.RULES_AUTO_APPROVE_REPORTS_UNDER.getRoute(policyID))} />, Date: Wed, 21 Aug 2024 06:54:26 +0530 Subject: [PATCH 062/502] Apply suggestions from code review Co-authored-by: Ishpaul Singh <104348397+ishpaul777@users.noreply.github.com> --- tests/actions/IOUTest.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 0b599824dba9..2b45443f8cac 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1165,7 +1165,9 @@ describe('actions/IOU', () => { // The 1:1 chat reports and the IOU reports should be linked together expect(carlosChatReport?.iouReportID).toBe(carlosIOUReport?.reportID); expect(carlosIOUReport?.chatReportID).toBe(carlosChatReport?.reportID); - expect(carlosIOUReport?.participants).toStrictEqual({[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); +Object.values(carlosIOUReport?.participants ?? {}).forEach((participant) => { + expect(participant.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); +}); expect(julesChatReport?.iouReportID).toBe(julesIOUReport?.reportID); expect(julesIOUReport?.chatReportID).toBe(julesChatReport?.reportID); @@ -2442,7 +2444,7 @@ describe('actions/IOU', () => { // Given a transaction thread thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.participants).toStrictEqual({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); + expect(thread.participants).toStrictEqual({[CARLOS_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, @@ -2631,7 +2633,7 @@ describe('actions/IOU', () => { // Given a transaction thread thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.participants).toEqual({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); + expect(thread.participants).toEqual({[CARLOS_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); const participantAccountIDs = Object.keys(thread.participants ?? {}).map(Number); const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs); @@ -2719,7 +2721,7 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.participants).toStrictEqual({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); + expect(thread.participants).toStrictEqual({[CARLOS_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, @@ -2959,7 +2961,7 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport); - expect(thread.participants).toStrictEqual({[RORY_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); + expect(thread.participants).toStrictEqual({[CARLOS_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, role: CONST.REPORT.ROLE.ADMIN}}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, From 6967e8dc193f74ec324e8ce00481bffcae55ede8 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 21 Aug 2024 06:55:58 +0530 Subject: [PATCH 063/502] Fix lint --- tests/actions/IOUTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 2b45443f8cac..5684619945f1 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1165,9 +1165,9 @@ describe('actions/IOU', () => { // The 1:1 chat reports and the IOU reports should be linked together expect(carlosChatReport?.iouReportID).toBe(carlosIOUReport?.reportID); expect(carlosIOUReport?.chatReportID).toBe(carlosChatReport?.reportID); -Object.values(carlosIOUReport?.participants ?? {}).forEach((participant) => { + Object.values(carlosIOUReport?.participants ?? {}).forEach((participant) => { expect(participant.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); -}); + }); expect(julesChatReport?.iouReportID).toBe(julesIOUReport?.reportID); expect(julesIOUReport?.chatReportID).toBe(julesChatReport?.reportID); From cf6ab27f9ac6622024642036cbe2f1127bb4b74f Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 21 Aug 2024 09:37:33 +0530 Subject: [PATCH 064/502] Fix jest test --- tests/actions/IOUTest.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 5684619945f1..f698ac5c7979 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1148,17 +1148,27 @@ describe('actions/IOU', () => { expect(isEmptyObject(vitIOUReport)).toBe(false); expect(vitIOUReport?.total).toBe(amount / 4); + const groupParticipants = { + [CARLOS_ACCOUNT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + role: CONST.REPORT.ROLE.MEMBER, + }, + [JULES_ACCOUNT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + role: CONST.REPORT.ROLE.MEMBER, + }, + [VIT_ACCOUNT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + role: CONST.REPORT.ROLE.MEMBER, + }, + [RORY_ACCOUNT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }, + }; + // 7. The group chat with everyone - groupChat = Object.values(allReports ?? {}).find( - (report) => - report?.type === CONST.REPORT.TYPE.CHAT && - isEqual(report.participants, { - [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT, - [JULES_ACCOUNT_ID]: JULES_PARTICIPANT, - [VIT_ACCOUNT_ID]: VIT_PARTICIPANT, - [RORY_ACCOUNT_ID]: RORY_PARTICIPANT, - }), - ); + groupChat = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT && isEqual(report.participants, groupParticipants)); expect(isEmptyObject(groupChat)).toBe(false); expect(groupChat?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}); From 7733be8a4f4d6ffa7bc7c5979727b3ae0d36886f Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 21 Aug 2024 09:39:15 +0530 Subject: [PATCH 065/502] Fix jest test --- src/libs/actions/IOU.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c433d9922f4b..7e9ec5cd3c55 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3844,6 +3844,7 @@ function getOrCreateOptimisticSplitChatReport(existingSplitChatReportID: string, undefined, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); + console.debug('Created a new group chat report', splitChatReport); return {existingSplitChatReport: null, splitChatReport}; } @@ -3954,6 +3955,16 @@ function createSplitsAndOnyxData( }; } + const updatedParticipants = fastMerge( + splitChatReport.participants, + { + [currentUserAccountID]: { + notificationPreference: splitChatReportNotificationPreference, + }, + }, + false, + ); + const optimisticData: OnyxUpdate[] = [ { // Use set for new reports because it doesn't exist yet, is faster, @@ -3962,11 +3973,7 @@ function createSplitsAndOnyxData( key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`, value: { ...splitChatReport, - participants: { - [currentUserAccountID]: { - notificationPreference: splitChatReportNotificationPreference, - }, - }, + participants: updatedParticipants, }, }, { From b8d8979bd9d310a38ae1755eb95761210b0cffec Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:51:51 +0530 Subject: [PATCH 066/502] Apply suggestions from code review --- src/libs/actions/IOU.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cf76b0069e96..2f382cb7829a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3848,7 +3848,6 @@ function getOrCreateOptimisticSplitChatReport(existingSplitChatReportID: string, undefined, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); - console.debug('Created a new group chat report', splitChatReport); return {existingSplitChatReport: null, splitChatReport}; } From 05f9529087a1fea6da0173875755e97488846797 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 21 Aug 2024 13:31:56 +0200 Subject: [PATCH 067/502] cleanup --- src/libs/actions/Policy/Policy.ts | 36 +++++++++---------- .../rules/ExpenseReportRulesSection.tsx | 15 ++++---- .../RulesAutoApproveReportsUnderPage.tsx | 6 ++-- .../rules/RulesAutoPayReportsUnderPage.tsx | 6 ++-- .../workspace/rules/RulesCustomNamePage.tsx | 6 ++-- .../rules/RulesRandomReportAuditPage.tsx | 4 +-- 6 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 1d1fa4ac0c5a..6758e3daa051 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -3362,10 +3362,10 @@ function getAdminPoliciesConnectedToNetSuite(): Policy[] { /** * Call the API to enable custom report title for the reports in the given policy - * @param enabled - whether custom report title for the reports is enabled in the given policy * @param policyID - id of the policy to apply the limit to + * @param enabled - whether custom report title for the reports is enabled in the given policy */ -function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { +function enablePolicyDefaultReportTitle(policyID: string, enabled: boolean) { const policy = getPolicy(policyID); const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; @@ -3410,10 +3410,10 @@ function enablePolicyDefaultReportTitle(enabled: boolean, policyID: string) { /** * Call the API to set default report title pattern for the given policy - * @param customName - name pattern to be used for the reports * @param policyID - id of the policy to apply the naming pattern to + * @param customName - name pattern to be used for the reports */ -function setPolicyDefaultReportTitle(customName: string, policyID: string) { +function setPolicyDefaultReportTitle(policyID: string, customName: string) { const policy = getPolicy(policyID); const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; @@ -3454,10 +3454,10 @@ function setPolicyDefaultReportTitle(customName: string, policyID: string) { /** * Call the API to enable or disable enforcing the naming pattern for the reports - * @param enforced - flag whether to enforce policy name * @param policyID - id of the policy to apply the naming pattern to + * @param enforced - flag whether to enforce policy name */ -function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) { +function setPolicyPreventMemberCreatedTitle(policyID: string, enforced: boolean) { const policy = getPolicy(policyID); const previousFieldList = policy?.fieldList?.[CONST.POLICY.FIELD_LIST_TITLE_FIELD_ID] ?? {}; @@ -3498,10 +3498,10 @@ function setPolicyPreventMemberCreatedTitle(enforced: boolean, policyID: string) /** * Call the API to enable or disable self approvals for the reports - * @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports * @param policyID - id of the policy to apply the naming pattern to + * @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports */ -function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: string) { +function setPolicyPreventSelfApproval(policyID: string, preventSelfApproval: boolean) { const policy = getPolicy(policyID); const optimisticData: OnyxUpdate[] = [ @@ -3537,10 +3537,10 @@ function setPolicyPreventSelfApproval(preventSelfApproval: boolean, policyID: st /** * Call the API to apply automatic approval limit for the given policy - * @param limit - max amount for auto-approval of the reports in the given policy * @param policyID - id of the policy to apply the limit to + * @param limit - max amount for auto-approval of the reports in the given policy */ -function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { +function setPolicyAutomaticApprovalLimit(policyID: string, limit: string) { const fallbackLimit = limit === '' ? '0' : limit; const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(fallbackLimit)); const policy = getPolicy(policyID); @@ -3582,10 +3582,10 @@ function setPolicyAutomaticApprovalLimit(limit: string, policyID: string) { /** * Call the API to set the audit rate for the given policy - * @param auditRate - percentage of the reports to be qualified for a random audit * @param policyID - id of the policy to apply the limit to + * @param auditRate - percentage of the reports to be qualified for a random audit */ -function setPolicyAutomaticApprovalRate(auditRate: string, policyID: string) { +function setPolicyAutomaticApprovalRate(policyID: string, auditRate: string) { const fallbackAuditRate = auditRate === '' ? '0' : auditRate; const parsedAuditRate = parseInt(fallbackAuditRate, 10); const policy = getPolicy(policyID); @@ -3627,10 +3627,10 @@ function setPolicyAutomaticApprovalRate(auditRate: string, policyID: string) { /** * Call the API to enable auto-approval for the reports in the given policy - * @param enabled - whether auto-approve for the reports is enabled in the given policy * @param policyID - id of the policy to apply the limit to + * @param enabled - whether auto-approve for the reports is enabled in the given policy */ -function enableAutoApprovalOptions(enabled: boolean, policyID: string) { +function enableAutoApprovalOptions(policyID: string, enabled: boolean) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -3664,10 +3664,10 @@ function enableAutoApprovalOptions(enabled: boolean, policyID: string) { /** * Call the API to set the limit for auto-payments in the given policy - * @param limit - max amount for auto-payment for the reports in the given policy * @param policyID - id of the policy to apply the limit to + * @param limit - max amount for auto-payment for the reports in the given policy */ -function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { +function setPolicyAutoReimbursementLimit(policyID: string, limit: string) { const policy = getPolicy(policyID); const fallbackLimit = limit === '' ? '0' : limit; const parsedLimit = CurrencyUtils.convertToBackendAmount(parseFloat(fallbackLimit)); @@ -3734,10 +3734,10 @@ function setPolicyAutoReimbursementLimit(limit: string, policyID: string) { /** * Call the API to enable auto-payment for the reports in the given policy - * @param enabled - whether auto-payment for the reports is enabled in the given policy * @param policyID - id of the policy to apply the limit to + * @param enabled - whether auto-payment for the reports is enabled in the given policy */ -function enablePolicyAutoReimbursementLimit(enabled: boolean, policyID: string) { +function enablePolicyAutoReimbursementLimit(policyID: string, enabled: boolean) { const policy = getPolicy(policyID); const optimisticData: OnyxUpdate[] = [ diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 3b0763c13374..81a486112f76 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -1,17 +1,16 @@ import React from 'react'; -import {useOnyx} from 'react-native-onyx'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import Section from '@components/Section'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; type ExpenseReportRulesSectionProps = { @@ -21,7 +20,7 @@ type ExpenseReportRulesSectionProps = { function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const policy = usePolicy(policyID); // Auto-approvals and self-approvals are unavailable due to the policy workflows settings const workflowApprovalsUnavailable = policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.BASIC || !!policy?.errorFields?.approvalMode; @@ -48,7 +47,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { subtitle: translate('workspace.rules.expenseReportRules.customReportNamesSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.customReportNamesTitle'), isActive: policy?.shouldShowCustomReportTitleOption, - onToggle: (isEnabled: boolean) => PolicyActions.enablePolicyDefaultReportTitle(isEnabled, policyID), + onToggle: (isEnabled: boolean) => PolicyActions.enablePolicyDefaultReportTitle(policyID, isEnabled), subMenuItems: [ PolicyActions.setPolicyPreventMemberCreatedTitle(isEnabled, policyID)} + onToggle={(isEnabled) => PolicyActions.setPolicyPreventMemberCreatedTitle(policyID, isEnabled)} />, ], }, @@ -78,7 +77,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { isActive: policy?.preventSelfApproval && !workflowApprovalsUnavailable, disabled: workflowApprovalsUnavailable, showLockIcon: workflowApprovalsUnavailable, - onToggle: (isEnabled: boolean) => PolicyActions.setPolicyPreventSelfApproval(isEnabled, policyID), + onToggle: (isEnabled: boolean) => PolicyActions.setPolicyPreventSelfApproval(policyID, isEnabled), }, { title: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), @@ -90,7 +89,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { disabled: workflowApprovalsUnavailable, showLockIcon: workflowApprovalsUnavailable, onToggle: (isEnabled: boolean) => { - PolicyActions.enableAutoApprovalOptions(isEnabled, policyID); + PolicyActions.enableAutoApprovalOptions(policyID, isEnabled); }, subMenuItems: [ { - PolicyActions.enablePolicyAutoReimbursementLimit(isEnabled, policyID); + PolicyActions.enablePolicyAutoReimbursementLimit(policyID, isEnabled); }, disabled: autoPayApprovedReportsUnavailable, showLockIcon: autoPayApprovedReportsUnavailable, diff --git a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx index 11b73dd25214..0bd360b47960 100644 --- a/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoApproveReportsUnderPage.tsx @@ -1,7 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -10,6 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -25,7 +25,7 @@ type RulesAutoApproveReportsUnderPageProps = StackScreenProps { - PolicyActions.setPolicyAutomaticApprovalLimit(maxExpenseAutoApprovalAmount, policyID); + PolicyActions.setPolicyAutomaticApprovalLimit(policyID, maxExpenseAutoApprovalAmount); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} diff --git a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx index 265338d0c7f5..0db7f6e85523 100644 --- a/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx +++ b/src/pages/workspace/rules/RulesAutoPayReportsUnderPage.tsx @@ -1,7 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -11,6 +10,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -26,7 +26,7 @@ type RulesAutoPayReportsUnderPageProps = StackScreenProps { - PolicyActions.setPolicyAutoReimbursementLimit(maxExpenseAutoPayAmount, policyID); + PolicyActions.setPolicyAutoReimbursementLimit(policyID, maxExpenseAutoPayAmount); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} diff --git a/src/pages/workspace/rules/RulesCustomNamePage.tsx b/src/pages/workspace/rules/RulesCustomNamePage.tsx index bd4671a4857f..0b1a49c79641 100644 --- a/src/pages/workspace/rules/RulesCustomNamePage.tsx +++ b/src/pages/workspace/rules/RulesCustomNamePage.tsx @@ -1,7 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import BulletList from '@components/BulletList'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -12,6 +11,7 @@ import Text from '@components/Text'; import TextInput from '@components/TextInput'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -26,7 +26,7 @@ type RulesCustomNamePageProps = StackScreenProps { - PolicyActions.setPolicyDefaultReportTitle(customName, policyID); + PolicyActions.setPolicyDefaultReportTitle(policyID, customName); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); }} submitButtonText={translate('common.save')} diff --git a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx index 233c187d9459..6a517d0e1c07 100644 --- a/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx +++ b/src/pages/workspace/rules/RulesRandomReportAuditPage.tsx @@ -1,7 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -10,6 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -24,7 +24,7 @@ type RulesRandomReportAuditPageProps = StackScreenProps Date: Wed, 21 Aug 2024 13:40:25 +0200 Subject: [PATCH 068/502] review fixes --- src/CONST.ts | 1 + src/languages/en.ts | 1 + src/pages/workspace/rules/RulesCustomNamePage.tsx | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 15922637edf4..74054391ec44 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -675,6 +675,7 @@ const CONST = { ADMIN: 'admin', MEMBER: 'member', }, + OLD_DOT_NAMING_URL: 'https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates', MAX_COUNT_BEFORE_FOCUS_UPDATE: 30, MIN_INITIAL_REPORT_ACTION_COUNT: 15, SPLIT_REPORTID: '-2', diff --git a/src/languages/en.ts b/src/languages/en.ts index 8d010c23b6fa..25c3c4a6bb42 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3596,6 +3596,7 @@ export default { subtitle: 'Set spend controls and defaults for individual expenses. You can also create rules for categories and tags.', }, expenseReportRules: { + examples: 'Examples:', title: 'Expense reports', subtitle: 'Automate expense report compliance, approvals, and payment.', customReportNamesTitle: 'Custom report names', diff --git a/src/pages/workspace/rules/RulesCustomNamePage.tsx b/src/pages/workspace/rules/RulesCustomNamePage.tsx index 0b1a49c79641..fc7ea5464e5a 100644 --- a/src/pages/workspace/rules/RulesCustomNamePage.tsx +++ b/src/pages/workspace/rules/RulesCustomNamePage.tsx @@ -64,12 +64,12 @@ function RulesCustomNamePage({route}: RulesCustomNamePageProps) { title={translate('workspace.rules.expenseReportRules.customNameTitle')} onBackButtonPress={() => Navigation.goBack()} /> - + {translate('workspace.rules.expenseReportRules.customNameDescription')} {translate('workspace.rules.expenseReportRules.customNameDescriptionLink')} @@ -97,7 +97,7 @@ function RulesCustomNamePage({route}: RulesCustomNamePageProps) { /> From 2af9b373c36c32bcdefbc0b75fbf19a5c1fd45ae Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 21 Aug 2024 13:59:50 +0200 Subject: [PATCH 069/502] fix translations --- src/languages/en.ts | 52 +++++++++++++++--------------- src/languages/es.ts | 77 ++++++++++++++++++++++++++++----------------- 2 files changed, 74 insertions(+), 55 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 25c3c4a6bb42..de9c2f5e707b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -821,8 +821,8 @@ export default { genericHoldExpenseFailureMessage: 'Unexpected error holding this expense. Please try again later.', genericUnholdExpenseFailureMessage: 'Unexpected error taking this expense off hold. Please try again later.', receiptDeleteFailureError: 'Unexpected error deleting this receipt. Please try again later.', - // eslint-disable-next-line rulesdir/use-periods-for-error-messages receiptFailureMessage: "The receipt didn't upload.", + // eslint-disable-next-line rulesdir/use-periods-for-error-messages saveFileMessage: 'Download the file ', // eslint-disable-next-line rulesdir/use-periods-for-error-messages loseFileMessage: 'or dismiss this error and lose it.', @@ -1730,33 +1730,33 @@ export default { hasBeenThrottledError: 'An error occurred while adding your bank account. Please wait a few minutes and try again.', hasCurrencyError: 'Oops! It appears that your workspace currency is set to a different currency than USD. To proceed, please set it to USD and try again.', error: { - youNeedToSelectAnOption: 'Please select an option to proceed', - noBankAccountAvailable: "Sorry, there's no bank account available", - noBankAccountSelected: 'Please choose an account', - taxID: 'Please enter a valid tax ID number', - website: 'Please enter a valid website using lower-case letters', + youNeedToSelectAnOption: 'Please select an option to proceed.', + noBankAccountAvailable: "Sorry, there's no bank account available.", + noBankAccountSelected: 'Please choose an account.', + taxID: 'Please enter a valid tax ID number.', + website: 'Please enter a valid website using lower-case letters.', zipCode: `Please enter a valid ZIP code using the format: ${CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}`, - phoneNumber: 'Please enter a valid phone number', - companyName: 'Please enter a valid business name', - addressCity: 'Please enter a valid city', - addressStreet: 'Please enter a valid street address', - addressState: 'Please select a valid state', - incorporationDateFuture: "Incorporation date can't be in the future", - incorporationState: 'Please select a valid state', - industryCode: 'Please enter a valid industry classification code with six digits', - restrictedBusiness: "Please confirm the business isn't on the list of restricted businesses", - routingNumber: 'Please enter a valid routing number', - accountNumber: 'Please enter a valid account number', - routingAndAccountNumberCannotBeSame: "Routing and account numbers can't match", - companyType: 'Please select a valid company type', + phoneNumber: 'Please enter a valid phone number.', + companyName: 'Please enter a valid business name.', + addressCity: 'Please enter a valid city.', + addressStreet: 'Please enter a valid street address.', + addressState: 'Please select a valid state.', + incorporationDateFuture: "Incorporation date can't be in the future.", + incorporationState: 'Please select a valid state.', + industryCode: 'Please enter a valid industry classification code with six digits.', + restrictedBusiness: "Please confirm the business isn't on the list of restricted businesses.", + routingNumber: 'Please enter a valid routing number.', + accountNumber: 'Please enter a valid account number.', + routingAndAccountNumberCannotBeSame: "Routing and account numbers can't match.", + companyType: 'Please select a valid company type.', tooManyAttempts: 'Due to a high number of login attempts, this option has been disabled for 24 hours. Please try again later or enter details manually instead.', - address: 'Please enter a valid address', - dob: 'Please select a valid date of birth', - age: 'Must be over 18 years old', - ssnLast4: 'Please enter valid last 4 digits of SSN', - firstName: 'Please enter a valid first name', - lastName: 'Please enter a valid last name', - noDefaultDepositAccountOrDebitCardAvailable: 'Please add a default deposit account or debit card', + address: 'Please enter a valid address.', + dob: 'Please select a valid date of birth.', + age: 'Must be over 18 years old.', + ssnLast4: 'Please enter valid last 4 digits of SSN.', + firstName: 'Please enter a valid first name.', + lastName: 'Please enter a valid last name.', + noDefaultDepositAccountOrDebitCardAvailable: 'Please add a default deposit account or debit card.', validationAmounts: 'The validation amounts you entered are incorrect. Please double check your bank statement and try again.', }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 5ff3c19d01d9..cb8e7f37e5a4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -164,6 +164,7 @@ export default { profile: 'Perfil', referral: 'Remisión', payments: 'Pagos', + approvals: 'Aprobaciones', wallet: 'Billetera', preferences: 'Preferencias', view: 'Ver', @@ -239,6 +240,7 @@ export default { conjunctionAt: 'a', conjunctionTo: 'a', genericErrorMessage: 'Ups... algo no ha ido bien y la acción no se ha podido completar. Por favor, inténtalo más tarde.', + percentage: 'Porcentaje', error: { invalidAmount: 'Importe no válido.', acceptTerms: 'Debes aceptar los Términos de Servicio para continuar.', @@ -815,8 +817,8 @@ export default { genericCreateFailureMessage: 'Error inesperado al enviar este gasto. Por favor, inténtalo más tarde.', genericCreateInvoiceFailureMessage: 'Error inesperado al enviar la factura. Por favor, inténtalo de nuevo más tarde.', receiptDeleteFailureError: 'Error inesperado al borrar este recibo. Por favor, vuelve a intentarlo más tarde.', - // eslint-disable-next-line rulesdir/use-periods-for-error-messages receiptFailureMessage: 'El recibo no se subió.', + // eslint-disable-next-line rulesdir/use-periods-for-error-messages saveFileMessage: 'Guarda el archivo ', // eslint-disable-next-line rulesdir/use-periods-for-error-messages loseFileMessage: 'o descarta este error y piérdelo.', @@ -1756,34 +1758,34 @@ export default { hasBeenThrottledError: 'Se ha producido un error al intentar añadir tu cuenta bancaria. Por favor, espera unos minutos e inténtalo de nuevo.', hasCurrencyError: '¡Ups! Parece que la moneda de tu espacio de trabajo no está configurada en USD. Por favor, configúrala en USD e inténtalo nuevamente.', error: { - youNeedToSelectAnOption: 'Debes seleccionar una opción para continuar', - noBankAccountAvailable: 'Lo sentimos, no hay ninguna cuenta bancaria disponible', - noBankAccountSelected: 'Por favor, elige una cuenta bancaria', - taxID: 'Por favor, introduce un número de identificación fiscal válido', + youNeedToSelectAnOption: 'Debes seleccionar una opción para continuar.', + noBankAccountAvailable: 'Lo sentimos, no hay ninguna cuenta bancaria disponible.', + noBankAccountSelected: 'Por favor, elige una cuenta bancaria.', + taxID: 'Por favor, introduce un número de identificación fiscal válido.', website: 'Por favor, introduce un sitio web válido. El sitio web debe estar en minúsculas.', zipCode: `Formato de código postal incorrecto. Formato aceptable: ${CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}`, - phoneNumber: 'Por favor, introduce un teléfono válido', - companyName: 'Por favor, introduce un nombre comercial legal válido', - addressCity: 'Por favor, introduce una ciudad válida', - addressStreet: 'Por favor, introduce una calle de dirección válida que no sea un apartado postal', - addressState: 'Por favor, selecciona un estado', - incorporationDateFuture: 'La fecha de incorporación no puede ser futura', - incorporationState: 'Por favor, selecciona una estado válido', - industryCode: 'Por favor, introduce un código de clasificación de industria válido', - restrictedBusiness: 'Por favor, confirma que la empresa no está en la lista de negocios restringidos', - routingNumber: 'Por favor, introduce un número de ruta válido', - accountNumber: 'Por favor, introduce un número de cuenta válido', - routingAndAccountNumberCannotBeSame: 'Los números de ruta y de cuenta no pueden ser iguales', - companyType: 'Por favor, selecciona un tipo de compañía válido', + phoneNumber: 'Por favor, introduce un teléfono válido.', + companyName: 'Por favor, introduce un nombre comercial legal válido.', + addressCity: 'Por favor, introduce una ciudad válida.', + addressStreet: 'Por favor, introduce una calle de dirección válida que no sea un apartado postal.', + addressState: 'Por favor, selecciona un estado.', + incorporationDateFuture: 'La fecha de incorporación no puede ser futura.', + incorporationState: 'Por favor, selecciona una estado válido.', + industryCode: 'Por favor, introduce un código de clasificación de industria válido.', + restrictedBusiness: 'Por favor, confirma que la empresa no está en la lista de negocios restringidos.', + routingNumber: 'Por favor, introduce un número de ruta válido.', + accountNumber: 'Por favor, introduce un número de cuenta válido.', + routingAndAccountNumberCannotBeSame: 'Los números de ruta y de cuenta no pueden ser iguales.', + companyType: 'Por favor, selecciona un tipo de compañía válido.', tooManyAttempts: 'Debido a la gran cantidad de intentos de inicio de sesión, esta opción ha sido desactivada temporalmente durante 24 horas. Por favor, inténtalo de nuevo más tarde.', - address: 'Por favor, introduce una dirección válida', - dob: 'Por favor, selecciona una fecha de nacimiento válida', - age: 'Debe ser mayor de 18 años', - ssnLast4: 'Por favor, introduce los últimos 4 dígitos del número de seguridad social', - firstName: 'Por favor, introduce el nombre', - lastName: 'Por favor, introduce los apellidos', - noDefaultDepositAccountOrDebitCardAvailable: 'Por favor, añade una cuenta bancaria para depósitos o una tarjeta de débito', + address: 'Por favor, introduce una dirección válida.', + dob: 'Por favor, selecciona una fecha de nacimiento válida.', + age: 'Debe ser mayor de 18 años.', + ssnLast4: 'Por favor, introduce los últimos 4 dígitos del número de seguridad social.', + firstName: 'Por favor, introduce el nombre.', + lastName: 'Por favor, introduce los apellidos.', + noDefaultDepositAccountOrDebitCardAvailable: 'Por favor, añade una cuenta bancaria para depósitos o una tarjeta de débito.', validationAmounts: 'Los importes de validación que introduciste son incorrectos. Por favor, comprueba tu cuenta bancaria e inténtalo de nuevo.', }, }, @@ -2153,6 +2155,7 @@ export default { travel: 'Viajes', members: 'Miembros', accounting: 'Contabilidad', + rules: 'Reglas', plan: 'Plan', profile: 'Perfil', bankAccount: 'Cuenta bancaria', @@ -2884,6 +2887,11 @@ export default { title: 'Gane', subtitle: 'Habilita funciones opcionales para agilizar tus ingresos y recibir pagos más rápido.', }, + manageSection: { + title: 'Gestionar', + subtitle: 'Agrega controles que ayuden a mantener los gastos dentro del presupuesto.', + }, + organizeSection: { title: 'Organizar', subtitle: 'Agrupa y analiza el gasto, registra cada impuesto pagado.', @@ -2892,6 +2900,10 @@ export default { title: 'Integrar', subtitle: 'Conecta Expensify a otros productos financieros populares.', }, + distanceRates: { + title: 'Tasas de distancia', + subtitle: 'Añade, actualiza y haz cumplir las tasas.', + }, expensifyCard: { title: 'Tarjeta Expensify', subtitle: 'Obtén información y control sobre tus gastos', @@ -2909,10 +2921,6 @@ export default { ctaTitle: 'Emitir nueva tarjeta', }, }, - distanceRates: { - title: 'Tasas de distancia', - subtitle: 'Añade, actualiza y haz cumplir las tasas.', - }, workflows: { title: 'Flujos de trabajo', subtitle: 'Configura cómo se aprueba y paga los gastos.', @@ -2947,6 +2955,10 @@ export default { disconnectText: 'Para desactivar la contabilidad, desconecta tu conexión contable del espacio de trabajo.', manageSettings: 'Gestionar la configuración', }, + rules: { + title: 'Reglas', + subtitle: 'Configura cuándo se requieren recibos, marca gastos elevados y más.', + }, }, reportFields: { addField: 'Añadir campo', @@ -3598,6 +3610,12 @@ export default { learnMore: 'más información', aboutOurPlans: 'sobre nuestros planes y precios.', }, + rules: { + title: 'Reglas', + description: `Las reglas se ejecutan en segundo plano y mantienen tus gastos bajo control, para que no tengas que preocuparte por los pequeños detalles.\n\nRequiere detalles de gastos como recibos y descripciones, establece límites y valores predeterminados, y automatiza aprobaciones y pagos, todo en un solo lugar.`, + onlyAvailableOnPlan: 'Las reglas solo están disponibles en el plan Control, a partir de ', + }, + pricing: { amount: '$9 ', perActiveMember: 'por miembro activo al mes.', @@ -3630,6 +3648,7 @@ export default { subtitle: 'Establece controles de gasto y valores predeterminados para gastos individuales. También puedes crear reglas para categorías y etiquetas.', }, expenseReportRules: { + examples: 'Ejemplos:', title: 'Informes de gastos', subtitle: 'Automatiza el cumplimiento, las aprobaciones y los pagos de los informes de gastos.', customReportNamesTitle: 'Nombres personalizados de informes', From 133290c376f869bee7dde8020e4f1723f34c70d8 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:02:15 +0100 Subject: [PATCH 070/502] Fix top margin --- src/pages/ReportParticipantsPage.tsx | 2 +- src/pages/RoomMembersPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index 7873abb0258d..6c48af902589 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -230,7 +230,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { return header; } - return {header}; + return {header}; }, [styles, translate, isGroupChat, isCurrentUserAdmin, StyleUtils, canSelectMultiple]); const bulkActionsButtonOptions = useMemo(() => { diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 71f425080ed4..9fbcc8a09887 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -339,7 +339,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { confirmText={translate('common.remove')} cancelText={translate('common.cancel')} /> - + Date: Wed, 21 Aug 2024 14:08:55 +0200 Subject: [PATCH 071/502] add jsdoc --- src/components/AmountForm.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index c5f30243b5a7..3553ad2d6557 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -43,8 +43,10 @@ type AmountFormProps = { /** Custom max amount length. It defaults to CONST.IOU.AMOUNT_MAX_LENGTH */ amountMaxLength?: number; + /** Custom label for the TextInput */ label?: string; + /** Whether the form should use a standard TextInput as a base */ displayAsTextInput?: boolean; } & Pick & Pick; From 4290ca5521ae3801507b13aa2afcbab49f763bc9 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:31:22 +0100 Subject: [PATCH 072/502] Update condition for showing search input --- src/pages/ReportParticipantsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index 6c48af902589..39607dc0830a 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -71,7 +71,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) { useEffect(() => { const chatParticipants = ReportUtils.getParticipantsList(report, personalDetails); - const shouldShowInput = chatParticipants.length > CONST.SHOULD_SHOW_MEMBERS_SEARCH_INPUT_BREAKPOINT; + const shouldShowInput = chatParticipants.length >= CONST.SHOULD_SHOW_MEMBERS_SEARCH_INPUT_BREAKPOINT; if (shouldShowTextInput !== shouldShowInput) { setShouldShowTextInput(shouldShowInput); From 62f461537b7307bba72d6d1d850bff57be17a39e Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:31:44 +0100 Subject: [PATCH 073/502] Refactor --- src/ROUTES.ts | 2 +- src/SCREENS.ts | 10 +++-- src/languages/en.ts | 6 +-- src/languages/es.ts | 6 +-- .../ModalStackNavigators/index.tsx | 14 ++----- .../Navigators/RightModalNavigator.tsx | 8 ---- src/libs/Navigation/linkingConfig/config.ts | 14 ++----- src/libs/Navigation/types.ts | 16 ++------ src/pages/ReportParticipantDetailsPage.tsx | 4 +- ...ailsPage.tsx => RoomMemberDetailsPage.tsx} | 37 ++++++------------- src/pages/RoomMembersPage.tsx | 4 +- .../home/report/withReportOrNotFound.tsx | 2 +- .../members/WorkspaceMemberDetailsPage.tsx | 2 +- 13 files changed, 40 insertions(+), 85 deletions(-) rename src/pages/{RoomMembersDetailsPage.tsx => RoomMemberDetailsPage.tsx} (81%) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9b495095c9e3..fa4e15661e65 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -347,7 +347,7 @@ const ROUTES = { route: 'r/:reportID/members', getRoute: (reportID: string) => `r/${reportID}/members` as const, }, - ROOM_MEMBERS_DETAILS: { + ROOM_MEMBER_DETAILS: { route: 'r/:reportID/members/:accountID', getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/members/${accountID}` as const, }, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index c1450d5adee3..dac2619f5034 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -155,7 +155,7 @@ const SCREENS = { SIGN_IN: 'SignIn', PRIVATE_NOTES: 'Private_Notes', ROOM_MEMBERS: 'RoomMembers', - ROOM_MEMBERS_DETAILS: 'RoomMembers_Details', + ROOM_MEMBER_DETAILS: 'RoomMembers_Details', ROOM_INVITE: 'RoomInvite', REFERRAL: 'Referral', PROCESS_MONEY_REQUEST_HOLD: 'ProcessMoneyRequestHold', @@ -494,9 +494,11 @@ const SCREENS = { DETAILS: 'ReportParticipants_Details', ROLE: 'ReportParticipants_Role', }, - ROOM_MEMBERS_ROOT: 'RoomMembers_Root', - ROOM_INVITE_ROOT: 'RoomInvite_Root', - ROOM_MEMBERS_DETAILS_ROOT: 'RoomMembersDetails_Root', + ROOM_MEMBERS: { + ROOT: 'RoomMembers_Root', + INVITE: 'RoomInvite_Root', + DETAILS: 'RoomMembersDetails_Root', + }, FLAG_COMMENT_ROOT: 'FlagComment_Root', REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount', GET_ASSISTANCE: 'GetAssistance', diff --git a/src/languages/en.ts b/src/languages/en.ts index 458b57d02e4c..3ca310167a30 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3033,9 +3033,9 @@ export default { removeMembersWarningPrompt: ({memberName, ownerName}: RemoveMembersWarningPrompt) => `${memberName} is an approver in this workspace. When you unshare this workspace with them, we’ll replace them in the approval workflow with the workspace owner, ${ownerName}`, removeMembersTitle: 'Remove members', - removeMemberButtonTitle: 'Remove from workspace', - removeMemberGroupButtonTitle: 'Remove from group', - removeMemberRoomButtonTitle: 'Remove from room', + removeWorkspaceMemberButtonTitle: 'Remove from workspace', + removeGroupMemberButtonTitle: 'Remove from group', + removeRoomMemberButtonTitle: 'Remove from room', removeMemberPrompt: ({memberName}: {memberName: string}) => `Are you sure you want to remove ${memberName}?`, removeMemberTitle: 'Remove member', transferOwner: 'Transfer owner', diff --git a/src/languages/es.ts b/src/languages/es.ts index 24ba827835a3..8f269ff31d12 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3085,9 +3085,9 @@ export default { removeMembersWarningPrompt: ({memberName, ownerName}: RemoveMembersWarningPrompt) => `${memberName} es un aprobador en este espacio de trabajo. Cuando lo elimine de este espacio de trabajo, los sustituiremos en el flujo de trabajo de aprobación por el propietario del espacio de trabajo, ${ownerName}`, removeMembersTitle: 'Eliminar miembros', - removeMemberButtonTitle: 'Eliminar del espacio de trabajo', - removeMemberGroupButtonTitle: 'Eliminar del grupo', - removeMemberRoomButtonTitle: 'Eliminar de la sala', + removeWorkspaceMemberButtonTitle: 'Eliminar del espacio de trabajo', + removeGroupMemberButtonTitle: 'Eliminar del grupo', + removeRoomMemberButtonTitle: 'Eliminar de la sala', removeMemberPrompt: ({memberName}: {memberName: string}) => `¿Estás seguro de que deseas eliminar a ${memberName}?`, removeMemberTitle: 'Eliminar miembro', transferOwner: 'Transferir la propiedad', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index b31fd61588b0..ec6bf436c949 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -18,7 +18,6 @@ import type { ReportDescriptionNavigatorParamList, ReportDetailsNavigatorParamList, ReportSettingsNavigatorParamList, - RoomInviteNavigatorParamList, RoomMembersNavigatorParamList, SearchAdvancedFiltersParamList, SearchReportParamList, @@ -149,16 +148,11 @@ const ReportParticipantsModalStackNavigator = createModalStackNavigator({ - [SCREENS.ROOM_MEMBERS_ROOT]: () => require('../../../../pages/RoomMembersPage').default, + [SCREENS.ROOM_MEMBERS.ROOT]: () => require('../../../../pages/RoomMembersPage').default, + [SCREENS.ROOM_MEMBERS.INVITE]: () => require('../../../../pages/RoomInvitePage').default, + [SCREENS.ROOM_MEMBERS.DETAILS]: () => require('../../../../pages/RoomMemberDetailsPage').default, }); -const RoomInviteModalStackNavigator = createModalStackNavigator({ - [SCREENS.ROOM_INVITE_ROOT]: () => require('../../../../pages/RoomInvitePage').default, -}); - -const RoomMembersDetailsModalStackNavigator = createModalStackNavigator({ - [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: () => require('../../../../pages/RoomMembersDetailsPage').default, -}); const NewChatModalStackNavigator = createModalStackNavigator({ [SCREENS.NEW_CHAT.ROOT]: () => require('../../../../pages/NewChatSelectorPage').default, [SCREENS.NEW_CHAT.NEW_CHAT_CONFIRM]: () => require('../../../../pages/NewChatConfirmPage').default, @@ -555,9 +549,7 @@ export { ReportDetailsModalStackNavigator, ReportParticipantsModalStackNavigator, ReportSettingsModalStackNavigator, - RoomInviteModalStackNavigator, RoomMembersModalStackNavigator, - RoomMembersDetailsModalStackNavigator, SettingsModalStackNavigator, SignInModalStackNavigator, CategoriesModalStackNavigator, diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index ab33ab39624e..89542409a51e 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -107,14 +107,6 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.ROOM_MEMBERS} component={ModalStackNavigators.RoomMembersModalStackNavigator} /> - - ['config'] = { [SCREENS.REPORT_PARTICIPANTS.ROLE]: ROUTES.REPORT_PARTICIPANTS_ROLE_SELECTION.route, }, }, - [SCREENS.RIGHT_MODAL.ROOM_INVITE]: { - screens: { - [SCREENS.ROOM_INVITE_ROOT]: ROUTES.ROOM_INVITE.route, - }, - }, [SCREENS.RIGHT_MODAL.ROOM_MEMBERS]: { screens: { - [SCREENS.ROOM_MEMBERS_ROOT]: ROUTES.ROOM_MEMBERS.route, - }, - }, - [SCREENS.RIGHT_MODAL.ROOM_MEMBERS_DETAILS]: { - screens: { - [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: ROUTES.ROOM_MEMBERS_DETAILS.route, + [SCREENS.ROOM_MEMBERS.ROOT]: ROUTES.ROOM_MEMBERS.route, + [SCREENS.ROOM_MEMBERS.INVITE]: ROUTES.ROOM_INVITE.route, + [SCREENS.ROOM_MEMBERS.DETAILS]: ROUTES.ROOM_MEMBER_DETAILS.route, }, }, [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index dfd9dc21ec66..7223e1da8d3f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -767,18 +767,12 @@ type ParticipantsNavigatorParamList = { }; type RoomMembersNavigatorParamList = { - [SCREENS.ROOM_MEMBERS_ROOT]: undefined; -}; - -type RoomInviteNavigatorParamList = { - [SCREENS.ROOM_INVITE_ROOT]: { + [SCREENS.ROOM_MEMBERS.ROOT]: {reportID: string}; + [SCREENS.ROOM_MEMBERS.INVITE]: { reportID: string; role?: 'accountant'; }; -}; - -type RoomMembersDetailsNavigatorParamList = { - [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: { + [SCREENS.ROOM_MEMBERS.DETAILS]: { reportID: string; accountID: string; }; @@ -1086,8 +1080,6 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.REPORT_DESCRIPTION]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.PARTICIPANTS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.ROOM_MEMBERS]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.ROOM_MEMBERS_DETAILS]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.ROOM_INVITE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.NEW_TASK]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: NavigatorScreenParams; @@ -1373,9 +1365,7 @@ export type { ReportDetailsNavigatorParamList, ReportSettingsNavigatorParamList, RightModalNavigatorParamList, - RoomInviteNavigatorParamList, RoomMembersNavigatorParamList, - RoomMembersDetailsNavigatorParamList, RootStackParamList, SearchNavigatorParamList, SettingsNavigatorParamList, diff --git a/src/pages/ReportParticipantDetailsPage.tsx b/src/pages/ReportParticipantDetailsPage.tsx index f03b2475d5a7..79745c33dd3a 100644 --- a/src/pages/ReportParticipantDetailsPage.tsx +++ b/src/pages/ReportParticipantDetailsPage.tsx @@ -102,7 +102,7 @@ function ReportParticipantDetails({personalDetails, report, route}: ReportPartic {isCurrentUserAdmin && ( <>