Skip to content

Commit 8eff354

Browse files
committed
Merge remote-tracking branch 'origin/main' into Beamanator-cherry-pick-staging-54729-1
2 parents 1f9eb85 + 9732c44 commit 8eff354

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2205
-243
lines changed

Mobile-Expensify

src/CONST.ts

+4
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,7 @@ const CONST = {
688688
CATEGORY_AND_TAG_APPROVERS: 'categoryAndTagApprovers',
689689
PER_DIEM: 'newDotPerDiem',
690690
PRODUCT_TRAINING: 'productTraining',
691+
NEWDOT_MERGE_ACCOUNTS: 'newDotMergeAccounts',
691692
},
692693
BUTTON_STATES: {
693694
DEFAULT: 'default',
@@ -2359,6 +2360,7 @@ const CONST = {
23592360
DISTANCE: 'distance',
23602361
MANUAL: 'manual',
23612362
SCAN: 'scan',
2363+
PER_DIEM: 'per-diem',
23622364
},
23632365
REPORT_ACTION_TYPE: {
23642366
PAY: 'pay',
@@ -4612,6 +4614,7 @@ const CONST = {
46124614
MANUAL: 'manual',
46134615
SCAN: 'scan',
46144616
DISTANCE: 'distance',
4617+
PER_DIEM: 'per-diem',
46154618
},
46164619

46174620
STATUS_TEXT_MAX_LENGTH: 100,
@@ -6456,6 +6459,7 @@ const CONST = {
64566459
LHN_WORKSPACE_CHAT_TOOLTIP: 'workspaceChatLHNTooltip',
64576460
GLOBAL_CREATE_TOOLTIP: 'globalCreateTooltip',
64586461
},
6462+
SMART_BANNER_HEIGHT: 152,
64596463
} as const;
64606464

64616465
type Country = keyof typeof CONST.ALL_COUNTRIES;

src/ONYXKEYS.ts

+8
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ const ONYXKEYS = {
475475
POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_',
476476
POLICY_TAGS: 'policyTags_',
477477
POLICY_RECENTLY_USED_TAGS: 'nvp_recentlyUsedTags_',
478+
POLICY_RECENTLY_USED_DESTINATIONS: 'nvp_recentlyUsedDestinations_',
478479
// Whether the policy's connection data was attempted to be fetched in
479480
// the current user session. As this state only exists client-side, it
480481
// should not be included as part of the policy object. The policy
@@ -620,6 +621,10 @@ const ONYXKEYS = {
620621
MONEY_REQUEST_HOLD_FORM_DRAFT: 'moneyHoldReasonFormDraft',
621622
MONEY_REQUEST_COMPANY_INFO_FORM: 'moneyRequestCompanyInfoForm',
622623
MONEY_REQUEST_COMPANY_INFO_FORM_DRAFT: 'moneyRequestCompanyInfoFormDraft',
624+
MONEY_REQUEST_TIME_FORM: 'moneyRequestTimeForm',
625+
MONEY_REQUEST_TIME_FORM_DRAFT: 'moneyRequestTimeFormDraft',
626+
MONEY_REQUEST_SUBRATE_FORM: 'moneyRequestSubrateForm',
627+
MONEY_REQUEST_SUBRATE_FORM_DRAFT: 'moneyRequestSubrateFormDraft',
623628
NEW_CONTACT_METHOD_FORM: 'newContactMethodForm',
624629
NEW_CONTACT_METHOD_FORM_DRAFT: 'newContactMethodFormDraft',
625630
WAYPOINT_FORM: 'waypointForm',
@@ -765,6 +770,8 @@ type OnyxFormValuesMapping = {
765770
[ONYXKEYS.FORMS.MONEY_REQUEST_MERCHANT_FORM]: FormTypes.MoneyRequestMerchantForm;
766771
[ONYXKEYS.FORMS.MONEY_REQUEST_AMOUNT_FORM]: FormTypes.MoneyRequestAmountForm;
767772
[ONYXKEYS.FORMS.MONEY_REQUEST_DATE_FORM]: FormTypes.MoneyRequestDateForm;
773+
[ONYXKEYS.FORMS.MONEY_REQUEST_TIME_FORM]: FormTypes.MoneyRequestTimeForm;
774+
[ONYXKEYS.FORMS.MONEY_REQUEST_SUBRATE_FORM]: FormTypes.MoneyRequestSubrateForm;
768775
[ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM]: FormTypes.MoneyRequestHoldReasonForm;
769776
[ONYXKEYS.FORMS.MONEY_REQUEST_COMPANY_INFO_FORM]: FormTypes.MoneyRequestCompanyInfoForm;
770777
[ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM]: FormTypes.NewContactMethodForm;
@@ -834,6 +841,7 @@ type OnyxCollectionValuesMapping = {
834841
[ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT]: OnyxTypes.PolicyCategories;
835842
[ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagLists;
836843
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories;
844+
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS]: OnyxTypes.RecentlyUsedCategories;
837845
[ONYXKEYS.COLLECTION.POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED]: boolean;
838846
[ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyEmployeeList;
839847
[ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs;

src/ROUTES.ts

+34
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,36 @@ const ROUTES = {
485485
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
486486
getUrlWithBackToParam(`${action as string}/${iouType as string}/upgrade/${transactionID}/${reportID}`, backTo),
487487
},
488+
MONEY_REQUEST_STEP_DESTINATION: {
489+
route: ':action/:iouType/destination/:transactionID/:reportID',
490+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
491+
getUrlWithBackToParam(`${action as string}/${iouType as string}/destination/${transactionID}/${reportID}`, backTo),
492+
},
493+
MONEY_REQUEST_STEP_TIME: {
494+
route: ':action/:iouType/time/:transactionID/:reportID',
495+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
496+
getUrlWithBackToParam(`${action as string}/${iouType as string}/time/${transactionID}/${reportID}`, backTo),
497+
},
498+
MONEY_REQUEST_STEP_SUBRATE: {
499+
route: ':action/:iouType/subrate/:transactionID/:reportID/:pageIndex',
500+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
501+
getUrlWithBackToParam(`${action as string}/${iouType as string}/subrate/${transactionID}/${reportID}/0`, backTo),
502+
},
503+
MONEY_REQUEST_STEP_DESTINATION_EDIT: {
504+
route: ':action/:iouType/destination/:transactionID/:reportID/edit',
505+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
506+
getUrlWithBackToParam(`${action as string}/${iouType as string}/destination/${transactionID}/${reportID}/edit`, backTo),
507+
},
508+
MONEY_REQUEST_STEP_TIME_EDIT: {
509+
route: ':action/:iouType/time/:transactionID/:reportID/edit',
510+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
511+
getUrlWithBackToParam(`${action as string}/${iouType as string}/time/${transactionID}/${reportID}/edit`, backTo),
512+
},
513+
MONEY_REQUEST_STEP_SUBRATE_EDIT: {
514+
route: ':action/:iouType/subrate/:transactionID/:reportID/edit/:pageIndex',
515+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, pageIndex = 0, backTo = '') =>
516+
getUrlWithBackToParam(`${action as string}/${iouType as string}/subrate/${transactionID}/${reportID}/edit/${pageIndex}`, backTo),
517+
},
488518
SETTINGS_TAGS_ROOT: {
489519
route: 'settings/:policyID/tags',
490520
getRoute: (policyID: string, backTo = '') => getUrlWithBackToParam(`settings/${policyID}/tags`, backTo),
@@ -645,6 +675,10 @@ const ROUTES = {
645675
route: ':action/:iouType/start/:transactionID/:reportID/scan',
646676
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `create/${iouType as string}/start/${transactionID}/${reportID}/scan` as const,
647677
},
678+
MONEY_REQUEST_CREATE_TAB_PER_DIEM: {
679+
route: ':action/:iouType/start/:transactionID/:reportID/per-diem',
680+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `create/${iouType as string}/start/${transactionID}/${reportID}/per-diem` as const,
681+
},
648682

649683
MONEY_REQUEST_STATE_SELECTOR: {
650684
route: 'submit/state',

src/SCREENS.ts

+6
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ const SCREENS = {
230230
RECEIPT: 'Money_Request_Receipt',
231231
STATE_SELECTOR: 'Money_Request_State_Selector',
232232
STEP_ATTENDEES: 'Money_Request_Attendee',
233+
STEP_DESTINATION: 'Money_Request_Destination',
234+
STEP_TIME: 'Money_Request_Time',
235+
STEP_SUBRATE: 'Money_Request_SubRate',
236+
STEP_DESTINATION_EDIT: 'Money_Request_Destination_Edit',
237+
STEP_TIME_EDIT: 'Money_Request_Time_Edit',
238+
STEP_SUBRATE_EDIT: 'Money_Request_SubRate_Edit',
233239
},
234240

235241
TRANSACTION_DUPLICATE: {

src/components/DestinationPicker.tsx

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React, {useMemo} from 'react';
2+
import {useOnyx} from 'react-native-onyx';
3+
import useDebouncedState from '@hooks/useDebouncedState';
4+
import useLocalize from '@hooks/useLocalize';
5+
import usePolicy from '@hooks/usePolicy';
6+
import * as OptionsListUtils from '@libs/OptionsListUtils';
7+
import * as PerDiemRequestUtils from '@libs/PerDiemRequestUtils';
8+
import type {Destination} from '@libs/PerDiemRequestUtils';
9+
import * as PolicyUtils from '@libs/PolicyUtils';
10+
import CONST from '@src/CONST';
11+
import ONYXKEYS from '@src/ONYXKEYS';
12+
import SelectionList from './SelectionList';
13+
import RadioListItem from './SelectionList/RadioListItem';
14+
import type {ListItem} from './SelectionList/types';
15+
16+
type DestinationPickerProps = {
17+
policyID: string;
18+
selectedDestination?: string;
19+
onSubmit: (item: ListItem & {currency: string}) => void;
20+
};
21+
22+
function DestinationPicker({selectedDestination, policyID, onSubmit}: DestinationPickerProps) {
23+
const policy = usePolicy(policyID);
24+
const customUnit = PolicyUtils.getPerDiemCustomUnit(policy);
25+
const [policyRecentlyUsedDestinations] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${policyID}`);
26+
27+
const {translate} = useLocalize();
28+
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
29+
30+
const selectedOptions = useMemo((): Destination[] => {
31+
if (!selectedDestination) {
32+
return [];
33+
}
34+
35+
const selectedRate = customUnit?.rates?.[selectedDestination];
36+
37+
if (!selectedRate?.customUnitRateID) {
38+
return [];
39+
}
40+
41+
return [
42+
{
43+
rateID: selectedRate.customUnitRateID,
44+
name: selectedRate?.name ?? '',
45+
currency: selectedRate?.currency ?? CONST.CURRENCY.USD,
46+
isSelected: true,
47+
},
48+
];
49+
}, [customUnit?.rates, selectedDestination]);
50+
51+
const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
52+
const destinationOptions = PerDiemRequestUtils.getDestinationListSections({
53+
searchValue: debouncedSearchValue,
54+
selectedOptions,
55+
destinations: Object.values(customUnit?.rates ?? {}),
56+
recentlyUsedDestinations: policyRecentlyUsedDestinations,
57+
});
58+
59+
const destinationData = destinationOptions?.at(0)?.data ?? [];
60+
const header = OptionsListUtils.getHeaderMessageForNonUserList(destinationData.length > 0, debouncedSearchValue);
61+
const destinationsCount = Object.values(customUnit?.rates ?? {}).length;
62+
const isDestinationsCountBelowThreshold = destinationsCount < CONST.STANDARD_LIST_ITEM_LIMIT;
63+
const showInput = !isDestinationsCountBelowThreshold;
64+
65+
return [destinationOptions, header, showInput];
66+
}, [debouncedSearchValue, selectedOptions, customUnit?.rates, policyRecentlyUsedDestinations]);
67+
68+
const selectedOptionKey = useMemo(
69+
() => (sections?.at(0)?.data ?? []).filter((destination) => destination.keyForList === selectedDestination).at(0)?.keyForList,
70+
[sections, selectedDestination],
71+
);
72+
73+
return (
74+
<SelectionList
75+
sections={sections}
76+
headerMessage={headerMessage}
77+
textInputValue={searchValue}
78+
textInputLabel={shouldShowTextInput ? translate('common.search') : undefined}
79+
onChangeText={setSearchValue}
80+
onSelectRow={onSubmit}
81+
ListItem={RadioListItem}
82+
initiallyFocusedOptionKey={selectedOptionKey ?? undefined}
83+
isRowMultilineSupported
84+
/>
85+
);
86+
}
87+
88+
DestinationPicker.displayName = 'DestinationPicker';
89+
90+
export default DestinationPicker;

src/components/Form/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type StatePicker from '@components/StatePicker';
2323
import type StateSelector from '@components/StateSelector';
2424
import type TextInput from '@components/TextInput';
2525
import type TextPicker from '@components/TextPicker';
26+
import type TimeModalPicker from '@components/TimeModalPicker';
2627
import type UploadFile from '@components/UploadFile';
2728
import type ValuePicker from '@components/ValuePicker';
2829
import type ConstantSelector from '@pages/Debug/ConstantSelector';
@@ -69,7 +70,8 @@ type ValidInputs =
6970
| typeof StatePicker
7071
| typeof ConstantSelector
7172
| typeof UploadFile
72-
| typeof PushRowWithModal;
73+
| typeof PushRowWithModal
74+
| typeof TimeModalPicker;
7375

7476
type ValueTypeKey = 'string' | 'boolean' | 'date' | 'country' | 'reportFields' | 'disabledListValues' | 'entityChart';
7577
type ValueTypeMap = {

src/components/MoneyRequestConfirmationList.tsx

+29-4
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ type MoneyRequestConfirmationListProps = {
114114
/** Whether the expense is a distance expense */
115115
isDistanceRequest?: boolean;
116116

117+
/** Whether the expense is a per diem expense */
118+
isPerDiemRequest?: boolean;
119+
117120
/** Whether we're editing a split expense */
118121
isEditingSplitBill?: boolean;
119122

@@ -151,6 +154,7 @@ function MoneyRequestConfirmationList({
151154
iouType = CONST.IOU.TYPE.SUBMIT,
152155
iouAmount,
153156
isDistanceRequest = false,
157+
isPerDiemRequest = false,
154158
isPolicyExpenseChat = false,
155159
iouCategory = '',
156160
shouldShowSmartScanFields = true,
@@ -231,11 +235,11 @@ function MoneyRequestConfirmationList({
231235
// A flag for showing the categories field
232236
const shouldShowCategories = (isPolicyExpenseChat || isTypeInvoice) && (!!iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {})));
233237

234-
const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend;
238+
const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend && !isPerDiemRequest;
235239

236240
const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]);
237241

238-
const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest);
242+
const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest) && !isPerDiemRequest;
239243

240244
const previousTransactionAmount = usePrevious(transaction?.amount);
241245
const previousTransactionCurrency = usePrevious(transaction?.currency);
@@ -428,7 +432,7 @@ function MoneyRequestConfirmationList({
428432
text = translate('iou.trackExpense');
429433
} else if (isTypeSplit && iouAmount === 0) {
430434
text = translate('iou.splitExpense');
431-
} else if ((receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) {
435+
} else if ((receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute || isPerDiemRequest) {
432436
text = translate('iou.submitExpense');
433437
if (iouAmount !== 0) {
434438
text = translate('iou.submitAmount', {amount: formattedAmount});
@@ -443,7 +447,20 @@ function MoneyRequestConfirmationList({
443447
value: iouType,
444448
},
445449
];
446-
}, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, policy, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount, isTypeInvoice]);
450+
}, [
451+
isTypeInvoice,
452+
isTypeTrackExpense,
453+
isTypeSplit,
454+
iouAmount,
455+
receiptPath,
456+
isTypeRequest,
457+
isDistanceRequestWithPendingRoute,
458+
isPerDiemRequest,
459+
iouType,
460+
policy,
461+
translate,
462+
formattedAmount,
463+
]);
447464

448465
const onSplitShareChange = useCallback(
449466
(accountID: number, value: number) => {
@@ -762,6 +779,11 @@ function MoneyRequestConfirmationList({
762779
return;
763780
}
764781

782+
if (isPerDiemRequest && (transaction.comment?.customUnit?.subRates ?? []).length === 0) {
783+
setFormError('iou.error.invalidSubrateLength');
784+
return;
785+
}
786+
765787
if (iouType !== CONST.IOU.TYPE.PAY) {
766788
// validate the amount for distance expenses
767789
const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode);
@@ -809,6 +831,7 @@ function MoneyRequestConfirmationList({
809831
onSendMoney,
810832
iouCurrencyCode,
811833
isDistanceRequest,
834+
isPerDiemRequest,
812835
isDistanceRequestWithPendingRoute,
813836
iouAmount,
814837
onConfirm,
@@ -916,6 +939,7 @@ function MoneyRequestConfirmationList({
916939
iouType={iouType}
917940
isCategoryRequired={isCategoryRequired}
918941
isDistanceRequest={isDistanceRequest}
942+
isPerDiemRequest={isPerDiemRequest}
919943
isEditingSplitBill={isEditingSplitBill}
920944
isMerchantEmpty={isMerchantEmpty}
921945
isMerchantRequired={isMerchantRequired}
@@ -937,6 +961,7 @@ function MoneyRequestConfirmationList({
937961
shouldShowCategories={shouldShowCategories}
938962
shouldShowMerchant={shouldShowMerchant}
939963
shouldShowSmartScanFields={shouldShowSmartScanFields}
964+
shouldShowAmountField={!isPerDiemRequest}
940965
shouldShowTax={shouldShowTax}
941966
transaction={transaction}
942967
transactionID={transactionID}

0 commit comments

Comments
 (0)