Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into Beamanator-cherry-pic…
Browse files Browse the repository at this point in the history
…k-staging-54729-1
  • Loading branch information
mjasikowski committed Jan 2, 2025
2 parents 1f9eb85 + 9732c44 commit 8eff354
Show file tree
Hide file tree
Showing 61 changed files with 2,205 additions and 243 deletions.
2 changes: 1 addition & 1 deletion Mobile-Expensify
4 changes: 4 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ const CONST = {
CATEGORY_AND_TAG_APPROVERS: 'categoryAndTagApprovers',
PER_DIEM: 'newDotPerDiem',
PRODUCT_TRAINING: 'productTraining',
NEWDOT_MERGE_ACCOUNTS: 'newDotMergeAccounts',
},
BUTTON_STATES: {
DEFAULT: 'default',
Expand Down Expand Up @@ -2359,6 +2360,7 @@ const CONST = {
DISTANCE: 'distance',
MANUAL: 'manual',
SCAN: 'scan',
PER_DIEM: 'per-diem',
},
REPORT_ACTION_TYPE: {
PAY: 'pay',
Expand Down Expand Up @@ -4612,6 +4614,7 @@ const CONST = {
MANUAL: 'manual',
SCAN: 'scan',
DISTANCE: 'distance',
PER_DIEM: 'per-diem',
},

STATUS_TEXT_MAX_LENGTH: 100,
Expand Down Expand Up @@ -6456,6 +6459,7 @@ const CONST = {
LHN_WORKSPACE_CHAT_TOOLTIP: 'workspaceChatLHNTooltip',
GLOBAL_CREATE_TOOLTIP: 'globalCreateTooltip',
},
SMART_BANNER_HEIGHT: 152,
} as const;

type Country = keyof typeof CONST.ALL_COUNTRIES;
Expand Down
8 changes: 8 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ const ONYXKEYS = {
POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_',
POLICY_TAGS: 'policyTags_',
POLICY_RECENTLY_USED_TAGS: 'nvp_recentlyUsedTags_',
POLICY_RECENTLY_USED_DESTINATIONS: 'nvp_recentlyUsedDestinations_',
// Whether the policy's connection data was attempted to be fetched in
// the current user session. As this state only exists client-side, it
// should not be included as part of the policy object. The policy
Expand Down Expand Up @@ -620,6 +621,10 @@ const ONYXKEYS = {
MONEY_REQUEST_HOLD_FORM_DRAFT: 'moneyHoldReasonFormDraft',
MONEY_REQUEST_COMPANY_INFO_FORM: 'moneyRequestCompanyInfoForm',
MONEY_REQUEST_COMPANY_INFO_FORM_DRAFT: 'moneyRequestCompanyInfoFormDraft',
MONEY_REQUEST_TIME_FORM: 'moneyRequestTimeForm',
MONEY_REQUEST_TIME_FORM_DRAFT: 'moneyRequestTimeFormDraft',
MONEY_REQUEST_SUBRATE_FORM: 'moneyRequestSubrateForm',
MONEY_REQUEST_SUBRATE_FORM_DRAFT: 'moneyRequestSubrateFormDraft',
NEW_CONTACT_METHOD_FORM: 'newContactMethodForm',
NEW_CONTACT_METHOD_FORM_DRAFT: 'newContactMethodFormDraft',
WAYPOINT_FORM: 'waypointForm',
Expand Down Expand Up @@ -765,6 +770,8 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.MONEY_REQUEST_MERCHANT_FORM]: FormTypes.MoneyRequestMerchantForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_AMOUNT_FORM]: FormTypes.MoneyRequestAmountForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_DATE_FORM]: FormTypes.MoneyRequestDateForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_TIME_FORM]: FormTypes.MoneyRequestTimeForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_SUBRATE_FORM]: FormTypes.MoneyRequestSubrateForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM]: FormTypes.MoneyRequestHoldReasonForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_COMPANY_INFO_FORM]: FormTypes.MoneyRequestCompanyInfoForm;
[ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM]: FormTypes.NewContactMethodForm;
Expand Down Expand Up @@ -834,6 +841,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT]: OnyxTypes.PolicyCategories;
[ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagLists;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS]: OnyxTypes.RecentlyUsedCategories;
[ONYXKEYS.COLLECTION.POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED]: boolean;
[ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyEmployeeList;
[ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs;
Expand Down
34 changes: 34 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,36 @@ const ROUTES = {
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/upgrade/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_DESTINATION: {
route: ':action/:iouType/destination/:transactionID/:reportID',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/destination/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_TIME: {
route: ':action/:iouType/time/:transactionID/:reportID',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/time/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_SUBRATE: {
route: ':action/:iouType/subrate/:transactionID/:reportID/:pageIndex',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/subrate/${transactionID}/${reportID}/0`, backTo),
},
MONEY_REQUEST_STEP_DESTINATION_EDIT: {
route: ':action/:iouType/destination/:transactionID/:reportID/edit',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/destination/${transactionID}/${reportID}/edit`, backTo),
},
MONEY_REQUEST_STEP_TIME_EDIT: {
route: ':action/:iouType/time/:transactionID/:reportID/edit',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/time/${transactionID}/${reportID}/edit`, backTo),
},
MONEY_REQUEST_STEP_SUBRATE_EDIT: {
route: ':action/:iouType/subrate/:transactionID/:reportID/edit/:pageIndex',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, pageIndex = 0, backTo = '') =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/subrate/${transactionID}/${reportID}/edit/${pageIndex}`, backTo),
},
SETTINGS_TAGS_ROOT: {
route: 'settings/:policyID/tags',
getRoute: (policyID: string, backTo = '') => getUrlWithBackToParam(`settings/${policyID}/tags`, backTo),
Expand Down Expand Up @@ -645,6 +675,10 @@ const ROUTES = {
route: ':action/:iouType/start/:transactionID/:reportID/scan',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `create/${iouType as string}/start/${transactionID}/${reportID}/scan` as const,
},
MONEY_REQUEST_CREATE_TAB_PER_DIEM: {
route: ':action/:iouType/start/:transactionID/:reportID/per-diem',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `create/${iouType as string}/start/${transactionID}/${reportID}/per-diem` as const,
},

MONEY_REQUEST_STATE_SELECTOR: {
route: 'submit/state',
Expand Down
6 changes: 6 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,12 @@ const SCREENS = {
RECEIPT: 'Money_Request_Receipt',
STATE_SELECTOR: 'Money_Request_State_Selector',
STEP_ATTENDEES: 'Money_Request_Attendee',
STEP_DESTINATION: 'Money_Request_Destination',
STEP_TIME: 'Money_Request_Time',
STEP_SUBRATE: 'Money_Request_SubRate',
STEP_DESTINATION_EDIT: 'Money_Request_Destination_Edit',
STEP_TIME_EDIT: 'Money_Request_Time_Edit',
STEP_SUBRATE_EDIT: 'Money_Request_SubRate_Edit',
},

TRANSACTION_DUPLICATE: {
Expand Down
90 changes: 90 additions & 0 deletions src/components/DestinationPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, {useMemo} from 'react';
import {useOnyx} from 'react-native-onyx';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PerDiemRequestUtils from '@libs/PerDiemRequestUtils';
import type {Destination} from '@libs/PerDiemRequestUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import SelectionList from './SelectionList';
import RadioListItem from './SelectionList/RadioListItem';
import type {ListItem} from './SelectionList/types';

type DestinationPickerProps = {
policyID: string;
selectedDestination?: string;
onSubmit: (item: ListItem & {currency: string}) => void;
};

function DestinationPicker({selectedDestination, policyID, onSubmit}: DestinationPickerProps) {
const policy = usePolicy(policyID);
const customUnit = PolicyUtils.getPerDiemCustomUnit(policy);
const [policyRecentlyUsedDestinations] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${policyID}`);

const {translate} = useLocalize();
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');

const selectedOptions = useMemo((): Destination[] => {
if (!selectedDestination) {
return [];
}

const selectedRate = customUnit?.rates?.[selectedDestination];

if (!selectedRate?.customUnitRateID) {
return [];
}

return [
{
rateID: selectedRate.customUnitRateID,
name: selectedRate?.name ?? '',
currency: selectedRate?.currency ?? CONST.CURRENCY.USD,
isSelected: true,
},
];
}, [customUnit?.rates, selectedDestination]);

const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
const destinationOptions = PerDiemRequestUtils.getDestinationListSections({
searchValue: debouncedSearchValue,
selectedOptions,
destinations: Object.values(customUnit?.rates ?? {}),
recentlyUsedDestinations: policyRecentlyUsedDestinations,
});

const destinationData = destinationOptions?.at(0)?.data ?? [];
const header = OptionsListUtils.getHeaderMessageForNonUserList(destinationData.length > 0, debouncedSearchValue);
const destinationsCount = Object.values(customUnit?.rates ?? {}).length;
const isDestinationsCountBelowThreshold = destinationsCount < CONST.STANDARD_LIST_ITEM_LIMIT;
const showInput = !isDestinationsCountBelowThreshold;

return [destinationOptions, header, showInput];
}, [debouncedSearchValue, selectedOptions, customUnit?.rates, policyRecentlyUsedDestinations]);

const selectedOptionKey = useMemo(
() => (sections?.at(0)?.data ?? []).filter((destination) => destination.keyForList === selectedDestination).at(0)?.keyForList,
[sections, selectedDestination],
);

return (
<SelectionList
sections={sections}
headerMessage={headerMessage}
textInputValue={searchValue}
textInputLabel={shouldShowTextInput ? translate('common.search') : undefined}
onChangeText={setSearchValue}
onSelectRow={onSubmit}
ListItem={RadioListItem}
initiallyFocusedOptionKey={selectedOptionKey ?? undefined}
isRowMultilineSupported
/>
);
}

DestinationPicker.displayName = 'DestinationPicker';

export default DestinationPicker;
4 changes: 3 additions & 1 deletion src/components/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type StatePicker from '@components/StatePicker';
import type StateSelector from '@components/StateSelector';
import type TextInput from '@components/TextInput';
import type TextPicker from '@components/TextPicker';
import type TimeModalPicker from '@components/TimeModalPicker';
import type UploadFile from '@components/UploadFile';
import type ValuePicker from '@components/ValuePicker';
import type ConstantSelector from '@pages/Debug/ConstantSelector';
Expand Down Expand Up @@ -69,7 +70,8 @@ type ValidInputs =
| typeof StatePicker
| typeof ConstantSelector
| typeof UploadFile
| typeof PushRowWithModal;
| typeof PushRowWithModal
| typeof TimeModalPicker;

type ValueTypeKey = 'string' | 'boolean' | 'date' | 'country' | 'reportFields' | 'disabledListValues' | 'entityChart';
type ValueTypeMap = {
Expand Down
33 changes: 29 additions & 4 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ type MoneyRequestConfirmationListProps = {
/** Whether the expense is a distance expense */
isDistanceRequest?: boolean;

/** Whether the expense is a per diem expense */
isPerDiemRequest?: boolean;

/** Whether we're editing a split expense */
isEditingSplitBill?: boolean;

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

const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend;
const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend && !isPerDiemRequest;

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

const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest);
const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest) && !isPerDiemRequest;

const previousTransactionAmount = usePrevious(transaction?.amount);
const previousTransactionCurrency = usePrevious(transaction?.currency);
Expand Down Expand Up @@ -428,7 +432,7 @@ function MoneyRequestConfirmationList({
text = translate('iou.trackExpense');
} else if (isTypeSplit && iouAmount === 0) {
text = translate('iou.splitExpense');
} else if ((receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) {
} else if ((receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute || isPerDiemRequest) {
text = translate('iou.submitExpense');
if (iouAmount !== 0) {
text = translate('iou.submitAmount', {amount: formattedAmount});
Expand All @@ -443,7 +447,20 @@ function MoneyRequestConfirmationList({
value: iouType,
},
];
}, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, policy, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount, isTypeInvoice]);
}, [
isTypeInvoice,
isTypeTrackExpense,
isTypeSplit,
iouAmount,
receiptPath,
isTypeRequest,
isDistanceRequestWithPendingRoute,
isPerDiemRequest,
iouType,
policy,
translate,
formattedAmount,
]);

const onSplitShareChange = useCallback(
(accountID: number, value: number) => {
Expand Down Expand Up @@ -762,6 +779,11 @@ function MoneyRequestConfirmationList({
return;
}

if (isPerDiemRequest && (transaction.comment?.customUnit?.subRates ?? []).length === 0) {
setFormError('iou.error.invalidSubrateLength');
return;
}

if (iouType !== CONST.IOU.TYPE.PAY) {
// validate the amount for distance expenses
const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode);
Expand Down Expand Up @@ -809,6 +831,7 @@ function MoneyRequestConfirmationList({
onSendMoney,
iouCurrencyCode,
isDistanceRequest,
isPerDiemRequest,
isDistanceRequestWithPendingRoute,
iouAmount,
onConfirm,
Expand Down Expand Up @@ -916,6 +939,7 @@ function MoneyRequestConfirmationList({
iouType={iouType}
isCategoryRequired={isCategoryRequired}
isDistanceRequest={isDistanceRequest}
isPerDiemRequest={isPerDiemRequest}
isEditingSplitBill={isEditingSplitBill}
isMerchantEmpty={isMerchantEmpty}
isMerchantRequired={isMerchantRequired}
Expand All @@ -937,6 +961,7 @@ function MoneyRequestConfirmationList({
shouldShowCategories={shouldShowCategories}
shouldShowMerchant={shouldShowMerchant}
shouldShowSmartScanFields={shouldShowSmartScanFields}
shouldShowAmountField={!isPerDiemRequest}
shouldShowTax={shouldShowTax}
transaction={transaction}
transactionID={transactionID}
Expand Down
Loading

0 comments on commit 8eff354

Please sign in to comment.