From 29e9ddc227cdf9b9008f5034464d414a10275dfb Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 12 May 2025 01:27:06 +0200 Subject: [PATCH 01/34] add vacation delegate lang --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index b5adf3db2d2e..943dc9aa9113 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2216,6 +2216,8 @@ const translations = { time: 'Time', clearAfter: 'Clear after', whenClearStatus: 'When should we clear your status?', + vacationDelegate: 'Vacation delegate', + setVacationDelegate: `Set a vacation delegate to approve reports on your behalf while you're out of office.` }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Step ${step}`; diff --git a/src/languages/es.ts b/src/languages/es.ts index dc4cdc9fa59a..6f0662060be6 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2238,6 +2238,8 @@ const translations = { time: 'Hora', clearAfter: 'Borrar después', whenClearStatus: '¿Cuándo deberíamos borrar tu estado?', + vacationDelegate: 'Delegado de vacaciones', + setVacationDelegate: 'Configura un delegado de vacaciones para aprobar informes en tu nombre mientras estás fuera de la oficina.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Paso ${step}`; From 09df88dcae98c77c9ad92ce88a4ab0b700a91ae0 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 12 May 2025 01:52:02 +0200 Subject: [PATCH 02/34] add skeleton menu item for vacation delegate --- .../Profile/CustomStatus/StatusPage.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 7841ba16e377..061f6486f140 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -232,6 +232,25 @@ function StatusPage() { /> )} + + {translate('statusPage.setVacationDelegate')} + {translate('statusPage.vacationDelegate')} + {}} + containerStyle={styles.pr2} + /> + {/* title: personalDetail?.displayName ?? formattedEmail, + description: personalDetail?.displayName ? formattedEmail : '', + badgeText: translate('delegate.role', {role}), + avatarID: personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID, + icon: personalDetail?.avatar ?? FallbackAvatar, + iconType: CONST.ICON_TYPE_AVATAR, + numberOfLinesDescription: 1, + wrapperStyle: [styles.sectionMenuItemTopDescription], */} + ); From 57849463760fcf0363cd67b95c8ddff71382c34f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 12 May 2025 02:29:51 +0200 Subject: [PATCH 03/34] add vacation delegate to onyx --- src/ONYXKEYS.ts | 4 ++ .../Profile/CustomStatus/StatusPage.tsx | 47 ++++++++++++------- src/types/onyx/VacationDelegate.ts | 10 ++++ src/types/onyx/index.ts | 2 + 4 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 src/types/onyx/VacationDelegate.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b56ac862f1c8..8b823750841f 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -498,6 +498,9 @@ const ONYXKEYS = { /** Stores the information about the state of side panel */ NVP_SIDE_PANEL: 'nvp_sidePanel', + /** Information about vacation delegate */ + NVP_PRIVATE_VACATION_DELEGATE: 'nvp_private_vacationDelegate', + /** Information about loading states while talking with AI sales */ TALK_TO_AI_SALES: 'talkToAISales', @@ -1157,6 +1160,7 @@ type OnyxValuesMapping = { [ONYXKEYS.SHOULD_BILL_WHEN_DOWNGRADING]: boolean | undefined; [ONYXKEYS.BILLING_RECEIPT_DETAILS]: OnyxTypes.BillingReceiptDetails; [ONYXKEYS.NVP_SIDE_PANEL]: OnyxTypes.SidePanel; + [ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE]: OnyxTypes.VacationDelegate; [ONYXKEYS.SCHEDULE_CALL_DRAFT]: OnyxTypes.ScheduleCallDraft; [ONYXKEYS.IS_FORCED_TO_CHANGE_CURRENCY]: boolean | undefined; [ONYXKEYS.IS_COMING_FROM_GLOBAL_REIMBURSEMENTS_FLOW]: boolean | undefined; diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 061f6486f140..9318c60fd941 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -21,7 +21,9 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; +import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; +import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import {clearCustomStatus, clearDraftCustomStatus, updateCustomStatus, updateDraftCustomStatus} from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -40,6 +42,12 @@ function StatusPage() { const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const formRef = useRef(null); const [brickRoadIndicator, setBrickRoadIndicator] = useState>(); + + const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true}); + const hasVacationDelegate = !!vacationDelegate?.delegate; + const vacationDelegatePersonalDetails = getPersonalDetailByEmail(vacationDelegate?.delegate ?? ''); + const formattedDelegateLogin = formatPhoneNumber(vacationDelegatePersonalDetails?.login ?? ''); + const currentUserEmojiCode = currentUserPersonalDetails?.status?.emojiCode ?? ''; const currentUserStatusText = currentUserPersonalDetails?.status?.text ?? ''; const currentUserClearAfter = currentUserPersonalDetails?.status?.clearAfter ?? ''; @@ -234,22 +242,29 @@ function StatusPage() { {translate('statusPage.setVacationDelegate')} - {translate('statusPage.vacationDelegate')} - {}} - containerStyle={styles.pr2} - /> - {/* title: personalDetail?.displayName ?? formattedEmail, - description: personalDetail?.displayName ? formattedEmail : '', - badgeText: translate('delegate.role', {role}), - avatarID: personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID, - icon: personalDetail?.avatar ?? FallbackAvatar, - iconType: CONST.ICON_TYPE_AVATAR, - numberOfLinesDescription: 1, - wrapperStyle: [styles.sectionMenuItemTopDescription], */} + {hasVacationDelegate && {translate('statusPage.vacationDelegate')}} + {hasVacationDelegate ? ( + {}} + containerStyle={styles.pr2} + /> + ) : ( + + {}} + containerStyle={styles.pr2} + /> + + )} diff --git a/src/types/onyx/VacationDelegate.ts b/src/types/onyx/VacationDelegate.ts new file mode 100644 index 000000000000..cb48de2ee9dd --- /dev/null +++ b/src/types/onyx/VacationDelegate.ts @@ -0,0 +1,10 @@ +/** Information about vacation delegate */ +type VacationDelegate = { + /** Email of the user that set the vacation delegate */ + creator: string; + + /** Email of the vacation delegate */ + delegate: string; +}; + +export default VacationDelegate; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 031aa613cc86..632dba8867e3 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -111,6 +111,7 @@ import type TryNewDot from './TryNewDot'; import type UserLocation from './UserLocation'; import type UserMetadata from './UserMetadata'; import type UserWallet from './UserWallet'; +import type VacationDelegate from './VacationDelegate'; import type ValidateMagicCodeAction from './ValidateMagicCodeAction'; import type ValidateUserAndGetAccessiblePolicies from './ValidateUserAndGetAccessiblePolicies'; import type WalletAdditionalDetails from './WalletAdditionalDetails'; @@ -262,4 +263,5 @@ export type { ScheduleCallDraft, ValidateUserAndGetAccessiblePolicies, BillingReceiptDetails, + VacationDelegate, }; From d29ae4d33496be7b3fddffd0b9118fd1e7cfbbf7 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 12 May 2025 02:46:10 +0200 Subject: [PATCH 04/34] add vacation delegate navigation configs --- src/ROUTES.ts | 1 + src/SCREENS.ts | 1 + .../Navigation/AppNavigator/ModalStackNavigators/index.tsx | 1 + .../Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 4 ++++ src/libs/Navigation/types.ts | 1 + src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 4 ++-- .../settings/Profile/CustomStatus/VacationDelegatePage.tsx | 5 +++++ 8 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b313dc694a21..8cf770183837 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -338,6 +338,7 @@ const ROUTES = { SETTINGS_STATUS_CLEAR_AFTER: 'settings/profile/status/clear-after', SETTINGS_STATUS_CLEAR_AFTER_DATE: 'settings/profile/status/clear-after/date', SETTINGS_STATUS_CLEAR_AFTER_TIME: 'settings/profile/status/clear-after/time', + SETTINGS_VACATION_DELEGATE: 'settings/profile/status/vacation-delegate', SETTINGS_TROUBLESHOOT: 'settings/troubleshoot', SETTINGS_CONSOLE: { route: 'settings/troubleshoot/console', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index ca53350f69c7..2a6f14d067aa 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -98,6 +98,7 @@ const SCREENS = { STATUS_CLEAR_AFTER: 'Settings_Status_Clear_After', STATUS_CLEAR_AFTER_DATE: 'Settings_Status_Clear_After_Date', STATUS_CLEAR_AFTER_TIME: 'Settings_Status_Clear_After_Time', + VACATION_DELEGATE: 'Settings_Status_Vacation_Delegate', STATUS: 'Settings_Status', PRONOUNS: 'Settings_Pronouns', TIMEZONE: 'Settings_Timezone', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index bd13b29b1dd4..45d76948446c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -287,6 +287,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../../pages/settings/Profile/CustomStatus/SetDatePage').default, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: () => require('../../../../pages/settings/Profile/CustomStatus/SetTimePage').default, + [SCREENS.SETTINGS.PROFILE.VACATION_DELEGATE]: () => require('../../../../pages/settings/Profile/CustomStatus/VacationDelegatePage').default, [SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: () => require('../../../../pages/settings/Subscription/SubscriptionSize').default, [SCREENS.SETTINGS.SUBSCRIPTION.SETTINGS_DETAILS]: () => require('../../../../pages/settings/Subscription/SubscriptionSettings').default, [SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY]: () => require('../../../../pages/settings/Subscription/DisableAutoRenewSurveyPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts index f2945bf375c5..1f43e369e1e3 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts @@ -11,6 +11,7 @@ const SETTINGS_TO_RHP: Partial['config'] = { [SCREENS.SETTINGS.SUBSCRIPTION.SETTINGS_DETAILS]: { path: ROUTES.SETTINGS_SUBSCRIPTION_SETTINGS_DETAILS, }, + [SCREENS.SETTINGS.PROFILE.VACATION_DELEGATE]: { + path: ROUTES.SETTINGS_VACATION_DELEGATE, + exact: true, + }, [SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: { path: ROUTES.SETTINGS_SUBSCRIPTION_SIZE.route, parse: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c163aead379f..922f5715fdc0 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -161,6 +161,7 @@ type SettingsNavigatorParamList = { [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER]: undefined; [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: undefined; [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: undefined; + [SCREENS.SETTINGS.PROFILE.VACATION_DELEGATE]: undefined; [SCREENS.WORKSPACE.CURRENCY]: undefined; [SCREENS.WORKSPACE.ADDRESS]: { policyID: string; diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 9318c60fd941..e0e9b78737ef 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -252,7 +252,7 @@ function StatusPage() { iconType={CONST.ICON_TYPE_AVATAR} numberOfLinesDescription={1} shouldShowRightIcon - onPress={() => {}} + onPress={() => Navigation.navigate(ROUTES.SETTINGS_VACATION_DELEGATE)} containerStyle={styles.pr2} /> ) : ( @@ -260,7 +260,7 @@ function StatusPage() { {}} + onPress={() => Navigation.navigate(ROUTES.SETTINGS_VACATION_DELEGATE)} containerStyle={styles.pr2} /> diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx new file mode 100644 index 000000000000..fbc025cc3133 --- /dev/null +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -0,0 +1,5 @@ +function VacationDelegatePage() { + return null; +} + +export default VacationDelegatePage; From f9dbca9242b93271ad09c4a034c7279c5d32f337 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 12 May 2025 03:25:07 +0200 Subject: [PATCH 05/34] add vacation delegate API def --- src/languages/en.ts | 3 +- src/languages/es.ts | 1 + .../parameters/SetVacationDelegateParams.ts | 6 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 4 + src/libs/actions/VacationDelegate.ts | 112 ++++++++++++++++++ src/types/onyx/VacationDelegate.ts | 13 +- 7 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 src/libs/API/parameters/SetVacationDelegateParams.ts create mode 100644 src/libs/actions/VacationDelegate.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index 943dc9aa9113..a6bee18593a0 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2217,7 +2217,8 @@ const translations = { clearAfter: 'Clear after', whenClearStatus: 'When should we clear your status?', vacationDelegate: 'Vacation delegate', - setVacationDelegate: `Set a vacation delegate to approve reports on your behalf while you're out of office.` + setVacationDelegate: `Set a vacation delegate to approve reports on your behalf while you're out of office.`, + vacationDelegateError: 'There was an error updating your vacation delegate.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Step ${step}`; diff --git a/src/languages/es.ts b/src/languages/es.ts index 6f0662060be6..74658673666d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2240,6 +2240,7 @@ const translations = { whenClearStatus: '¿Cuándo deberíamos borrar tu estado?', vacationDelegate: 'Delegado de vacaciones', setVacationDelegate: 'Configura un delegado de vacaciones para aprobar informes en tu nombre mientras estás fuera de la oficina.', + vacationDelegateError: 'Hubo un error al actualizar tu delegado de vacaciones.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Paso ${step}`; diff --git a/src/libs/API/parameters/SetVacationDelegateParams.ts b/src/libs/API/parameters/SetVacationDelegateParams.ts new file mode 100644 index 000000000000..dcd25caaab74 --- /dev/null +++ b/src/libs/API/parameters/SetVacationDelegateParams.ts @@ -0,0 +1,6 @@ +type SetVacationDelegateParams = { + creator: string; + delegate: string; +}; + +export default SetVacationDelegateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 5db0f0d46287..0a19a7d3bf14 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -393,3 +393,4 @@ export type {default as GetEmphemeralTokenParams} from './GetEmphemeralTokenPara export type {default as CreateAppleDigitalWalletParams} from './CreateAppleDigitalWalletParams'; export type {default as CompleteConciergeCallParams} from './CompleteConciergeCallParams'; export type {default as FinishCorpayBankAccountOnboardingParams} from './FinishCorpayBankAccountOnboardingParams'; +export type {default as SetVacationDelegateParams} from './SetVacationDelegateParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index b4034bde5a13..21d70353180d 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -475,6 +475,8 @@ const WRITE_COMMANDS = { COMPLETE_CONCIERGE_CALL: 'CompleteConciergeCall', FINISH_CORPAY_BANK_ACCOUNT_ONBOARDING: 'FinishCorpayBankAccountOnboarding', GET_GUIDE_CALL_AVAILABILITY_SCHEDULE: 'GetGuideCallAvailabilitySchedule', + SET_VACATION_DELEGATE: 'SetVacationDelegate', + DELETE_VACATION_DELEGATE: 'DeleteVacationDelegate', } as const; type WriteCommand = ValueOf; @@ -818,6 +820,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_POLICY_PROHIBITED_EXPENSES]: Parameters.SetPolicyProhibitedExpensesParams; [WRITE_COMMANDS.COMPLETE_CONCIERGE_CALL]: Parameters.CompleteConciergeCallParams; [WRITE_COMMANDS.FINISH_CORPAY_BANK_ACCOUNT_ONBOARDING]: Parameters.FinishCorpayBankAccountOnboardingParams; + [WRITE_COMMANDS.SET_VACATION_DELEGATE]: Parameters.SetVacationDelegateParams; + [WRITE_COMMANDS.DELETE_VACATION_DELEGATE]: null; [WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH]: Parameters.DeleteMoneyRequestOnSearchParams; [WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.HoldMoneyRequestOnSearchParams; diff --git a/src/libs/actions/VacationDelegate.ts b/src/libs/actions/VacationDelegate.ts new file mode 100644 index 000000000000..4feda0ed3e6e --- /dev/null +++ b/src/libs/actions/VacationDelegate.ts @@ -0,0 +1,112 @@ +import Onyx from 'react-native-onyx'; +import type {OnyxUpdate} from 'react-native-onyx'; +import * as API from '@libs/API'; +import type {SetVacationDelegateParams} from '@libs/API/parameters'; +import {WRITE_COMMANDS} from '@libs/API/types'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {VacationDelegate} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +let vacationDelegate: VacationDelegate; +Onyx.connect({ + key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, + callback: (val) => { + vacationDelegate = val ?? {}; + }, +}); + +function SetVacationDelegate(creator: string, delegate: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, + value: { + creator, + delegate, + errors: null, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, + value: { + errors: null, + pendingAction: null, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, + value: { + creator: null, + delegate: null, + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('statusPage.vacationDelegateError'), + pendingAction: null, + }, + }, + ]; + + const parameters: SetVacationDelegateParams = { + creator, + delegate, + }; + + API.write(WRITE_COMMANDS.SET_VACATION_DELEGATE, parameters, {optimisticData, successData, failureData}); +} + +function DeleteVacationDelegate() { + if (isEmptyObject(vacationDelegate)) { + return; + } + + const {creator, delegate} = vacationDelegate; + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, + value: { + creator: null, + delegate: null, + errors: null, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, + value: { + errors: null, + pendingAction: null, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, + value: { + creator, + delegate, + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('statusPage.vacationDelegateError'), + pendingAction: null, + }, + }, + ]; + + API.write(WRITE_COMMANDS.DELETE_VACATION_DELEGATE, null, {optimisticData, successData, failureData}); +} + +export {SetVacationDelegate, DeleteVacationDelegate}; diff --git a/src/types/onyx/VacationDelegate.ts b/src/types/onyx/VacationDelegate.ts index cb48de2ee9dd..9b39ae5f31b1 100644 --- a/src/types/onyx/VacationDelegate.ts +++ b/src/types/onyx/VacationDelegate.ts @@ -1,10 +1,15 @@ +import type * as OnyxCommon from './OnyxCommon'; + /** Information about vacation delegate */ -type VacationDelegate = { +type VacationDelegate = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Email of the user that set the vacation delegate */ - creator: string; + creator?: string; /** Email of the vacation delegate */ - delegate: string; -}; + delegate?: string; + + /** Error message */ + errors?: OnyxCommon.Errors; +}>; export default VacationDelegate; From faf58b89780b279cd76dcb928d380a935c6e5316 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 12 May 2025 05:39:02 +0200 Subject: [PATCH 06/34] add delegate select page --- src/libs/actions/VacationDelegate.ts | 6 +- .../CustomStatus/VacationDelegatePage.tsx | 165 +++++++++++++++++- 2 files changed, 167 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/VacationDelegate.ts b/src/libs/actions/VacationDelegate.ts index 4feda0ed3e6e..edadc582a691 100644 --- a/src/libs/actions/VacationDelegate.ts +++ b/src/libs/actions/VacationDelegate.ts @@ -17,7 +17,7 @@ Onyx.connect({ }, }); -function SetVacationDelegate(creator: string, delegate: string) { +function setVacationDelegate(creator: string, delegate: string) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -63,7 +63,7 @@ function SetVacationDelegate(creator: string, delegate: string) { API.write(WRITE_COMMANDS.SET_VACATION_DELEGATE, parameters, {optimisticData, successData, failureData}); } -function DeleteVacationDelegate() { +function deleteVacationDelegate() { if (isEmptyObject(vacationDelegate)) { return; } @@ -109,4 +109,4 @@ function DeleteVacationDelegate() { API.write(WRITE_COMMANDS.DELETE_VACATION_DELEGATE, null, {optimisticData, successData, failureData}); } -export {SetVacationDelegate, DeleteVacationDelegate}; +export {setVacationDelegate, deleteVacationDelegate}; diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index fbc025cc3133..9fa7e61af6b1 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -1,5 +1,168 @@ +import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {useBetas} from '@components/OnyxProvider'; +import {useOptionsList} from '@components/OptionListContextProvider'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import UserListItem from '@components/SelectionList/UserListItem'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useDebouncedState from '@hooks/useDebouncedState'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ReportActions from '@libs/actions/Report'; +import {deleteVacationDelegate, setVacationDelegate} from '@libs/actions/VacationDelegate'; +import Navigation from '@libs/Navigation/Navigation'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; +import type {OptionData} from '@libs/ReportUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Participant} from '@src/types/onyx/IOU'; + +function useOptions() { + const betas = useBetas(); + const [isLoading, setIsLoading] = useState(true); + const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); + const {options: optionsList, areOptionsInitialized} = useOptionsList(); + const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true}); + const currentVacationDelegate = vacationDelegate?.delegate; + const [selectedOption, setSelectedOption] = useState(); + + const defaultOptions = useMemo(() => { + const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getValidOptions( + { + reports: optionsList.reports, + personalDetails: optionsList.personalDetails, + }, + { + betas, + excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT}, + }, + ); + + const headerMessage = OptionsListUtils.getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0, !!userToInvite, ''); + + if (isLoading) { + // eslint-disable-next-line react-compiler/react-compiler + setIsLoading(false); + } + + return { + userToInvite, + recentReports, + personalDetails, + currentUserOption, + headerMessage, + }; + }, [optionsList.reports, optionsList.personalDetails, betas, isLoading]); + + const options = useMemo(() => { + const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { + excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT}, + maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, + }); + const headerMessage = OptionsListUtils.getHeaderMessage( + (filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0, + !!filteredOptions.userToInvite, + debouncedSearchValue, + ); + + return { + ...filteredOptions, + headerMessage, + }; + }, [debouncedSearchValue, defaultOptions]); + + return {...options, currentVacationDelegate, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized}; +} + function VacationDelegatePage() { - return null; + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {login: currentUserLogin} = useCurrentUserPersonalDetails(); + + const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); + const {currentVacationDelegate, userToInvite, recentReports, personalDetails, searchValue, debouncedSearchValue, setSearchValue, headerMessage, areOptionsInitialized} = useOptions(); + // const delegateOption = personalDetails.filter((option) => option.login === currentVacationDelegate).at(0); + + const sections = useMemo(() => { + const sectionsList = []; + + sectionsList.push({ + title: translate('common.recents'), + data: recentReports, + shouldShow: recentReports?.length > 0, + }); + + sectionsList.push({ + title: translate('common.contacts'), + data: personalDetails, + shouldShow: personalDetails?.length > 0, + }); + + if (userToInvite) { + sectionsList.push({ + title: undefined, + data: [userToInvite], + shouldShow: true, + }); + } + + return sectionsList.map((section) => ({ + ...section, + data: section.data.map((option) => ({ + ...option, + text: option?.text ?? option?.displayName ?? '', + alternateText: option?.alternateText ?? option?.login ?? undefined, + keyForList: option.keyForList ?? '', + isDisabled: option.isDisabled ?? undefined, + login: option.login ?? undefined, + shouldShowSubscript: option.shouldShowSubscript ?? undefined, + })), + })); + }, [personalDetails, recentReports, translate, userToInvite]); + + const onSelectRow = useCallback( + (option: Participant) => { + setVacationDelegate(option?.login ?? '', currentUserLogin ?? ''); + Navigation.goBack(); + }, + [currentUserLogin], + ); + + useEffect(() => { + ReportActions.searchInServer(debouncedSearchValue); + }, [debouncedSearchValue]); + + return ( + + Navigation.goBack()} + /> + + + + + ); } +VacationDelegatePage.displayName = 'VacationDelegatePage'; + export default VacationDelegatePage; From cff7b6e879f5a891421c1d9f700a92ce42011cd4 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 09:12:54 +0200 Subject: [PATCH 07/34] add RBR and clear errors --- src/libs/UserUtils.ts | 11 +++++-- src/libs/actions/VacationDelegate.ts | 15 ++++++--- src/pages/settings/InitialSettingsPage.tsx | 4 ++- .../Profile/CustomStatus/StatusPage.tsx | 33 ++++++++++++------- .../CustomStatus/VacationDelegatePage.tsx | 11 +++++-- src/pages/settings/Profile/ProfilePage.tsx | 3 +- 6 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 93e0bc83e7cf..83d4b036b112 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -6,8 +6,9 @@ import * as defaultAvatars from '@components/Icon/DefaultAvatars'; import {ConciergeAvatar, NotificationsAvatar} from '@components/Icon/Expensicons'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Account, LoginList, PrivatePersonalDetails, Session} from '@src/types/onyx'; +import type {Account, LoginList, PrivatePersonalDetails, Session, VacationDelegate} from '@src/types/onyx'; import type Login from '@src/types/onyx/Login'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import hashCode from './hashCode'; @@ -97,9 +98,13 @@ function getLoginListBrickRoadIndicator(loginList: OnyxEntry): LoginL * Gets the appropriate brick road indicator status for the Profile section. * Error status is higher priority, so we check for that first. */ -function getProfilePageBrickRoadIndicator(loginList: OnyxEntry, privatePersonalDetails: OnyxEntry): LoginListIndicator { +function getProfilePageBrickRoadIndicator( + loginList: OnyxEntry, + privatePersonalDetails: OnyxEntry, + vacationDelegate: OnyxEntry, +): LoginListIndicator { const hasPhoneNumberError = !!privatePersonalDetails?.errorFields?.phoneNumber; - if (hasLoginListError(loginList) || hasPhoneNumberError) { + if (hasLoginListError(loginList) || hasPhoneNumberError || !isEmptyObject(vacationDelegate?.errors)) { return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } if (hasLoginListInfo(loginList)) { diff --git a/src/libs/actions/VacationDelegate.ts b/src/libs/actions/VacationDelegate.ts index edadc582a691..36942d39ae80 100644 --- a/src/libs/actions/VacationDelegate.ts +++ b/src/libs/actions/VacationDelegate.ts @@ -47,10 +47,7 @@ function setVacationDelegate(creator: string, delegate: string) { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, value: { - creator: null, - delegate: null, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('statusPage.vacationDelegateError'), - pendingAction: null, }, }, ]; @@ -101,7 +98,6 @@ function deleteVacationDelegate() { creator, delegate, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('statusPage.vacationDelegateError'), - pendingAction: null, }, }, ]; @@ -109,4 +105,13 @@ function deleteVacationDelegate() { API.write(WRITE_COMMANDS.DELETE_VACATION_DELEGATE, null, {optimisticData, successData, failureData}); } -export {setVacationDelegate, deleteVacationDelegate}; +function clearVacationDelegateError() { + Onyx.merge(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, { + errors: null, + pendingAction: null, + delegate: null, + creator: null, + }); +} + +export {setVacationDelegate, deleteVacationDelegate, clearVacationDelegateError}; diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index bf8614a4daac..ebc80a29a4ff 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -89,6 +89,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS, {canBeMissing: true}); const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true}); + const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true}); const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT, {canBeMissing: true}); const [allCards] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true}); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); @@ -150,7 +151,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr * @returns object with translationKey, style and items for the account section */ const accountMenuItemsData: Menu = useMemo(() => { - const profileBrickRoadIndicator = getProfilePageBrickRoadIndicator(loginList, privatePersonalDetails); + const profileBrickRoadIndicator = getProfilePageBrickRoadIndicator(loginList, privatePersonalDetails, vacationDelegate); const items: MenuData[] = [ { translationKey: 'common.profile', @@ -201,6 +202,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr }, [ loginList, privatePersonalDetails, + vacationDelegate, walletBrickRoadIndicator, hasActivatedWallet, userWallet?.currentBalance, diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 3f6b0ef74a5e..ebdb29215a0d 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -11,6 +11,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -21,11 +22,12 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; -import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import focusAfterModalClose from '@libs/focusAfterModalClose'; +import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import {clearCustomStatus, clearDraftCustomStatus, updateCustomStatus, updateDraftCustomStatus} from '@userActions/User'; +import {clearVacationDelegateError} from '@userActions/VacationDelegate'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -243,17 +245,24 @@ function StatusPage() { {translate('statusPage.setVacationDelegate')} {hasVacationDelegate && {translate('statusPage.vacationDelegate')}} {hasVacationDelegate ? ( - Navigation.navigate(ROUTES.SETTINGS_VACATION_DELEGATE)} - containerStyle={styles.pr2} - /> + + Navigation.navigate(ROUTES.SETTINGS_VACATION_DELEGATE)} + containerStyle={styles.pr2} + /> + ) : ( { - setVacationDelegate(option?.login ?? '', currentUserLogin ?? ''); + if (option?.login === currentVacationDelegate) { + deleteVacationDelegate(); + Navigation.goBack(); + return; + } + + setVacationDelegate(currentUserLogin ?? '', option?.login ?? ''); Navigation.goBack(); }, - [currentUserLogin], + [currentUserLogin, currentVacationDelegate], ); useEffect(() => { diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index d33bd317ac15..6523ec949e9a 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -66,7 +66,7 @@ function ProfilePage() { const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: false}); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); - + const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true}); const publicOptions = [ { description: translate('displayNamePage.headerTitle'), @@ -83,6 +83,7 @@ function ProfilePage() { description: translate('statusPage.status'), title: emojiCode ? `${emojiCode} ${currentUserPersonalDetails?.status?.text ?? ''}` : '', pageRoute: ROUTES.SETTINGS_STATUS, + brickRoadIndicator: isEmptyObject(vacationDelegate?.errors) ? undefined : CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, }, { description: translate('pronounsPage.pronouns'), From e5777ecee900d858b82e482e08ecae1d775ba976 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 10:21:23 +0200 Subject: [PATCH 08/34] add selected option --- .../CustomStatus/VacationDelegatePage.tsx | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index 2b1fd6f627bd..1cd2fb39607d 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -15,9 +15,12 @@ import * as ReportActions from '@libs/actions/Report'; import {deleteVacationDelegate, setVacationDelegate} from '@libs/actions/VacationDelegate'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import type {OptionData} from '@libs/ReportUtils'; +import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; +import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import * as Expensicons from '@components/Icon/Expensicons'; import type {Participant} from '@src/types/onyx/IOU'; function useOptions() { @@ -27,7 +30,13 @@ function useOptions() { const {options: optionsList, areOptionsInitialized} = useOptionsList(); const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true}); const currentVacationDelegate = vacationDelegate?.delegate; - const [selectedOption, setSelectedOption] = useState(); + const excludeLogins = useMemo( + () => ({ + ...CONST.EXPENSIFY_EMAILS_OBJECT, + [currentVacationDelegate ?? '']: true, + }), + [currentVacationDelegate], + ); const defaultOptions = useMemo(() => { const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getValidOptions( @@ -37,7 +46,7 @@ function useOptions() { }, { betas, - excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT}, + excludeLogins, }, ); @@ -55,11 +64,11 @@ function useOptions() { currentUserOption, headerMessage, }; - }, [optionsList.reports, optionsList.personalDetails, betas, isLoading]); + }, [optionsList.reports, optionsList.personalDetails, betas, excludeLogins, isLoading]); const options = useMemo(() => { const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { - excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT}, + excludeLogins, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); const headerMessage = OptionsListUtils.getHeaderMessage( @@ -72,7 +81,7 @@ function useOptions() { ...filteredOptions, headerMessage, }; - }, [debouncedSearchValue, defaultOptions]); + }, [debouncedSearchValue, defaultOptions, excludeLogins]); return {...options, currentVacationDelegate, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized}; } @@ -84,11 +93,38 @@ function VacationDelegatePage() { const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); const {currentVacationDelegate, userToInvite, recentReports, personalDetails, searchValue, debouncedSearchValue, setSearchValue, headerMessage, areOptionsInitialized} = useOptions(); - // const delegateOption = personalDetails.filter((option) => option.login === currentVacationDelegate).at(0); + const delegatePersonalDetails = getPersonalDetailByEmail(currentVacationDelegate ?? ''); const sections = useMemo(() => { const sectionsList = []; + if (currentVacationDelegate && delegatePersonalDetails) { + sectionsList.push({ + title: undefined, + data: [ + { + ...delegatePersonalDetails, + text: delegatePersonalDetails?.displayName ?? '', + alternateText: delegatePersonalDetails?.login ?? undefined, + login: delegatePersonalDetails.login ?? undefined, + keyForList: `vacationDelegate-${delegatePersonalDetails.login}`, + isDisabled: false, + isSelected: true, + shouldShowSubscript: undefined, + icons: [ + { + source: delegatePersonalDetails?.avatar ?? Expensicons.FallbackAvatar, + name: formatPhoneNumber(delegatePersonalDetails?.login ?? ''), + type: CONST.ICON_TYPE_AVATAR, + id: delegatePersonalDetails?.accountID, + }, + ], + }, + ], + shouldShow: true, + }); + } + sectionsList.push({ title: translate('common.recents'), data: recentReports, @@ -117,22 +153,23 @@ function VacationDelegatePage() { alternateText: option?.alternateText ?? option?.login ?? undefined, keyForList: option.keyForList ?? '', isDisabled: option.isDisabled ?? undefined, + isSelected: option.isSelected ?? undefined, login: option.login ?? undefined, shouldShowSubscript: option.shouldShowSubscript ?? undefined, })), })); - }, [personalDetails, recentReports, translate, userToInvite]); + }, [currentVacationDelegate, delegatePersonalDetails, personalDetails, recentReports, translate, userToInvite]); const onSelectRow = useCallback( (option: Participant) => { if (option?.login === currentVacationDelegate) { deleteVacationDelegate(); - Navigation.goBack(); + Navigation.goBack(ROUTES.SETTINGS_STATUS); return; } setVacationDelegate(currentUserLogin ?? '', option?.login ?? ''); - Navigation.goBack(); + Navigation.goBack(ROUTES.SETTINGS_STATUS); }, [currentUserLogin, currentVacationDelegate], ); From 6fb6f4dcebf1a247f54d26c20c5675de2d4d52f7 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 11:13:30 +0200 Subject: [PATCH 09/34] fix eslint --- .../CustomStatus/VacationDelegatePage.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index 1cd2fb39607d..4b85c9afd78e 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -2,6 +2,7 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; import {useBetas} from '@components/OnyxProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -11,16 +12,15 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActions from '@libs/actions/Report'; +import {searchInServer} from '@libs/actions/Report'; import {deleteVacationDelegate, setVacationDelegate} from '@libs/actions/VacationDelegate'; +import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; +import {filterAndOrderOptions, getHeaderMessage, getValidOptions} from '@libs/OptionsListUtils'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; -import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import * as Expensicons from '@components/Icon/Expensicons'; import type {Participant} from '@src/types/onyx/IOU'; function useOptions() { @@ -39,7 +39,7 @@ function useOptions() { ); const defaultOptions = useMemo(() => { - const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getValidOptions( + const {recentReports, personalDetails, userToInvite, currentUserOption} = getValidOptions( { reports: optionsList.reports, personalDetails: optionsList.personalDetails, @@ -50,7 +50,7 @@ function useOptions() { }, ); - const headerMessage = OptionsListUtils.getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0, !!userToInvite, ''); + const headerMessage = getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0, !!userToInvite, ''); if (isLoading) { // eslint-disable-next-line react-compiler/react-compiler @@ -67,11 +67,11 @@ function useOptions() { }, [optionsList.reports, optionsList.personalDetails, betas, excludeLogins, isLoading]); const options = useMemo(() => { - const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { + const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { excludeLogins, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); - const headerMessage = OptionsListUtils.getHeaderMessage( + const headerMessage = getHeaderMessage( (filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0, !!filteredOptions.userToInvite, debouncedSearchValue, @@ -175,7 +175,7 @@ function VacationDelegatePage() { ); useEffect(() => { - ReportActions.searchInServer(debouncedSearchValue); + searchInServer(debouncedSearchValue); }, [debouncedSearchValue]); return ( From 4f047fb98d81c8159fbbc32b90c572ef09dd6bfc Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 11:18:24 +0200 Subject: [PATCH 10/34] fix eslint --- .../settings/Profile/CustomStatus/VacationDelegatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index 4b85c9afd78e..2b104ad899b8 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -91,7 +91,7 @@ function VacationDelegatePage() { const styles = useThemeStyles(); const {login: currentUserLogin} = useCurrentUserPersonalDetails(); - const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); + const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: false}); const {currentVacationDelegate, userToInvite, recentReports, personalDetails, searchValue, debouncedSearchValue, setSearchValue, headerMessage, areOptionsInitialized} = useOptions(); const delegatePersonalDetails = getPersonalDetailByEmail(currentVacationDelegate ?? ''); From 9a354328cf400fdc26c15fbf8bbcb0c74a41a8d3 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 11:34:31 +0200 Subject: [PATCH 11/34] add all generated lang --- src/languages/de.ts | 3 +++ src/languages/fr.ts | 3 +++ src/languages/it.ts | 3 +++ src/languages/ja.ts | 3 +++ src/languages/nl.ts | 3 +++ src/languages/pl.ts | 3 +++ src/languages/pt-BR.ts | 3 +++ src/languages/zh-hans.ts | 3 +++ 8 files changed, 24 insertions(+) diff --git a/src/languages/de.ts b/src/languages/de.ts index 16c97f560633..fe5a8b6b4875 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2396,6 +2396,9 @@ const translations = { time: 'Zeit', clearAfter: 'Nach dem L\u00F6schen', whenClearStatus: 'Wann sollten wir Ihren Status l\u00F6schen?', + vacationDelegate: 'Urlaubsbeauftragter', + setVacationDelegate: 'Einen Urlaubsbeauftragten festlegen, um Berichte in Ihrem Namen zu genehmigen, w\u00E4hrend Sie nicht im B\u00FCro sind.', + vacationDelegateError: 'Beim Aktualisieren Ihres Urlaubsbeauftragten ist ein Fehler aufgetreten.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Schritt ${step}`; diff --git a/src/languages/fr.ts b/src/languages/fr.ts index d59978faeedf..b5485a0ce7cc 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2404,6 +2404,9 @@ const translations = { time: 'Temps', clearAfter: 'Effacer apr\u00E8s', whenClearStatus: 'Quand devrions-nous effacer votre statut ?', + vacationDelegate: 'D\u00E9l\u00E9gu\u00E9 de vacances', + setVacationDelegate: 'D\u00E9finir un d\u00E9l\u00E9gu\u00E9 de vacances pour approuver les rapports en votre nom pendant votre absence.', + vacationDelegateError: 'Une erreur s\'est produite lors de la mise \u00E0 jour de votre d\u00E9l\u00E9gu\u00E9 de vacances.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u00C9tape ${step}`; diff --git a/src/languages/it.ts b/src/languages/it.ts index 2d730f2384a1..471aa796dd63 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2371,6 +2371,9 @@ const translations = { time: 'Tempo', clearAfter: 'Cancella dopo', whenClearStatus: 'Quando dovremmo cancellare il tuo stato?', + vacationDelegate: 'Delegato per le vacanze', + setVacationDelegate: 'Imposta un delegato per le vacanze per approvare i report per tuo conto mentre sei fuori ufficio.', + vacationDelegateError: 'Si \u00E8 verificato un errore durante l\'aggiornamento del tuo delegato per le vacanze.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Passo ${step}`; diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 977749de540b..290d35cf7605 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2640,6 +2640,9 @@ const translations = { time: '\u6642\u9593', clearAfter: '\u5F8C\u3067\u30AF\u30EA\u30A2', whenClearStatus: '\u3044\u3064\u30B9\u30C6\u30FC\u30BF\u30B9\u3092\u30AF\u30EA\u30A2\u3059\u3079\u304D\u3067\u3059\u304B\uFF1F', + vacationDelegate: '\u30D9\u30C3\u30C0\u30FC\u30B0\u30EA\u30D1\u30C3\u30AF', + setVacationDelegate: `\u30D9\u30C3\u30C0\u30FC\u30B0\u30EA\u30D1\u30C3\u30AF\u3092\u8A2D\u5B9A\u3057\u3066\u3001\u304A\u4E0A\u306B\u51FA\u3057\u3066\u3044\u308B\u9593\u306BExpensify \u306E \u5834\u5408 \u306B \u5834 \u3067 \u652F \u6255 \u3044 \u3092 \u8A31 \u53EF \u3057 \u307E \u3059 \u3002`, + vacationDelegateError: '\u30D9\u30C3\u30C0\u30FC\u30B0\u30EA\u30D1\u30C3\u30AF\u3092\u66F4\u65B0\u3059\u308B\u306B\u306F\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u30B9\u30C6\u30C3\u30D7${step}`; diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 1fadda63c78a..0d26b6523f0b 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2368,6 +2368,9 @@ const translations = { time: 'Tijd', clearAfter: 'Wissen na', whenClearStatus: 'Wanneer moeten we je status wissen?', + vacationDelegate: 'Vakantievertegenwoordiger', + vacationDelegateError: 'Er is een fout opgetreden bij het bijwerken van uw vakantievertegenwoordiger.', + setVacationDelegate: 'Stel een vakantievertegenwoordiger in om rapporten namens u goed te keuren terwijl u afwezig bent.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Stap ${step}`; diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 87fc0c62749e..56c54a17aacd 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2417,6 +2417,9 @@ const translations = { time: 'Czas', clearAfter: 'Wyczy\u015B\u0107 po', whenClearStatus: 'Kiedy powinni\u015Bmy wyczy\u015Bci\u0107 Tw\u00F3j status?', + vacationDelegate: 'Delegat urlopowy', + vacationDelegateError: 'Wyst\u0105pi\u0142 b\u0142\u0105d podczas aktualizacji Twojego delegata urlopowego.', + setVacationDelegate: 'Ustaw delegata urlopowego, aby zatwierdza\u0107 raporty w Twoim imieniu podczas Twojej nieobecno\u015Bci.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Krok ${step}`; diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index e5d7af7a534f..78e15f5e8a25 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2381,6 +2381,9 @@ const translations = { time: 'Tempo', clearAfter: 'Limpar ap\u00F3s', whenClearStatus: 'Quando devemos limpar seu status?', + vacationDelegate: 'Delegado de f\u00E9rias', + vacationDelegateError: 'Ocorreu um erro ao atualizar seu delegado de f\u00E9rias.', + setVacationDelegate: 'Defina um delegado de f\u00E9rias para aprovar relat\u00F3rios em seu nome enquanto voc\u00EA estiver fora do escritório.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Etapa ${step}`; diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 5b5575466572..a9d58e2eb0d2 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2461,6 +2461,9 @@ const translations = { time: '\u65F6\u95F4', clearAfter: '\u6E05\u9664\u540E', whenClearStatus: '\u6211\u4EEC\u5E94\u8BE5\u4F55\u65F6\u6E05\u9664\u60A8\u7684\u72B6\u6001\uFF1F', + vacationDelegate: `\u5F85\u5047\u4EE3\u7406`, + setVacationDelegate: `\u8BBE\u7F6E\u4E00\u4E2A\u5047\u671F\u4EE3\u7406\uFF0C\u5728\u60A8\u79BB\u5F00\u804A\u5929\u65F6\u5B9E\u65F6\u5BA1\u6279\u62A5\u544A\u3002`, + vacationDelegateError: '\u8BBE\u7F6E\u5047\u671F\u4EE3\u7406\u65F6\u53D1\u751F\u9519\u8BEF\u3002', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u6B65\u9AA4 ${step}`; From 81fc6489d61cd2bd91c14f3194a7180023a6ebbc Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 11:36:52 +0200 Subject: [PATCH 12/34] run pretty --- src/languages/fr.ts | 2 +- src/languages/it.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/fr.ts b/src/languages/fr.ts index b5485a0ce7cc..40c5a03cb221 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2406,7 +2406,7 @@ const translations = { whenClearStatus: 'Quand devrions-nous effacer votre statut ?', vacationDelegate: 'D\u00E9l\u00E9gu\u00E9 de vacances', setVacationDelegate: 'D\u00E9finir un d\u00E9l\u00E9gu\u00E9 de vacances pour approuver les rapports en votre nom pendant votre absence.', - vacationDelegateError: 'Une erreur s\'est produite lors de la mise \u00E0 jour de votre d\u00E9l\u00E9gu\u00E9 de vacances.', + vacationDelegateError: "Une erreur s'est produite lors de la mise \u00E0 jour de votre d\u00E9l\u00E9gu\u00E9 de vacances.", }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u00C9tape ${step}`; diff --git a/src/languages/it.ts b/src/languages/it.ts index 471aa796dd63..05556db0db8c 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2373,7 +2373,7 @@ const translations = { whenClearStatus: 'Quando dovremmo cancellare il tuo stato?', vacationDelegate: 'Delegato per le vacanze', setVacationDelegate: 'Imposta un delegato per le vacanze per approvare i report per tuo conto mentre sei fuori ufficio.', - vacationDelegateError: 'Si \u00E8 verificato un errore durante l\'aggiornamento del tuo delegato per le vacanze.', + vacationDelegateError: "Si \u00E8 verificato un errore durante l'aggiornamento del tuo delegato per le vacanze.", }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Passo ${step}`; From f4b88cf361221f3f1e27ec4406330abc6282bbd6 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 11:54:35 +0200 Subject: [PATCH 13/34] show vacation delegate in report --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/languages/params.ts | 3 +++ src/pages/home/report/ReportActionItemSingle.tsx | 5 +++++ src/types/onyx/ReportAction.ts | 3 +++ 5 files changed, 15 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index debed916f7cc..6419dbc93372 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -231,6 +231,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2345,6 +2346,7 @@ const translations = { vacationDelegate: 'Vacation delegate', setVacationDelegate: `Set a vacation delegate to approve reports on your behalf while you're out of office.`, vacationDelegateError: 'There was an error updating your vacation delegate.', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `as ${managerName}'s vacation delegate`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Step ${step}`; diff --git a/src/languages/es.ts b/src/languages/es.ts index 37f20f1c29c9..6c1aa22491fa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -230,6 +230,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2365,6 +2366,7 @@ const translations = { vacationDelegate: 'Delegado de vacaciones', setVacationDelegate: 'Configura un delegado de vacaciones para aprobar informes en tu nombre mientras estás fuera de la oficina.', vacationDelegateError: 'Hubo un error al actualizar tu delegado de vacaciones.', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `como delegado de vacaciones de ${managerName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Paso ${step}`; diff --git a/src/languages/params.ts b/src/languages/params.ts index fc2a8e4e9e79..0e88eb19c849 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -555,6 +555,8 @@ type DelegateRoleParams = {role: DelegateRole}; type DelegatorParams = {delegator: string}; +type VacationDelegateParams = {managerName: string}; + type RoleNamesParams = {role: string}; type AssignCardParams = { @@ -790,6 +792,7 @@ export type { SpreadCategoriesParams, DelegateRoleParams, DelegatorParams, + VacationDelegateParams, ReconciliationWorksParams, LastSyncAccountingParams, SyncStageNameConnectionsParams, diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 0b6ca963fe37..7896a5824a16 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -123,6 +123,8 @@ function ReportActionItemSingle({ let displayName = getDisplayNameForParticipant({accountID: actorAccountID, personalDetailsData: personalDetails}); const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails?.[actorAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? {}; const accountOwnerDetails = getPersonalDetailByEmail(login ?? ''); + const vacationDelegateDetails = getPersonalDetailByEmail(action?.managerOnVacation ?? ''); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing let actorHint = (login || (displayName ?? '')).replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); const isTripRoom = isTripRoomReportUtils(report); @@ -342,6 +344,9 @@ function ReportActionItemSingle({ {translate('delegate.onBehalfOfMessage', {delegator: accountOwnerDetails?.displayName ?? ''})} )} {children} + {!!action?.managerOnVacation && ( + {translate('statusPage.asVacationDelegate', {managerName: vacationDelegateDetails?.displayName ?? ''})} + )} ); diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 5667284fdba1..5851cfa920ff 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -231,6 +231,9 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The accountID of the copilot who took this action on behalf of the user */ delegateAccountID?: number; + /** The login of the user on vacation who the action was taken on behalf of */ + managerOnVacation?: string; + /** Server side errors keyed by microtime */ errors?: OnyxCommon.Errors | OnyxCommon.ErrorFields; From b5defa3e870cb9020219c0df8377bbd6a0d03011 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 12:07:29 +0200 Subject: [PATCH 14/34] fix type error --- src/pages/home/report/ReportActionsListItemRenderer.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx index 971fcd1c93ed..e4b8e18cd317 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.tsx +++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx @@ -104,6 +104,7 @@ function ReportActionsListItemRenderer({ person: reportAction.person, isOptimisticAction: reportAction.isOptimisticAction, delegateAccountID: reportAction.delegateAccountID, + managerOnVacation: reportAction.managerOnVacation, previousMessage: reportAction.previousMessage, isAttachmentWithText: reportAction.isAttachmentWithText, childStateNum: reportAction.childStateNum, @@ -133,6 +134,7 @@ function ReportActionsListItemRenderer({ reportAction.person, reportAction.isOptimisticAction, reportAction.delegateAccountID, + reportAction.managerOnVacation, reportAction.previousMessage, reportAction.isAttachmentWithText, reportAction.childStateNum, From dc7bb3324c4f29c6c46792548bf9fa3d6747fc80 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 12:20:07 +0200 Subject: [PATCH 15/34] use correct name for api --- src/libs/API/parameters/SetVacationDelegateParams.ts | 2 +- src/libs/actions/VacationDelegate.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/API/parameters/SetVacationDelegateParams.ts b/src/libs/API/parameters/SetVacationDelegateParams.ts index dcd25caaab74..5aa68289b1dc 100644 --- a/src/libs/API/parameters/SetVacationDelegateParams.ts +++ b/src/libs/API/parameters/SetVacationDelegateParams.ts @@ -1,6 +1,6 @@ type SetVacationDelegateParams = { creator: string; - delegate: string; + vacationDelegateEmail: string; }; export default SetVacationDelegateParams; diff --git a/src/libs/actions/VacationDelegate.ts b/src/libs/actions/VacationDelegate.ts index 36942d39ae80..3c9900417721 100644 --- a/src/libs/actions/VacationDelegate.ts +++ b/src/libs/actions/VacationDelegate.ts @@ -54,7 +54,7 @@ function setVacationDelegate(creator: string, delegate: string) { const parameters: SetVacationDelegateParams = { creator, - delegate, + vacationDelegateEmail: delegate, }; API.write(WRITE_COMMANDS.SET_VACATION_DELEGATE, parameters, {optimisticData, successData, failureData}); From 4eb14345c6c27acde7c38100fed1324bc80a3c78 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 12:21:08 +0200 Subject: [PATCH 16/34] Revert "add all generated lang" This reverts commit 9a354328cf400fdc26c15fbf8bbcb0c74a41a8d3. --- src/languages/de.ts | 3 --- src/languages/fr.ts | 3 --- src/languages/it.ts | 3 --- src/languages/ja.ts | 3 --- src/languages/nl.ts | 3 --- src/languages/pl.ts | 3 --- src/languages/pt-BR.ts | 3 --- src/languages/zh-hans.ts | 3 --- 8 files changed, 24 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index fe5a8b6b4875..16c97f560633 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2396,9 +2396,6 @@ const translations = { time: 'Zeit', clearAfter: 'Nach dem L\u00F6schen', whenClearStatus: 'Wann sollten wir Ihren Status l\u00F6schen?', - vacationDelegate: 'Urlaubsbeauftragter', - setVacationDelegate: 'Einen Urlaubsbeauftragten festlegen, um Berichte in Ihrem Namen zu genehmigen, w\u00E4hrend Sie nicht im B\u00FCro sind.', - vacationDelegateError: 'Beim Aktualisieren Ihres Urlaubsbeauftragten ist ein Fehler aufgetreten.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Schritt ${step}`; diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 40c5a03cb221..d59978faeedf 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2404,9 +2404,6 @@ const translations = { time: 'Temps', clearAfter: 'Effacer apr\u00E8s', whenClearStatus: 'Quand devrions-nous effacer votre statut ?', - vacationDelegate: 'D\u00E9l\u00E9gu\u00E9 de vacances', - setVacationDelegate: 'D\u00E9finir un d\u00E9l\u00E9gu\u00E9 de vacances pour approuver les rapports en votre nom pendant votre absence.', - vacationDelegateError: "Une erreur s'est produite lors de la mise \u00E0 jour de votre d\u00E9l\u00E9gu\u00E9 de vacances.", }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u00C9tape ${step}`; diff --git a/src/languages/it.ts b/src/languages/it.ts index 05556db0db8c..2d730f2384a1 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2371,9 +2371,6 @@ const translations = { time: 'Tempo', clearAfter: 'Cancella dopo', whenClearStatus: 'Quando dovremmo cancellare il tuo stato?', - vacationDelegate: 'Delegato per le vacanze', - setVacationDelegate: 'Imposta un delegato per le vacanze per approvare i report per tuo conto mentre sei fuori ufficio.', - vacationDelegateError: "Si \u00E8 verificato un errore durante l'aggiornamento del tuo delegato per le vacanze.", }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Passo ${step}`; diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 290d35cf7605..977749de540b 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2640,9 +2640,6 @@ const translations = { time: '\u6642\u9593', clearAfter: '\u5F8C\u3067\u30AF\u30EA\u30A2', whenClearStatus: '\u3044\u3064\u30B9\u30C6\u30FC\u30BF\u30B9\u3092\u30AF\u30EA\u30A2\u3059\u3079\u304D\u3067\u3059\u304B\uFF1F', - vacationDelegate: '\u30D9\u30C3\u30C0\u30FC\u30B0\u30EA\u30D1\u30C3\u30AF', - setVacationDelegate: `\u30D9\u30C3\u30C0\u30FC\u30B0\u30EA\u30D1\u30C3\u30AF\u3092\u8A2D\u5B9A\u3057\u3066\u3001\u304A\u4E0A\u306B\u51FA\u3057\u3066\u3044\u308B\u9593\u306BExpensify \u306E \u5834\u5408 \u306B \u5834 \u3067 \u652F \u6255 \u3044 \u3092 \u8A31 \u53EF \u3057 \u307E \u3059 \u3002`, - vacationDelegateError: '\u30D9\u30C3\u30C0\u30FC\u30B0\u30EA\u30D1\u30C3\u30AF\u3092\u66F4\u65B0\u3059\u308B\u306B\u306F\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u30B9\u30C6\u30C3\u30D7${step}`; diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 0d26b6523f0b..1fadda63c78a 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2368,9 +2368,6 @@ const translations = { time: 'Tijd', clearAfter: 'Wissen na', whenClearStatus: 'Wanneer moeten we je status wissen?', - vacationDelegate: 'Vakantievertegenwoordiger', - vacationDelegateError: 'Er is een fout opgetreden bij het bijwerken van uw vakantievertegenwoordiger.', - setVacationDelegate: 'Stel een vakantievertegenwoordiger in om rapporten namens u goed te keuren terwijl u afwezig bent.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Stap ${step}`; diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 56c54a17aacd..87fc0c62749e 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2417,9 +2417,6 @@ const translations = { time: 'Czas', clearAfter: 'Wyczy\u015B\u0107 po', whenClearStatus: 'Kiedy powinni\u015Bmy wyczy\u015Bci\u0107 Tw\u00F3j status?', - vacationDelegate: 'Delegat urlopowy', - vacationDelegateError: 'Wyst\u0105pi\u0142 b\u0142\u0105d podczas aktualizacji Twojego delegata urlopowego.', - setVacationDelegate: 'Ustaw delegata urlopowego, aby zatwierdza\u0107 raporty w Twoim imieniu podczas Twojej nieobecno\u015Bci.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Krok ${step}`; diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 78e15f5e8a25..e5d7af7a534f 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2381,9 +2381,6 @@ const translations = { time: 'Tempo', clearAfter: 'Limpar ap\u00F3s', whenClearStatus: 'Quando devemos limpar seu status?', - vacationDelegate: 'Delegado de f\u00E9rias', - vacationDelegateError: 'Ocorreu um erro ao atualizar seu delegado de f\u00E9rias.', - setVacationDelegate: 'Defina um delegado de f\u00E9rias para aprovar relat\u00F3rios em seu nome enquanto voc\u00EA estiver fora do escritório.', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Etapa ${step}`; diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index a9d58e2eb0d2..5b5575466572 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2461,9 +2461,6 @@ const translations = { time: '\u65F6\u95F4', clearAfter: '\u6E05\u9664\u540E', whenClearStatus: '\u6211\u4EEC\u5E94\u8BE5\u4F55\u65F6\u6E05\u9664\u60A8\u7684\u72B6\u6001\uFF1F', - vacationDelegate: `\u5F85\u5047\u4EE3\u7406`, - setVacationDelegate: `\u8BBE\u7F6E\u4E00\u4E2A\u5047\u671F\u4EE3\u7406\uFF0C\u5728\u60A8\u79BB\u5F00\u804A\u5929\u65F6\u5B9E\u65F6\u5BA1\u6279\u62A5\u544A\u3002`, - vacationDelegateError: '\u8BBE\u7F6E\u5047\u671F\u4EE3\u7406\u65F6\u53D1\u751F\u9519\u8BEF\u3002', }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u6B65\u9AA4 ${step}`; From 6a25c2bf2fca7d80f17aa192bab00d5351b24760 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 12:53:32 +0200 Subject: [PATCH 17/34] add manageronvacation to debugutils --- src/libs/DebugUtils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index cb11bfa51340..4b80aaec2323 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -693,6 +693,8 @@ function validateReportActionDraftProperty(key: keyof ReportAction, value: strin case 'adminAccountID': case 'delegateAccountID': return validateNumber(value); + case 'managerOnVacation': + return validateString(value); case 'isLoading': case 'automatic': case 'shouldShow': @@ -746,6 +748,7 @@ function validateReportActionDraftProperty(key: keyof ReportAction, value: strin receipt: CONST.RED_BRICK_ROAD_PENDING_ACTION, lastModified: CONST.RED_BRICK_ROAD_PENDING_ACTION, delegateAccountID: CONST.RED_BRICK_ROAD_PENDING_ACTION, + managerOnVacation: CONST.RED_BRICK_ROAD_PENDING_ACTION, error: CONST.RED_BRICK_ROAD_PENDING_ACTION, childRecentReceiptTransactionIDs: CONST.RED_BRICK_ROAD_PENDING_ACTION, linkMetadata: CONST.RED_BRICK_ROAD_PENDING_ACTION, @@ -1218,6 +1221,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string) isAttachmentWithText: 'boolean', lastModified: 'string', delegateAccountID: 'number', + managerOnVacation: 'string', childRecentReceiptTransactionIDs: 'object', linkMetadata: 'array', childReportNotificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE, From 4db489b555a50ff442b6bc78a9b5416421471668 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Jun 2025 19:14:31 +0200 Subject: [PATCH 18/34] override policy diff warning --- src/libs/API/parameters/SetVacationDelegateParams.ts | 1 + src/libs/actions/VacationDelegate.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/libs/API/parameters/SetVacationDelegateParams.ts b/src/libs/API/parameters/SetVacationDelegateParams.ts index 5aa68289b1dc..8e33e6b1a13b 100644 --- a/src/libs/API/parameters/SetVacationDelegateParams.ts +++ b/src/libs/API/parameters/SetVacationDelegateParams.ts @@ -1,6 +1,7 @@ type SetVacationDelegateParams = { creator: string; vacationDelegateEmail: string; + overridePolicyDiffWarning: boolean; }; export default SetVacationDelegateParams; diff --git a/src/libs/actions/VacationDelegate.ts b/src/libs/actions/VacationDelegate.ts index 3c9900417721..4e994e84eb45 100644 --- a/src/libs/actions/VacationDelegate.ts +++ b/src/libs/actions/VacationDelegate.ts @@ -55,6 +55,7 @@ function setVacationDelegate(creator: string, delegate: string) { const parameters: SetVacationDelegateParams = { creator, vacationDelegateEmail: delegate, + overridePolicyDiffWarning: true, }; API.write(WRITE_COMMANDS.SET_VACATION_DELEGATE, parameters, {optimisticData, successData, failureData}); From 4be55ed8adbdaf205f2475670cf3febe7e09eaab Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 17 Jun 2025 00:08:09 +0200 Subject: [PATCH 19/34] use managerOnVacation from OriginalMessage --- src/libs/DebugUtils.ts | 4 ---- src/libs/ReportActionsUtils.ts | 9 +++++++++ src/pages/home/report/ReportActionItemSingle.tsx | 14 +++++++++----- .../home/report/ReportActionsListItemRenderer.tsx | 2 -- src/types/onyx/OriginalMessage.ts | 3 +++ src/types/onyx/ReportAction.ts | 3 --- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 4b80aaec2323..cb11bfa51340 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -693,8 +693,6 @@ function validateReportActionDraftProperty(key: keyof ReportAction, value: strin case 'adminAccountID': case 'delegateAccountID': return validateNumber(value); - case 'managerOnVacation': - return validateString(value); case 'isLoading': case 'automatic': case 'shouldShow': @@ -748,7 +746,6 @@ function validateReportActionDraftProperty(key: keyof ReportAction, value: strin receipt: CONST.RED_BRICK_ROAD_PENDING_ACTION, lastModified: CONST.RED_BRICK_ROAD_PENDING_ACTION, delegateAccountID: CONST.RED_BRICK_ROAD_PENDING_ACTION, - managerOnVacation: CONST.RED_BRICK_ROAD_PENDING_ACTION, error: CONST.RED_BRICK_ROAD_PENDING_ACTION, childRecentReceiptTransactionIDs: CONST.RED_BRICK_ROAD_PENDING_ACTION, linkMetadata: CONST.RED_BRICK_ROAD_PENDING_ACTION, @@ -1221,7 +1218,6 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string) isAttachmentWithText: 'boolean', lastModified: 'string', delegateAccountID: 'number', - managerOnVacation: 'string', childRecentReceiptTransactionIDs: 'object', linkMetadata: 'array', childReportNotificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE, diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 5e30863d1443..24ffcc3bb850 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -2923,6 +2923,14 @@ function getIntegrationSyncFailedMessage(action: OnyxEntry): strin return translateLocal('report.actions.type.integrationSyncFailed', {label, errorMessage}); } +function getManagerOnVacation(action: OnyxEntry): string | undefined { + if (!isApprovedAction(action)) { + return; + } + + return getOriginalMessage(action)?.managerOnVacation; +} + export { doesReportHaveVisibleActions, extractLinksFromMessageHtml, @@ -3087,6 +3095,7 @@ export { getReportActionFromExpensifyCard, isReopenedAction, getIntegrationSyncFailedMessage, + getManagerOnVacation, }; export type {LastVisibleMessage}; diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 7896a5824a16..bcd8b7a36637 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -21,7 +21,7 @@ import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; -import {getReportActionMessage} from '@libs/ReportActionsUtils'; +import {getManagerOnVacation, getReportActionMessage} from '@libs/ReportActionsUtils'; import { getDefaultWorkspaceAvatar, getDisplayNameForParticipant, @@ -123,7 +123,8 @@ function ReportActionItemSingle({ let displayName = getDisplayNameForParticipant({accountID: actorAccountID, personalDetailsData: personalDetails}); const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails?.[actorAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? {}; const accountOwnerDetails = getPersonalDetailByEmail(login ?? ''); - const vacationDelegateDetails = getPersonalDetailByEmail(action?.managerOnVacation ?? ''); + const managerOnVacation = getManagerOnVacation(action); + const vacationDelegateDetails = getPersonalDetailByEmail(managerOnVacation ?? ''); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing let actorHint = (login || (displayName ?? '')).replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); @@ -286,6 +287,7 @@ function ReportActionItemSingle({ ); }; + const hasEmojiStatus = !displayAllActors && status?.emojiCode; const formattedDate = DateUtils.getStatusUntilDate(status?.clearAfter ?? ''); const statusText = status?.text ?? ''; @@ -343,10 +345,12 @@ function ReportActionItemSingle({ {!!action?.delegateAccountID && ( {translate('delegate.onBehalfOfMessage', {delegator: accountOwnerDetails?.displayName ?? ''})} )} - {children} - {!!action?.managerOnVacation && ( - {translate('statusPage.asVacationDelegate', {managerName: vacationDelegateDetails?.displayName ?? ''})} + {!!managerOnVacation && ( + + {translate('statusPage.asVacationDelegate', {managerName: vacationDelegateDetails?.displayName ?? managerOnVacation ?? ''})} + )} + {children} ); diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx index e4b8e18cd317..971fcd1c93ed 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.tsx +++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx @@ -104,7 +104,6 @@ function ReportActionsListItemRenderer({ person: reportAction.person, isOptimisticAction: reportAction.isOptimisticAction, delegateAccountID: reportAction.delegateAccountID, - managerOnVacation: reportAction.managerOnVacation, previousMessage: reportAction.previousMessage, isAttachmentWithText: reportAction.isAttachmentWithText, childStateNum: reportAction.childStateNum, @@ -134,7 +133,6 @@ function ReportActionsListItemRenderer({ reportAction.person, reportAction.isOptimisticAction, reportAction.delegateAccountID, - reportAction.managerOnVacation, reportAction.previousMessage, reportAction.isAttachmentWithText, reportAction.childStateNum, diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 19a8a014e515..7cf6546c8793 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -688,6 +688,9 @@ type OriginalMessageApproved = { /** Report ID of the expense */ expenseReportID: string; + + /** The login of approver who is on vacation */ + managerOnVacation?: string; }; /** Model of `forwarded` report action */ diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 5851cfa920ff..5667284fdba1 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -231,9 +231,6 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The accountID of the copilot who took this action on behalf of the user */ delegateAccountID?: number; - /** The login of the user on vacation who the action was taken on behalf of */ - managerOnVacation?: string; - /** Server side errors keyed by microtime */ errors?: OnyxCommon.Errors | OnyxCommon.ErrorFields; From 18082b72e97104325b5031b4aa16dcd6b4944ad5 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 17 Jun 2025 00:49:21 +0200 Subject: [PATCH 20/34] add vacation delegate for submitted action --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/languages/params.ts | 3 +++ src/libs/ReportActionsUtils.ts | 18 +++++++++++++++ .../home/report/ReportActionItemSingle.tsx | 22 ++++++++++++++++--- src/types/onyx/OriginalMessage.ts | 6 +++++ 6 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 6419dbc93372..1d0098b55245 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -180,6 +180,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -2347,6 +2348,7 @@ const translations = { setVacationDelegate: `Set a vacation delegate to approve reports on your behalf while you're out of office.`, vacationDelegateError: 'There was an error updating your vacation delegate.', asVacationDelegate: ({managerName}: VacationDelegateParams) => `as ${managerName}'s vacation delegate`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `to ${submittedToName} as vacation delegate for ${vacationDelegateName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Step ${step}`; diff --git a/src/languages/es.ts b/src/languages/es.ts index 6c1aa22491fa..fa11988f1f6a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -179,6 +179,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -2367,6 +2368,7 @@ const translations = { setVacationDelegate: 'Configura un delegado de vacaciones para aprobar informes en tu nombre mientras estás fuera de la oficina.', vacationDelegateError: 'Hubo un error al actualizar tu delegado de vacaciones.', asVacationDelegate: ({managerName}: VacationDelegateParams) => `como delegado de vacaciones de ${managerName}`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `a ${submittedToName} como delegado de vacaciones de ${vacationDelegateName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Paso ${step}`; diff --git a/src/languages/params.ts b/src/languages/params.ts index 0e88eb19c849..99ec5339e04c 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -557,6 +557,8 @@ type DelegatorParams = {delegator: string}; type VacationDelegateParams = {managerName: string}; +type SubmittedToVacationDelegateParams = {submittedToName: string; vacationDelegateName: string}; + type RoleNamesParams = {role: string}; type AssignCardParams = { @@ -861,6 +863,7 @@ export type { FormattedMaxLengthParams, GoBackMessageParams, GoToRoomParams, + SubmittedToVacationDelegateParams, HeldRequestParams, InstantSummaryParams, IssueVirtualCardParams, diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 24ffcc3bb850..81b69d349964 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -2931,6 +2931,22 @@ function getManagerOnVacation(action: OnyxEntry): string | undefin return getOriginalMessage(action)?.managerOnVacation; } +function getVacationer(action: OnyxEntry): string | undefined { + if (!isSubmittedAction(action)) { + return; + } + + return getOriginalMessage(action)?.vacationer; +} + +function getSubmittedTo(action: OnyxEntry): string | undefined { + if (!isSubmittedAction(action)) { + return; + } + + return getOriginalMessage(action)?.to; +} + export { doesReportHaveVisibleActions, extractLinksFromMessageHtml, @@ -3096,6 +3112,8 @@ export { isReopenedAction, getIntegrationSyncFailedMessage, getManagerOnVacation, + getVacationer, + getSubmittedTo, }; export type {LastVisibleMessage}; diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index bcd8b7a36637..6f07d1fd6a71 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -21,7 +21,7 @@ import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; -import {getManagerOnVacation, getReportActionMessage} from '@libs/ReportActionsUtils'; +import {getManagerOnVacation, getReportActionMessage, getSubmittedTo, getVacationer} from '@libs/ReportActionsUtils'; import { getDefaultWorkspaceAvatar, getDisplayNameForParticipant, @@ -123,8 +123,16 @@ function ReportActionItemSingle({ let displayName = getDisplayNameForParticipant({accountID: actorAccountID, personalDetailsData: personalDetails}); const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails?.[actorAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? {}; const accountOwnerDetails = getPersonalDetailByEmail(login ?? ''); + + // Vacation delegate details for submitted action + const vacationer = getVacationer(action); + const submittedTo = getSubmittedTo(action); + const vacationDelegateDetailsForSubmit = getPersonalDetailByEmail(vacationer ?? ''); + const submittedToDetails = getPersonalDetailByEmail(submittedTo ?? ''); + + // Vacation delegate details for approved action const managerOnVacation = getManagerOnVacation(action); - const vacationDelegateDetails = getPersonalDetailByEmail(managerOnVacation ?? ''); + const vacationDelegateDetailsForApprove = getPersonalDetailByEmail(managerOnVacation ?? ''); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing let actorHint = (login || (displayName ?? '')).replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); @@ -345,9 +353,17 @@ function ReportActionItemSingle({ {!!action?.delegateAccountID && ( {translate('delegate.onBehalfOfMessage', {delegator: accountOwnerDetails?.displayName ?? ''})} )} + {!!vacationer && !!submittedTo && ( + + {translate('statusPage.toAsVacationDelegate', { + submittedToName: submittedToDetails?.displayName ?? '', + vacationDelegateName: vacationDelegateDetailsForSubmit?.displayName ?? '', + })} + + )} {!!managerOnVacation && ( - {translate('statusPage.asVacationDelegate', {managerName: vacationDelegateDetails?.displayName ?? managerOnVacation ?? ''})} + {translate('statusPage.asVacationDelegate', {managerName: vacationDelegateDetailsForApprove?.displayName ?? managerOnVacation ?? ''})} )} {children} diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 7cf6546c8793..53ef13936333 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -148,6 +148,12 @@ type OriginalMessageSubmitted = { /** Was the report submitted via harvesting (delayed submit) */ harvesting?: boolean; + + /** The login the approver who is acting on behalf of the vacationer */ + to?: string; + + /** The login of the approver who is on a vacation */ + vacationer?: string; }; /** Model of `closed` report action */ From 399dd5ff1bfaeec55e9b5bf9c554322c651163d0 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 17 Jun 2025 01:02:54 +0200 Subject: [PATCH 21/34] add all upcoming lang --- src/languages/de.ts | 7 +++++++ src/languages/fr.ts | 8 ++++++++ src/languages/it.ts | 7 +++++++ src/languages/ja.ts | 7 +++++++ src/languages/nl.ts | 7 +++++++ src/languages/pl.ts | 7 +++++++ src/languages/pt-BR.ts | 7 +++++++ src/languages/zh-hans.ts | 8 ++++++++ 8 files changed, 58 insertions(+) diff --git a/src/languages/de.ts b/src/languages/de.ts index 16c97f560633..07ecd72e0bea 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -192,6 +192,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -243,6 +244,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2396,6 +2398,11 @@ const translations = { time: 'Zeit', clearAfter: 'Nach dem L\u00F6schen', whenClearStatus: 'Wann sollten wir Ihren Status l\u00F6schen?', + vacationDelegate: 'Urlaubsvertretung', + setVacationDelegate: `Legen Sie eine Urlaubsvertretung fest, die Berichte in Ihrer Abwesenheit genehmigt.`, + vacationDelegateError: 'Beim Aktualisieren Ihrer Urlaubsvertretung ist ein Fehler aufgetreten.', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `als Urlaubsvertretung von ${managerName}`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `an ${submittedToName} als Urlaubsvertretung von ${vacationDelegateName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Schritt ${step}`; diff --git a/src/languages/fr.ts b/src/languages/fr.ts index d59978faeedf..723f23127cbe 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -192,6 +192,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -243,6 +244,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2404,6 +2406,12 @@ const translations = { time: 'Temps', clearAfter: 'Effacer apr\u00E8s', whenClearStatus: 'Quand devrions-nous effacer votre statut ?', + vacationDelegate: 'Délégué de vacances', + setVacationDelegate: `Définissez un délégué de vacances pour approuver les rapports en votre absence.`, + vacationDelegateError: 'Une erreur est survenue lors de la mise à jour de votre délégué de vacances.', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `en tant que délégué de vacances de ${managerName}`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => + `à ${submittedToName} en tant que délégué de vacances de ${vacationDelegateName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u00C9tape ${step}`; diff --git a/src/languages/it.ts b/src/languages/it.ts index 2d730f2384a1..924c6e74aee8 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -192,6 +192,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -243,6 +244,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2371,6 +2373,11 @@ const translations = { time: 'Tempo', clearAfter: 'Cancella dopo', whenClearStatus: 'Quando dovremmo cancellare il tuo stato?', + vacationDelegate: 'Delegato per le vacanze', + setVacationDelegate: `Imposta un delegato per le vacanze per approvare i report al tuo posto mentre sei fuori ufficio.`, + vacationDelegateError: 'Si è verificato un errore durante l’aggiornamento del delegato per le vacanze.', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `come delegato per le vacanze di ${managerName}`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `a ${submittedToName} come delegato per le vacanze di ${vacationDelegateName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Passo ${step}`; diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 977749de540b..1df6a7e475fa 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -192,6 +192,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -243,6 +244,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2640,6 +2642,11 @@ const translations = { time: '\u6642\u9593', clearAfter: '\u5F8C\u3067\u30AF\u30EA\u30A2', whenClearStatus: '\u3044\u3064\u30B9\u30C6\u30FC\u30BF\u30B9\u3092\u30AF\u30EA\u30A2\u3059\u3079\u304D\u3067\u3059\u304B\uFF1F', + vacationDelegate: '休暇代理人', + setVacationDelegate: `不在中にレポートを承認してもらうため、休暇代理人を設定してください。`, + vacationDelegateError: '休暇代理人の更新中にエラーが発生しました。', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `${managerName}の休暇代理人として`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `${submittedToName}へ、${vacationDelegateName}の休暇代理人として`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u30B9\u30C6\u30C3\u30D7${step}`; diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 1fadda63c78a..100319448629 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -192,6 +192,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -243,6 +244,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2368,6 +2370,11 @@ const translations = { time: 'Tijd', clearAfter: 'Wissen na', whenClearStatus: 'Wanneer moeten we je status wissen?', + vacationDelegate: 'Vakantievervanger', + setVacationDelegate: `Stel een vakantievervanger in om rapporten namens jou goed te keuren terwijl je afwezig bent.`, + vacationDelegateError: 'Er is een fout opgetreden bij het bijwerken van je vakantievervanger.', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `als vakantievervanger van ${managerName}`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `aan ${submittedToName} als vakantievervanger van ${vacationDelegateName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Stap ${step}`; diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 87fc0c62749e..ee0a2da5657d 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -192,6 +192,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -243,6 +244,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2417,6 +2419,11 @@ const translations = { time: 'Czas', clearAfter: 'Wyczy\u015B\u0107 po', whenClearStatus: 'Kiedy powinni\u015Bmy wyczy\u015Bci\u0107 Tw\u00F3j status?', + vacationDelegate: 'Zastępca urlopowy', + setVacationDelegate: `Ustaw zastępcę urlopowego, który będzie zatwierdzał raporty w twoim imieniu podczas twojej nieobecności.`, + vacationDelegateError: 'Wystąpił błąd podczas aktualizacji twojego zastępcy urlopowego.', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `jako zastępca urlopowy ${managerName}`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `do ${submittedToName} jako zastępca urlopowy ${vacationDelegateName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Krok ${step}`; diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index e5d7af7a534f..5134f0c7648c 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -192,6 +192,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -243,6 +244,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2381,6 +2383,11 @@ const translations = { time: 'Tempo', clearAfter: 'Limpar ap\u00F3s', whenClearStatus: 'Quando devemos limpar seu status?', + vacationDelegate: 'Delegado de férias', + setVacationDelegate: `Defina um delegado de férias para aprovar relatórios em seu nome enquanto estiver fora do escritório.`, + vacationDelegateError: 'Ocorreu um erro ao atualizar seu delegado de férias.', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `como delegado de férias de ${managerName}`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `para ${submittedToName} como delegado de férias de ${vacationDelegateName}`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Etapa ${step}`; diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 5b5575466572..bd5ac19850db 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -192,6 +192,7 @@ import type { StepCounterParams, StripePaidParams, SubmitsToParams, + SubmittedToVacationDelegateParams, SubscriptionCommitmentParams, SubscriptionSettingsRenewsOnParams, SubscriptionSettingsSaveUpToParams, @@ -243,6 +244,7 @@ import type { UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, + VacationDelegateParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -2461,6 +2463,12 @@ const translations = { time: '\u65F6\u95F4', clearAfter: '\u6E05\u9664\u540E', whenClearStatus: '\u6211\u4EEC\u5E94\u8BE5\u4F55\u65F6\u6E05\u9664\u60A8\u7684\u72B6\u6001\uFF1F', + vacationDelegate: '\u4F11\u5047\u4EE3\u7406\u4EBA', + setVacationDelegate: '\u8BBE\u7F6E\u4E00\u4F4D\u4F11\u5047\u4EE3\u7406\u4EBA\uFF0C\u5728\u60A8\u5916\u51FA\u65F6\u4EE3\u60A8\u6279\u51C6\u62A5\u544A\u3002', + vacationDelegateError: '\u66F4\u65B0\u4F11\u5047\u4EE3\u7406\u4EBA\u65F6\u51FA\u9519\u3002', + asVacationDelegate: ({managerName}: VacationDelegateParams) => `\u4F5C\u4E3A ${managerName} \u7684\u4F11\u5047\u4EE3\u7406\u4EBA`, + toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => + `\u53D1\u9001\u7ED9 ${submittedToName}\uFF0C\u4F5C\u4E3A ${vacationDelegateName} \u7684\u4F11\u5047\u4EE3\u7406\u4EBA`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `\u6B65\u9AA4 ${step}`; From dd15be7fa929522c501c3aaf9fe91e97bf381c6b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 17 Jun 2025 01:08:42 +0200 Subject: [PATCH 22/34] add fallbacks --- src/pages/home/report/ReportActionItemSingle.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 6f07d1fd6a71..f122acdffb42 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -356,8 +356,8 @@ function ReportActionItemSingle({ {!!vacationer && !!submittedTo && ( {translate('statusPage.toAsVacationDelegate', { - submittedToName: submittedToDetails?.displayName ?? '', - vacationDelegateName: vacationDelegateDetailsForSubmit?.displayName ?? '', + submittedToName: submittedToDetails?.displayName ?? submittedTo ?? '', + vacationDelegateName: vacationDelegateDetailsForSubmit?.displayName ?? vacationer ?? '', })} )} From 161fc6fcd161e931ac62aba1fb0e9a20b49a381e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 19 Jun 2025 16:32:55 +0200 Subject: [PATCH 23/34] add more fallbacks --- src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index ebdb29215a0d..953f2250086c 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -252,8 +252,8 @@ function StatusPage() { onClose={clearVacationDelegateError} > Date: Fri, 20 Jun 2025 12:29:33 +0200 Subject: [PATCH 24/34] add usestate --- Mobile-Expensify | 2 +- src/pages/settings/Profile/ProfilePage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index fdf4b95b6ae6..143a1fef484f 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit fdf4b95b6ae6d9a91a21abecaaa37fd072067e2f +Subproject commit 143a1fef484ff3358eb90953d1ecf5a511c43f8d diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index 42c3208d67d1..7320ffe3ae72 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -1,5 +1,5 @@ import {useRoute} from '@react-navigation/native'; -import React, {useContext} from 'react'; +import React, {useContext, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import AvatarSkeleton from '@components/AvatarSkeleton'; From 77383166fcbe943c3d8a2a35b5165d110413cd1e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 12:30:16 +0200 Subject: [PATCH 25/34] rm unused --- src/pages/settings/Profile/ProfilePage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index 7320ffe3ae72..3bae27c7a4d9 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -1,5 +1,5 @@ import {useRoute} from '@react-navigation/native'; -import React, {useContext, useState} from 'react'; +import React, {useContext} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import AvatarSkeleton from '@components/AvatarSkeleton'; @@ -64,7 +64,6 @@ function ProfilePage() { const privateDetails = privatePersonalDetails ?? {}; const legalName = `${privateDetails.legalFirstName ?? ''} ${privateDetails.legalLastName ?? ''}`.trim(); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true}); const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const publicOptions = [ From 5f09b5aa775764b54d2d4952d695c76c69d98637 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 14:16:09 +0200 Subject: [PATCH 26/34] try to fix navigation issue --- .../CustomStatus/VacationDelegatePage.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index 2b104ad899b8..7e3ac6c9ccd9 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -30,6 +30,8 @@ function useOptions() { const {options: optionsList, areOptionsInitialized} = useOptionsList(); const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true}); const currentVacationDelegate = vacationDelegate?.delegate; + const delegatePersonalDetails = getPersonalDetailByEmail(currentVacationDelegate ?? ''); + const excludeLogins = useMemo( () => ({ ...CONST.EXPENSIFY_EMAILS_OBJECT, @@ -83,7 +85,7 @@ function useOptions() { }; }, [debouncedSearchValue, defaultOptions, excludeLogins]); - return {...options, currentVacationDelegate, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized}; + return {...options, currentVacationDelegate, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized, delegatePersonalDetails}; } function VacationDelegatePage() { @@ -92,8 +94,18 @@ function VacationDelegatePage() { const {login: currentUserLogin} = useCurrentUserPersonalDetails(); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: false}); - const {currentVacationDelegate, userToInvite, recentReports, personalDetails, searchValue, debouncedSearchValue, setSearchValue, headerMessage, areOptionsInitialized} = useOptions(); - const delegatePersonalDetails = getPersonalDetailByEmail(currentVacationDelegate ?? ''); + const { + currentVacationDelegate, + userToInvite, + recentReports, + personalDetails, + searchValue, + debouncedSearchValue, + setSearchValue, + headerMessage, + areOptionsInitialized, + delegatePersonalDetails, + } = useOptions(); const sections = useMemo(() => { const sectionsList = []; @@ -185,7 +197,7 @@ function VacationDelegatePage() { > Navigation.goBack()} + onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_STATUS)} /> Date: Fri, 20 Jun 2025 14:17:56 +0200 Subject: [PATCH 27/34] disable autofocus --- src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index 7e3ac6c9ccd9..91a584a98f61 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -205,6 +205,7 @@ function VacationDelegatePage() { ListItem={UserListItem} onSelectRow={onSelectRow} shouldSingleExecuteRowSelect + textInputAutoFocus={false} onChangeText={setSearchValue} textInputValue={searchValue} headerMessage={headerMessage} From 83eb5443cca5c4e536cbd77660997c7f9bbd13bc Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 14:36:34 +0200 Subject: [PATCH 28/34] use pure function --- src/libs/actions/VacationDelegate.ts | 10 +--------- .../Profile/CustomStatus/VacationDelegatePage.tsx | 14 +++++++------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/VacationDelegate.ts b/src/libs/actions/VacationDelegate.ts index 4e994e84eb45..8821b40c42ed 100644 --- a/src/libs/actions/VacationDelegate.ts +++ b/src/libs/actions/VacationDelegate.ts @@ -9,14 +9,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {VacationDelegate} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -let vacationDelegate: VacationDelegate; -Onyx.connect({ - key: ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, - callback: (val) => { - vacationDelegate = val ?? {}; - }, -}); - function setVacationDelegate(creator: string, delegate: string) { const optimisticData: OnyxUpdate[] = [ { @@ -61,7 +53,7 @@ function setVacationDelegate(creator: string, delegate: string) { API.write(WRITE_COMMANDS.SET_VACATION_DELEGATE, parameters, {optimisticData, successData, failureData}); } -function deleteVacationDelegate() { +function deleteVacationDelegate(vacationDelegate?: VacationDelegate) { if (isEmptyObject(vacationDelegate)) { return; } diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index 91a584a98f61..b22b01cb8a66 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -85,7 +85,7 @@ function useOptions() { }; }, [debouncedSearchValue, defaultOptions, excludeLogins]); - return {...options, currentVacationDelegate, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized, delegatePersonalDetails}; + return {...options, vacationDelegate, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized, delegatePersonalDetails}; } function VacationDelegatePage() { @@ -95,7 +95,7 @@ function VacationDelegatePage() { const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: false}); const { - currentVacationDelegate, + vacationDelegate, userToInvite, recentReports, personalDetails, @@ -110,7 +110,7 @@ function VacationDelegatePage() { const sections = useMemo(() => { const sectionsList = []; - if (currentVacationDelegate && delegatePersonalDetails) { + if (vacationDelegate && delegatePersonalDetails) { sectionsList.push({ title: undefined, data: [ @@ -170,12 +170,12 @@ function VacationDelegatePage() { shouldShowSubscript: option.shouldShowSubscript ?? undefined, })), })); - }, [currentVacationDelegate, delegatePersonalDetails, personalDetails, recentReports, translate, userToInvite]); + }, [vacationDelegate, delegatePersonalDetails, personalDetails, recentReports, translate, userToInvite]); const onSelectRow = useCallback( (option: Participant) => { - if (option?.login === currentVacationDelegate) { - deleteVacationDelegate(); + if (option?.login === vacationDelegate?.delegate) { + deleteVacationDelegate(vacationDelegate); Navigation.goBack(ROUTES.SETTINGS_STATUS); return; } @@ -183,7 +183,7 @@ function VacationDelegatePage() { setVacationDelegate(currentUserLogin ?? '', option?.login ?? ''); Navigation.goBack(ROUTES.SETTINGS_STATUS); }, - [currentUserLogin, currentVacationDelegate], + [currentUserLogin, vacationDelegate], ); useEffect(() => { From 98771a033471f34cb1f498453b611c1d8ddf4649 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 15:30:23 +0200 Subject: [PATCH 29/34] add autofocus --- src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index b22b01cb8a66..b014794465bb 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -205,7 +205,6 @@ function VacationDelegatePage() { ListItem={UserListItem} onSelectRow={onSelectRow} shouldSingleExecuteRowSelect - textInputAutoFocus={false} onChangeText={setSearchValue} textInputValue={searchValue} headerMessage={headerMessage} From 87aa303261b3691a2e915956c8c552aa0187d8f1 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 15:33:50 +0200 Subject: [PATCH 30/34] run pretty --- .../Profile/CustomStatus/VacationDelegatePage.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index b014794465bb..6e37e68a8672 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -94,18 +94,8 @@ function VacationDelegatePage() { const {login: currentUserLogin} = useCurrentUserPersonalDetails(); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: false}); - const { - vacationDelegate, - userToInvite, - recentReports, - personalDetails, - searchValue, - debouncedSearchValue, - setSearchValue, - headerMessage, - areOptionsInitialized, - delegatePersonalDetails, - } = useOptions(); + const {vacationDelegate, userToInvite, recentReports, personalDetails, searchValue, debouncedSearchValue, setSearchValue, headerMessage, areOptionsInitialized, delegatePersonalDetails} = + useOptions(); const sections = useMemo(() => { const sectionsList = []; From 0556ecb5af4e84077eb9c3c0e6b9b3e426afc9c4 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 21:46:14 +0200 Subject: [PATCH 31/34] show warning modal if policy diff warning --- src/CONST/index.ts | 1 + src/languages/de.ts | 5 +- src/languages/en.ts | 5 +- src/languages/es.ts | 4 +- src/languages/fr.ts | 4 +- src/languages/it.ts | 4 +- src/languages/ja.ts | 4 +- src/languages/nl.ts | 4 +- src/languages/params.ts | 2 +- src/languages/pl.ts | 4 +- src/languages/pt-BR.ts | 4 +- src/languages/zh-hans.ts | 4 +- src/libs/API/types.ts | 4 +- src/libs/actions/VacationDelegate.ts | 10 ++- .../home/report/ReportActionItemSingle.tsx | 2 +- .../CustomStatus/VacationDelegatePage.tsx | 79 +++++++++++++------ 16 files changed, 97 insertions(+), 43 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index b9ea25a8c2d9..06b9e490c652 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1761,6 +1761,7 @@ const CONST = { UNABLE_TO_RETRY: 'unableToRetry', UPDATE_REQUIRED: 426, INCORRECT_MAGIC_CODE: 451, + POLICY_DIFF_WARNING: 305, }, HTTP_STATUS: { // When Cloudflare throttles diff --git a/src/languages/de.ts b/src/languages/de.ts index aa99e8d237b6..d9c9938ea819 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2382,8 +2382,11 @@ const translations = { vacationDelegate: 'Urlaubsvertretung', setVacationDelegate: `Legen Sie eine Urlaubsvertretung fest, die Berichte in Ihrer Abwesenheit genehmigt.`, vacationDelegateError: 'Beim Aktualisieren Ihrer Urlaubsvertretung ist ein Fehler aufgetreten.', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `als Urlaubsvertretung von ${managerName}`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `als Urlaubsvertretung von ${managerName}`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `an ${submittedToName} als Urlaubsvertretung von ${vacationDelegateName}`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `Sie weisen ${nameOrEmail} als Ihre Urlaubsvertretung zu. Diese Person ist noch nicht in all Ihren Workspaces. Wenn Sie fortfahren, wird eine E-Mail an alle Ihre Workspace-Administratoren gesendet, um sie hinzuzufügen.`, + clearAfter: 'Nach dem Löschen', whenClearStatus: 'Wann sollten wir Ihren Status löschen?', }, diff --git a/src/languages/en.ts b/src/languages/en.ts index 20bb87407460..c88d0faa989d 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -602,6 +602,7 @@ const translations = { workspacesTabTitle: 'Workspaces', getTheApp: 'Get the app', scanReceiptsOnTheGo: 'Scan receipts from your phone', + headsUp: 'Heads up!', }, supportalNoAccess: { title: 'Not so fast', @@ -2344,8 +2345,10 @@ const translations = { vacationDelegate: 'Vacation delegate', setVacationDelegate: `Set a vacation delegate to approve reports on your behalf while you're out of office.`, vacationDelegateError: 'There was an error updating your vacation delegate.', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `as ${managerName}'s vacation delegate`, + asVacationDelegate: ({nameOrEmail}: VacationDelegateParams) => `as ${nameOrEmail}'s vacation delegate`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `to ${submittedToName} as vacation delegate for ${vacationDelegateName}`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `You're assigning ${nameOrEmail} as your vacation delegate. They're not on all your workspaces yet. If you choose to continue, an email will be sent to all your workspace admins to add them.`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Step ${step}`; diff --git a/src/languages/es.ts b/src/languages/es.ts index 549edfded15a..a79b12d12d43 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2364,8 +2364,10 @@ const translations = { vacationDelegate: 'Delegado de vacaciones', setVacationDelegate: 'Configura un delegado de vacaciones para aprobar informes en tu nombre mientras estás fuera de la oficina.', vacationDelegateError: 'Hubo un error al actualizar tu delegado de vacaciones.', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `como delegado de vacaciones de ${managerName}`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `como delegado de vacaciones de ${managerName}`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `a ${submittedToName} como delegado de vacaciones de ${vacationDelegateName}`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `Está asignando a ${nameOrEmail} como su delegado de vacaciones. Aún no está en todos sus espacios de trabajo. Si decide continuar, se enviará un correo electrónico a todos los administradores de sus espacios de trabajo para agregarlo.`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Paso ${step}`; diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 4a93d5f242ea..559a72f674a4 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2382,9 +2382,11 @@ const translations = { vacationDelegate: 'Délégué de vacances', setVacationDelegate: `Définissez un délégué de vacances pour approuver les rapports en votre absence.`, vacationDelegateError: 'Une erreur est survenue lors de la mise à jour de votre délégué de vacances.', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `en tant que délégué de vacances de ${managerName}`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `en tant que délégué de vacances de ${managerName}`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `à ${submittedToName} en tant que délégué de vacances de ${vacationDelegateName}`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `Vous assignez ${nameOrEmail} en tant que délégué de vacances. Il/elle n'est pas encore présent(e) dans tous vos espaces de travail. Si vous choisissez de continuer, un e-mail sera envoyé à tous les administrateurs de vos espaces pour l’ajouter.`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Étape ${step}`; diff --git a/src/languages/it.ts b/src/languages/it.ts index 9f725a4ca9cc..8dd8045bb9a2 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2369,8 +2369,10 @@ const translations = { vacationDelegate: 'Delegato per le vacanze', setVacationDelegate: `Imposta un delegato per le vacanze per approvare i report al tuo posto mentre sei fuori ufficio.`, vacationDelegateError: 'Si è verificato un errore durante l’aggiornamento del delegato per le vacanze.', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `come delegato per le vacanze di ${managerName}`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `come delegato per le vacanze di ${managerName}`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `a ${submittedToName} come delegato per le vacanze di ${vacationDelegateName}`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `Stai assegnando ${nameOrEmail} come tuo delegato per le vacanze. Non è ancora presente in tutti i tuoi workspace. Se scegli di continuare, verrà inviata un'e-mail a tutti gli amministratori dei tuoi workspace per aggiungerlo.`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Passo ${step}`; diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 6d434a9040b9..b84685a89946 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2355,8 +2355,10 @@ const translations = { vacationDelegate: '休暇代理人', setVacationDelegate: `不在中にレポートを承認してもらうため、休暇代理人を設定してください。`, vacationDelegateError: '休暇代理人の更新中にエラーが発生しました。', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `${managerName}の休暇代理人として`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `${managerName}の休暇代理人として`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `${submittedToName}へ、${vacationDelegateName}の休暇代理人として`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `${nameOrEmail} を休暇代理人として設定しています。まだすべてのワークスペースに参加していません。続行すると、すべてのワークスペース管理者に追加を依頼するメールが送信されます。`, untilTomorrow: '明日まで', untilTime: ({time}: UntilTimeParams) => `${time}まで`, date: '日付', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 0528ab195568..b306575b0b85 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2370,8 +2370,10 @@ const translations = { vacationDelegate: 'Vakantievervanger', setVacationDelegate: `Stel een vakantievervanger in om rapporten namens jou goed te keuren terwijl je afwezig bent.`, vacationDelegateError: 'Er is een fout opgetreden bij het bijwerken van je vakantievervanger.', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `als vakantievervanger van ${managerName}`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `als vakantievervanger van ${managerName}`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `aan ${submittedToName} als vakantievervanger van ${vacationDelegateName}`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `Je wijst ${nameOrEmail} aan als je vakantievervanger. Deze persoon zit nog niet in al je werkruimtes. Als je doorgaat, wordt er een e-mail gestuurd naar alle beheerders van je werkruimtes om hem/haar toe te voegen.`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Stap ${step}`; diff --git a/src/languages/params.ts b/src/languages/params.ts index bb93777226fe..d7eb4dd693f3 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -557,7 +557,7 @@ type DelegateRoleParams = {role: DelegateRole}; type DelegatorParams = {delegator: string}; -type VacationDelegateParams = {managerName: string}; +type VacationDelegateParams = {nameOrEmail: string}; type SubmittedToVacationDelegateParams = {submittedToName: string; vacationDelegateName: string}; diff --git a/src/languages/pl.ts b/src/languages/pl.ts index f925c31d7992..59a40e6299f2 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2365,8 +2365,10 @@ const translations = { vacationDelegate: 'Zastępca urlopowy', setVacationDelegate: `Ustaw zastępcę urlopowego, który będzie zatwierdzał raporty w twoim imieniu podczas twojej nieobecności.`, vacationDelegateError: 'Wystąpił błąd podczas aktualizacji twojego zastępcy urlopowego.', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `jako zastępca urlopowy ${managerName}`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `jako zastępca urlopowy ${managerName}`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `do ${submittedToName} jako zastępca urlopowy ${vacationDelegateName}`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `Przydzielasz ${nameOrEmail} jako swojego zastępcę urlopowego. Osoba ta nie jest jeszcze członkiem wszystkich twoich przestrzeni roboczych. Jeśli zdecydujesz się kontynuować, zostanie wysłany e-mail do wszystkich administratorów twoich przestrzeni roboczych z prośbą o jej dodanie.`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Krok ${step}`; diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f6364223f3bb..0f66530e6e1c 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2367,8 +2367,10 @@ const translations = { vacationDelegate: 'Delegado de férias', setVacationDelegate: `Defina um delegado de férias para aprovar relatórios em seu nome enquanto estiver fora do escritório.`, vacationDelegateError: 'Ocorreu um erro ao atualizar seu delegado de férias.', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `como delegado de férias de ${managerName}`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `como delegado de férias de ${managerName}`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `para ${submittedToName} como delegado de férias de ${vacationDelegateName}`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `Você está designando ${nameOrEmail} como seu delegado de férias. Essa pessoa ainda não está em todos os seus espaços de trabalho. Se você continuar, um e-mail será enviado para todos os administradores dos seus espaços solicitando a inclusão dela.`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Etapa ${step}`; diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 8db246735bef..0637ae73a85c 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2340,9 +2340,11 @@ const translations = { vacationDelegate: '\u4F11\u5047\u4EE3\u7406\u4EBA', setVacationDelegate: '\u8BBE\u7F6E\u4E00\u4F4D\u4F11\u5047\u4EE3\u7406\u4EBA\uFF0C\u5728\u60A8\u5916\u51FA\u65F6\u4EE3\u60A8\u6279\u51C6\u62A5\u544A\u3002', vacationDelegateError: '\u66F4\u65B0\u4F11\u5047\u4EE3\u7406\u4EBA\u65F6\u51FA\u9519\u3002', - asVacationDelegate: ({managerName}: VacationDelegateParams) => `\u4F5C\u4E3A ${managerName} \u7684\u4F11\u5047\u4EE3\u7406\u4EBA`, + asVacationDelegate: ({nameOrEmail: managerName}: VacationDelegateParams) => `\u4F5C\u4E3A ${managerName} \u7684\u4F11\u5047\u4EE3\u7406\u4EBA`, toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `\u53D1\u9001\u7ED9 ${submittedToName}\uFF0C\u4F5C\u4E3A ${vacationDelegateName} \u7684\u4F11\u5047\u4EE3\u7406\u4EBA`, + vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => + `\u60A8\u6B63\u5728\u6307\u5B9A ${nameOrEmail} \u4F5C\u4E3A\u60A8\u7684\u4F11\u5047\u4EE3\u7406\u4EBA\u3002\u4ED6/\u5979\u8FD8\u672A\u52A0\u5165\u60A8\u7684\u6240\u6709\u5DE5\u4F5C\u7A7A\u95F4\u3002\u5982\u679C\u60A8\u9009\u62E9\u7EE7\u7EED\uFF0C\u5C06\u5411\u6240\u6709\u5DE5\u4F5C\u7A7A\u95F4\u7BA1\u7406\u5458\u53D1\u9001\u90AE\u4EF6\uFF0C\u901A\u77E5\u4ED6\u4EEC\u6DFB\u52A0\u8BE5\u4EBA\u3002`, }, stepCounter: ({step, total, text}: StepCounterParams) => { let result = `步骤 ${step}`; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 4e41b633ad76..c63644372db0 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -487,7 +487,6 @@ const WRITE_COMMANDS = { FINISH_CORPAY_BANK_ACCOUNT_ONBOARDING: 'FinishCorpayBankAccountOnboarding', REOPEN_REPORT: 'ReopenReport', GET_GUIDE_CALL_AVAILABILITY_SCHEDULE: 'GetGuideCallAvailabilitySchedule', - SET_VACATION_DELEGATE: 'SetVacationDelegate', DELETE_VACATION_DELEGATE: 'DeleteVacationDelegate', } as const; @@ -840,7 +839,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.RETRACT_REPORT]: Parameters.RetractReportParams; [WRITE_COMMANDS.COMPLETE_CONCIERGE_CALL]: Parameters.CompleteConciergeCallParams; [WRITE_COMMANDS.FINISH_CORPAY_BANK_ACCOUNT_ONBOARDING]: Parameters.FinishCorpayBankAccountOnboardingParams; - [WRITE_COMMANDS.SET_VACATION_DELEGATE]: Parameters.SetVacationDelegateParams; [WRITE_COMMANDS.DELETE_VACATION_DELEGATE]: null; [WRITE_COMMANDS.REOPEN_REPORT]: Parameters.ReopenReportParams; @@ -1169,6 +1167,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { CREATE_DIGITAL_WALLET: 'CreateDigitalWallet', VERIFY_TEST_DRIVE_RECIPIENT: 'VerifyTestDriveRecipient', LOCK_ACCOUNT: 'LockAccount', + SET_VACATION_DELEGATE: 'SetVacationDelegate', } as const; type SideEffectRequestCommand = ValueOf; @@ -1192,6 +1191,7 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.CREATE_DIGITAL_WALLET]: Parameters.CreateDigitalWalletParams; [SIDE_EFFECT_REQUEST_COMMANDS.VERIFY_TEST_DRIVE_RECIPIENT]: Parameters.VerifyTestDriveRecipientParams; [SIDE_EFFECT_REQUEST_COMMANDS.LOCK_ACCOUNT]: Parameters.LockAccountParams; + [SIDE_EFFECT_REQUEST_COMMANDS.SET_VACATION_DELEGATE]: Parameters.SetVacationDelegateParams; }; type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters; diff --git a/src/libs/actions/VacationDelegate.ts b/src/libs/actions/VacationDelegate.ts index 8821b40c42ed..1d5d9fb6a683 100644 --- a/src/libs/actions/VacationDelegate.ts +++ b/src/libs/actions/VacationDelegate.ts @@ -2,14 +2,14 @@ import Onyx from 'react-native-onyx'; import type {OnyxUpdate} from 'react-native-onyx'; import * as API from '@libs/API'; import type {SetVacationDelegateParams} from '@libs/API/parameters'; -import {WRITE_COMMANDS} from '@libs/API/types'; +import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {VacationDelegate} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -function setVacationDelegate(creator: string, delegate: string) { +function setVacationDelegate(creator: string, delegate: string, shouldOverridePolicyDiffWarning = false) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -47,10 +47,12 @@ function setVacationDelegate(creator: string, delegate: string) { const parameters: SetVacationDelegateParams = { creator, vacationDelegateEmail: delegate, - overridePolicyDiffWarning: true, + overridePolicyDiffWarning: shouldOverridePolicyDiffWarning, }; - API.write(WRITE_COMMANDS.SET_VACATION_DELEGATE, parameters, {optimisticData, successData, failureData}); + // We need to read the API response for showing a warning if there is a policy diff warning. + // eslint-disable-next-line rulesdir/no-api-side-effects-method + return API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.SET_VACATION_DELEGATE, parameters, {optimisticData, successData, failureData}); } function deleteVacationDelegate(vacationDelegate?: VacationDelegate) { diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index b80cb53a23ab..1abbe2ac0ff4 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -368,7 +368,7 @@ function ReportActionItemSingle({ )} {!!managerOnVacation && ( - {translate('statusPage.asVacationDelegate', {managerName: vacationDelegateDetailsForApprove?.displayName ?? managerOnVacation ?? ''})} + {translate('statusPage.asVacationDelegate', {nameOrEmail: vacationDelegateDetailsForApprove?.displayName ?? managerOnVacation ?? ''})} )} {children} diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index 6e37e68a8672..ff238a9083ab 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -1,6 +1,7 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import {useBetas} from '@components/OnyxProvider'; @@ -13,7 +14,7 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {searchInServer} from '@libs/actions/Report'; -import {deleteVacationDelegate, setVacationDelegate} from '@libs/actions/VacationDelegate'; +import {clearVacationDelegateError, deleteVacationDelegate, setVacationDelegate} from '@libs/actions/VacationDelegate'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import {filterAndOrderOptions, getHeaderMessage, getValidOptions} from '@libs/OptionsListUtils'; @@ -91,6 +92,8 @@ function useOptions() { function VacationDelegatePage() { const {translate} = useLocalize(); const styles = useThemeStyles(); + const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); + const [newVacationDelegate, setNewVacationDelegate] = useState(''); const {login: currentUserLogin} = useCurrentUserPersonalDetails(); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: false}); @@ -170,8 +173,17 @@ function VacationDelegatePage() { return; } - setVacationDelegate(currentUserLogin ?? '', option?.login ?? ''); - Navigation.goBack(ROUTES.SETTINGS_STATUS); + setVacationDelegate(currentUserLogin ?? '', option?.login ?? '').then((response) => { + if (!response?.jsonCode) { + Navigation.goBack(ROUTES.SETTINGS_STATUS); + return; + } + + if (response.jsonCode === CONST.JSON_CODE.POLICY_DIFF_WARNING) { + setIsWarningModalVisible(true); + setNewVacationDelegate(option?.login ?? ''); + } + }); }, [currentUserLogin, vacationDelegate], ); @@ -181,29 +193,46 @@ function VacationDelegatePage() { }, [debouncedSearchValue]); return ( - - Navigation.goBack(ROUTES.SETTINGS_STATUS)} - /> - - + + Navigation.goBack(ROUTES.SETTINGS_STATUS)} /> - - + + + + + { + setIsWarningModalVisible(false); + setVacationDelegate(currentUserLogin ?? '', newVacationDelegate, true).then(() => Navigation.goBack(ROUTES.SETTINGS_STATUS)); + }} + onCancel={() => { + setIsWarningModalVisible(false); + clearVacationDelegateError(); + }} + confirmText={translate('common.confirm')} + cancelText={translate('common.cancel')} + /> + ); } From 4fb1a0f5cfea7d827fec1772916b588c69747b63 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 21:58:08 +0200 Subject: [PATCH 32/34] show display name in warning --- .../settings/Profile/CustomStatus/VacationDelegatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index ff238a9083ab..e04a676230fc 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -220,7 +220,7 @@ function VacationDelegatePage() { { setIsWarningModalVisible(false); setVacationDelegate(currentUserLogin ?? '', newVacationDelegate, true).then(() => Navigation.goBack(ROUTES.SETTINGS_STATUS)); From 608001fba14138422f7e8beab221b090f320f2aa Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 22:05:42 +0200 Subject: [PATCH 33/34] fix lang --- Mobile-Expensify | 2 +- src/languages/de.ts | 2 +- src/languages/es.ts | 1 + src/languages/fr.ts | 1 + src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + 10 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index a0730693b5db..60a9f7ac457f 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit a0730693b5db811907ff2347b0b5ca0b436f0dc1 +Subproject commit 60a9f7ac457f9f8f1a91697101c8c213d1a5f618 diff --git a/src/languages/de.ts b/src/languages/de.ts index d9c9938ea819..1eb7bd00423c 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -612,6 +612,7 @@ const translations = { workspacesTabTitle: 'Arbeitsbereiche', getTheApp: 'Hole dir die App', scanReceiptsOnTheGo: 'Scannen Sie Belege von Ihrem Telefon aus', + headsUp: 'Achtung!', }, supportalNoAccess: { title: 'Nicht so schnell', @@ -2386,7 +2387,6 @@ const translations = { toAsVacationDelegate: ({submittedToName, vacationDelegateName}: SubmittedToVacationDelegateParams) => `an ${submittedToName} als Urlaubsvertretung von ${vacationDelegateName}`, vacationDelegateWarning: ({nameOrEmail}: VacationDelegateParams) => `Sie weisen ${nameOrEmail} als Ihre Urlaubsvertretung zu. Diese Person ist noch nicht in all Ihren Workspaces. Wenn Sie fortfahren, wird eine E-Mail an alle Ihre Workspace-Administratoren gesendet, um sie hinzuzufügen.`, - clearAfter: 'Nach dem Löschen', whenClearStatus: 'Wann sollten wir Ihren Status löschen?', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index a79b12d12d43..6bf3b5280717 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -593,6 +593,7 @@ const translations = { workspacesTabTitle: 'Espacios', getTheApp: 'Descarga la app', scanReceiptsOnTheGo: 'Escanea recibos desde tu teléfono', + headsUp: '¡Atención!', }, supportalNoAccess: { title: 'No tan rápido', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 559a72f674a4..02c775fd7c0d 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -612,6 +612,7 @@ const translations = { workspacesTabTitle: 'Espaces de travail', getTheApp: "Obtenez l'application", scanReceiptsOnTheGo: 'Numérisez les reçus depuis votre téléphone', + headsUp: 'Attention !', }, supportalNoAccess: { title: 'Pas si vite', diff --git a/src/languages/it.ts b/src/languages/it.ts index 8dd8045bb9a2..9d6b8903c7b4 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -612,6 +612,7 @@ const translations = { workspacesTabTitle: 'Spazi di lavoro', getTheApp: "Scarica l'app", scanReceiptsOnTheGo: 'Scansiona le ricevute dal tuo telefono', + headsUp: 'Attenzione!', }, supportalNoAccess: { title: 'Non così in fretta', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index b84685a89946..72bc1eb8d0fb 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -612,6 +612,7 @@ const translations = { workspacesTabTitle: 'ワークスペース', getTheApp: 'アプリを入手', scanReceiptsOnTheGo: '携帯電話から領収書をスキャンする', + headsUp: 'ご注意ください!', }, supportalNoAccess: { title: 'ちょっと待ってください', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index b306575b0b85..99e30a473c33 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -611,6 +611,7 @@ const translations = { workspacesTabTitle: 'Werkruimtes', getTheApp: 'Download de app', scanReceiptsOnTheGo: 'Scan bonnetjes vanaf je telefoon', + headsUp: 'Let op!', }, supportalNoAccess: { title: 'Niet zo snel', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 59a40e6299f2..2fee9b1043ee 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -612,6 +612,7 @@ const translations = { workspacesTabTitle: 'Przestrzenie robocze', getTheApp: 'Pobierz aplikację', scanReceiptsOnTheGo: 'Skanuj paragony za pomocą telefonu', + headsUp: 'Uwaga!', }, supportalNoAccess: { title: 'Nie tak szybko', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 0f66530e6e1c..583c5721b422 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -611,6 +611,7 @@ const translations = { workspacesTabTitle: 'Workspaces', getTheApp: 'Obtenha o aplicativo', scanReceiptsOnTheGo: 'Digitalize recibos com seu celular', + headsUp: 'Atenção!', }, supportalNoAccess: { title: 'Não tão rápido', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 0637ae73a85c..af4ae3342555 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -611,6 +611,7 @@ const translations = { workspacesTabTitle: '工作区', getTheApp: '获取应用程序', scanReceiptsOnTheGo: '用手机扫描收据', + headsUp: '\u6CE8\u610F\uFF01', }, supportalNoAccess: { title: '慢一点', From c63ffa7528b0825bf591bc04de6f3a124c8cfbf2 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 20 Jun 2025 22:10:57 +0200 Subject: [PATCH 34/34] submodule --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 60a9f7ac457f..a0730693b5db 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 60a9f7ac457f9f8f1a91697101c8c213d1a5f618 +Subproject commit a0730693b5db811907ff2347b0b5ca0b436f0dc1