Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
83128dd
Implemented per diem request flow
shubham1206agra Dec 17, 2024
4309d58
Fix ts lint
shubham1206agra Dec 17, 2024
f52e55e
Fix crash
shubham1206agra Dec 17, 2024
ee04b68
Fix errors
shubham1206agra Dec 17, 2024
02f5b0a
Fix component
shubham1206agra Dec 17, 2024
411754d
Fix wrong navigation
shubham1206agra Dec 17, 2024
0cd1397
Fix subrate page layout
shubham1206agra Dec 17, 2024
705413a
Fix unused keys
shubham1206agra Dec 17, 2024
e7cf663
Fix small bug
shubham1206agra Dec 17, 2024
9e45c3b
Fix small bug
shubham1206agra Dec 17, 2024
5fe7c8e
Fix prettier
shubham1206agra Dec 17, 2024
03f8148
Add menuitems to confirmation page
shubham1206agra Dec 18, 2024
8ea64b5
Fixed wrong policyID
shubham1206agra Dec 18, 2024
70fb5ba
Fix wrong subrate filter logic
shubham1206agra Dec 18, 2024
f0205ee
Trying to fix navigation by declaring redundant routes
shubham1206agra Dec 18, 2024
a3fbb08
Fix condition shouldShowPerDiemOption
shubham1206agra Dec 18, 2024
dd12886
Add validation to time page
shubham1206agra Dec 18, 2024
71c6bcf
Fix confirmation page
shubham1206agra Dec 18, 2024
68eceec
Fix confirmation page
shubham1206agra Dec 18, 2024
57bc75b
Test something on confirmation page
shubham1206agra Dec 18, 2024
40d2ea4
Fix style
shubham1206agra Dec 18, 2024
9230ffa
Added text to translation
shubham1206agra Dec 18, 2024
ccc2d26
Fix button text
shubham1206agra Dec 18, 2024
ad5fe97
Fix go back from confirmation page
shubham1206agra Dec 18, 2024
dd17663
Fix per diem display condition
shubham1206agra Dec 18, 2024
ab477da
Temp: return early when starting createTransaction since per diem req…
shubham1206agra Dec 18, 2024
fb2fb95
Merge branch 'Expensify:main' into per-diem-5
shubham1206agra Dec 18, 2024
2f9a048
Fix shouldShowPerDiemOption to exclude track expense
shubham1206agra Dec 18, 2024
4f8b40c
Fix tabselector item width
shubham1206agra Dec 18, 2024
ba5fe24
Merge branch 'main' into per-diem-5
shubham1206agra Dec 18, 2024
0bbe1a5
Merge branch 'main' into per-diem-5
shubham1206agra Dec 19, 2024
f6703d4
Merge branch 'main' into per-diem-5
shubham1206agra Dec 19, 2024
0ee0a12
Fix ts lint
shubham1206agra Dec 19, 2024
624ddef
Fix text on destination step
shubham1206agra Dec 19, 2024
123ff41
Merge branch 'main' into per-diem-5
shubham1206agra Dec 27, 2024
701ec8b
Fix lint
shubham1206agra Dec 27, 2024
51b13bd
Fix eslint
shubham1206agra Dec 27, 2024
76612eb
Fix missing screen config
shubham1206agra Dec 27, 2024
21336ba
Apply suggestions from code review
shubham1206agra Dec 28, 2024
a34f050
Merge branch 'Expensify:main' into per-diem-5
shubham1206agra Dec 30, 2024
a1e9d78
Add error for no subrate
shubham1206agra Dec 30, 2024
7d825e4
Fix error condition
shubham1206agra Dec 30, 2024
c68399b
Apply suggestions from code review
shubham1206agra Dec 30, 2024
114f67d
Fix error condition
shubham1206agra Dec 30, 2024
8ff19ff
Fix padding
shubham1206agra Dec 30, 2024
dc599ba
Merge branch 'main' into per-diem-5
shubham1206agra Jan 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2360,6 +2360,7 @@ const CONST = {
DISTANCE: 'distance',
MANUAL: 'manual',
SCAN: 'scan',
PER_DIEM: 'per-diem',
},
REPORT_ACTION_TYPE: {
PAY: 'pay',
Expand Down Expand Up @@ -4613,6 +4614,7 @@ const CONST = {
MANUAL: 'manual',
SCAN: 'scan',
DISTANCE: 'distance',
PER_DIEM: 'per-diem',
},

STATUS_TEXT_MAX_LENGTH: 100,
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 @@ -622,6 +623,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 @@ -767,6 +772,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 @@ -837,6 +844,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 @@ -486,6 +486,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 @@ -646,6 +676,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 @@ -231,6 +231,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