Skip to content

Commit

Permalink
Merge pull request #37282 from shubham1206agra/track-expense
Browse files Browse the repository at this point in the history
Track Expense flow
  • Loading branch information
thienlnam authored Mar 20, 2024
2 parents 4aecfcb + c5ac594 commit 2571c8d
Show file tree
Hide file tree
Showing 32 changed files with 1,207 additions and 70 deletions.
5 changes: 5 additions & 0 deletions assets/images/document-plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ const CONST = {
BETA_COMMENT_LINKING: 'commentLinking',
VIOLATIONS: 'violations',
REPORT_FIELDS: 'reportFields',
TRACK_EXPENSE: 'trackExpense',
P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests',
WORKFLOWS_DELAYED_SUBMISSION: 'workflowsDelayedSubmission',
},
Expand Down Expand Up @@ -1344,6 +1345,7 @@ const CONST = {
SEND: 'send',
SPLIT: 'split',
REQUEST: 'request',
TRACK_EXPENSE: 'track-expense',
},
REQUEST_TYPE: {
DISTANCE: 'distance',
Expand All @@ -1358,6 +1360,7 @@ const CONST = {
CANCEL: 'cancel',
DELETE: 'delete',
APPROVE: 'approve',
TRACK: 'track',
},
AMOUNT_MAX_LENGTH: 10,
RECEIPT_STATE: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/AvatarWithDisplayName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function AvatarWithDisplayName({
const title = ReportUtils.getReportName(report);
const subtitle = ReportUtils.getChatRoomSubtitle(report);
const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report);
const isMoneyRequestOrReport = ReportUtils.isMoneyRequestReport(report) || ReportUtils.isMoneyRequest(report);
const isMoneyRequestOrReport = ReportUtils.isMoneyRequestReport(report) || ReportUtils.isMoneyRequest(report) || ReportUtils.isTrackExpenseReport(report);
const icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy);
const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails);
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails) as PersonalDetails[], false);
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import Concierge from '@assets/images/concierge.svg';
import Connect from '@assets/images/connect.svg';
import Copy from '@assets/images/copy.svg';
import CreditCard from '@assets/images/creditcard.svg';
import DocumentPlus from '@assets/images/document-plus.svg';
import DocumentSlash from '@assets/images/document-slash.svg';
import Document from '@assets/images/document.svg';
import DotIndicatorUnfilled from '@assets/images/dot-indicator-unfilled.svg';
Expand Down Expand Up @@ -314,4 +315,5 @@ export {
ChatBubbleUnread,
ChatBubbleReply,
Lightbulb,
DocumentPlus,
};
11 changes: 8 additions & 3 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,15 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
const deleteTransaction = useCallback(() => {
if (parentReportAction) {
const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : '';
if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) {
IOU.deleteTrackExpense(parentReport?.reportID ?? '', iouTransactionID, parentReportAction, true);
return;
}
IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true);
}

setIsDeleteModalVisible(false);
}, [parentReportAction, setIsDeleteModalVisible]);
}, [parentReport?.reportID, parentReportAction, setIsDeleteModalVisible]);

const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction);
const isPending = TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction);
Expand All @@ -84,7 +88,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction;

// If the report supports adding transactions to it, then it also supports deleting transactions from it.
const canDeleteRequest = isActionOwner && ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) && !isDeletedParentAction;
const canDeleteRequest = isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(report)) && !isDeletedParentAction;

const changeMoneyRequestStatus = () => {
const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : '';
Expand All @@ -109,7 +113,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
if (canHoldOrUnholdRequest) {
const isRequestIOU = parentReport?.type === 'iou';
const isHoldCreator = ReportUtils.isHoldCreator(transaction, report?.reportID) && isRequestIOU;
const canModifyStatus = isPolicyAdmin || isActionOwner || isApprover;
const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(report);
const canModifyStatus = !isTrackExpenseReport && (isPolicyAdmin || isActionOwner || isApprover);
if (isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus))) {
threeDotsMenuItems.push({
icon: Expensicons.Stopwatch,
Expand Down
14 changes: 11 additions & 3 deletions src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST;
const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT;
const isTypeSend = iouType === CONST.IOU.TYPE.SEND;
const isTypeTrackExpense = iouType === CONST.IOU.TYPE.TRACK_EXPENSE;
const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isTypeSplit);

const {unit, rate, currency} = mileageRate;
Expand Down Expand Up @@ -381,7 +382,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({

const splitOrRequestOptions = useMemo(() => {
let text;
if (isTypeSplit && iouAmount === 0) {
if (isTypeTrackExpense) {
text = translate('iou.trackExpense');
} else if (isTypeSplit && iouAmount === 0) {
text = translate('iou.split');
} else if ((receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) {
text = translate('iou.request');
Expand All @@ -398,7 +401,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
value: iouType,
},
];
}, [isTypeSplit, isTypeRequest, iouType, iouAmount, receiptPath, formattedAmount, isDistanceRequestWithPendingRoute, translate]);
}, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount]);

const selectedParticipants = useMemo(() => _.filter(pickedParticipants, (participant) => participant.selected), [pickedParticipants]);
const personalDetailsOfPayee = useMemo(() => payeePersonalDetails || currentUserPersonalDetails, [payeePersonalDetails, currentUserPersonalDetails]);
Expand Down Expand Up @@ -446,7 +449,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
} else {
const formattedSelectedParticipants = _.map(selectedParticipants, (participant) => ({
...participant,
isDisabled: !participant.isPolicyExpenseChat && ReportUtils.isOptimisticPersonalDetail(participant.accountID),
isDisabled: !participant.isPolicyExpenseChat && !participant.isSelfDM && ReportUtils.isOptimisticPersonalDetail(participant.accountID),
}));
sections.push({
title: translate('common.to'),
Expand Down Expand Up @@ -538,6 +541,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const navigateToReportOrUserDetail = (option) => {
const activeRoute = Navigation.getActiveRouteWithoutParams();

if (option.isSelfDM) {
Navigation.navigate(ROUTES.PROFILE.getRoute(currentUserPersonalDetails.accountID, activeRoute));
return;
}

if (option.accountID) {
Navigation.navigate(ROUTES.PROFILE.getRoute(option.accountID, activeRoute));
} else if (option.reportID) {
Expand Down
7 changes: 6 additions & 1 deletion src/components/OptionRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,12 @@ function OptionRow({
numberOfLines={isMultilineSupported ? 2 : 1}
textStyles={displayNameStyle}
shouldUseFullTitle={
!!option.isChatRoom || !!option.isPolicyExpenseChat || !!option.isMoneyRequestReport || !!option.isThread || !!option.isTaskReport
!!option.isChatRoom ||
!!option.isPolicyExpenseChat ||
!!option.isMoneyRequestReport ||
!!option.isThread ||
!!option.isTaskReport ||
!!option.isSelfDM
}
/>
{option.alternateText ? (
Expand Down
20 changes: 16 additions & 4 deletions src/components/ReportActionItem/MoneyRequestAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
Expand Down Expand Up @@ -80,7 +81,8 @@ function MoneyRequestAction({
const {translate} = useLocalize();
const {isOffline} = useNetwork();

const isSplitBillAction = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT;
const isSplitBillAction = ReportActionsUtils.isSplitBillAction(action);
const isTrackExpenseAction = ReportActionsUtils.isTrackExpenseAction(action);

const onMoneyRequestPreviewPressed = () => {
if (isSplitBillAction) {
Expand Down Expand Up @@ -108,14 +110,24 @@ function MoneyRequestAction({
shouldShowPendingConversionMessage = IOUUtils.isIOUReportPendingCurrencyConversion(iouReport);
}

return isDeletedParentAction || isReversedTransaction ? (
<RenderHTML html={`<comment>${translate(isReversedTransaction ? 'parentReportAction.reversedTransaction' : 'parentReportAction.deletedRequest')}</comment>`} />
) : (
if (isDeletedParentAction || isReversedTransaction) {
let message: TranslationPaths;
if (isReversedTransaction) {
message = 'parentReportAction.reversedTransaction';
} else if (isTrackExpenseAction) {
message = 'parentReportAction.deletedExpense';
} else {
message = 'parentReportAction.deletedRequest';
}
return <RenderHTML html={`<comment>${translate(message)}</comment>`} />;
}
return (
<MoneyRequestPreview
iouReportID={requestReportID}
chatReportID={chatReportID}
reportID={reportID}
isBillSplit={isSplitBillAction}
isTrackExpense={isTrackExpenseAction}
action={action}
contextMenuAnchor={contextMenuAnchor}
checkIfContextMenuActive={checkIfContextMenuActive}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ReportActionItem/MoneyRequestPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import MoneyRequestPreviewContent from './MoneyRequestPreviewContent';
import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './types';

function MoneyRequestPreview(props: MoneyRequestPreviewProps) {
// We should not render the component if there is no iouReport and it's not a split.
// We should not render the component if there is no iouReport and it's not a split or track expense.
// Moved outside of the component scope to allow for easier use of hooks in the main component.
// eslint-disable-next-line react/jsx-props-no-spreading
return lodashIsEmpty(props.iouReport) && !props.isBillSplit ? null : <MoneyRequestPreviewContent {...props} />;
return lodashIsEmpty(props.iouReport) && !(props.isBillSplit || props.isTrackExpense) ? null : <MoneyRequestPreviewContent {...props} />;
}

MoneyRequestPreview.displayName = 'MoneyRequestPreview';
Expand Down
3 changes: 3 additions & 0 deletions src/components/ReportActionItem/MoneyRequestPreview/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & {
/** True if this is this IOU is a split instead of a 1:1 request */
isBillSplit: boolean;

/** Whether this IOU is a track expense */
isTrackExpense: boolean;

/** True if the IOU Preview card is hovered */
isHovered?: boolean;

Expand Down
10 changes: 6 additions & 4 deletions src/components/ReportWelcomeText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import usePermissions from '@hooks/usePermissions';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
Expand Down Expand Up @@ -33,6 +34,7 @@ type ReportWelcomeTextProps = ReportWelcomeTextOnyxProps & {
function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {canUseTrackExpense} = usePermissions();
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isChatRoom = ReportUtils.isChatRoom(report);
const isSelfDM = ReportUtils.isSelfDM(report);
Expand All @@ -42,7 +44,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant);
const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy);
const roomWelcomeMessage = ReportUtils.getRoomWelcomeMessage(report, isUserPolicyAdmin);
const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, policy, participantAccountIDs);
const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, policy, participantAccountIDs, canUseTrackExpense);
const additionalText = moneyRequestOptions.map((item) => translate(`reportActionsView.iouTypes.${item}`)).join(', ');
const canEditPolicyDescription = ReportUtils.canEditPolicyDescription(policy);
const reportName = ReportUtils.getReportName(report);
Expand Down Expand Up @@ -158,9 +160,9 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
))}
</Text>
)}
{(moneyRequestOptions.includes(CONST.IOU.TYPE.SEND) || moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)) && (
<Text>{translate('reportActionsView.usePlusButton', {additionalText})}</Text>
)}
{(moneyRequestOptions.includes(CONST.IOU.TYPE.SEND) ||
moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST) ||
moneyRequestOptions.includes(CONST.IOU.TYPE.TRACK_EXPENSE)) && <Text>{translate('reportActionsView.usePlusButton', {additionalText})}</Text>}
</View>
</>
);
Expand Down
17 changes: 14 additions & 3 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,9 +462,14 @@ export default {
copyEmailToClipboard: 'Copy email to clipboard',
markAsUnread: 'Mark as unread',
markAsRead: 'Mark as read',
editAction: ({action}: EditActionParams) => `Edit ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'request' : 'comment'}`,
deleteAction: ({action}: DeleteActionParams) => `Delete ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'request' : 'comment'}`,
deleteConfirmation: ({action}: DeleteConfirmationParams) => `Are you sure you want to delete this ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'request' : 'comment'}?`,
editAction: ({action}: EditActionParams) =>
`Edit ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'expense' : 'request'}` : 'comment'}`,
deleteAction: ({action}: DeleteActionParams) =>
`Delete ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'expense' : 'request'}` : 'comment'}`,
deleteConfirmation: ({action}: DeleteConfirmationParams) =>
`Are you sure you want to delete this ${
action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'expense' : 'request'}` : 'comment'
}?`,
onlyVisible: 'Only visible to',
replyInThread: 'Reply in thread',
joinThread: 'Join thread',
Expand Down Expand Up @@ -503,6 +508,8 @@ export default {
send: 'send money',
split: 'split a bill',
request: 'request money',
// eslint-disable-next-line @typescript-eslint/naming-convention
'track-expense': 'track an expense',
},
},
reportAction: {
Expand Down Expand Up @@ -593,6 +600,7 @@ export default {
participants: 'Participants',
requestMoney: 'Request money',
sendMoney: 'Send money',
trackExpense: 'Track expense',
pay: 'Pay',
cancelPayment: 'Cancel payment',
cancelPaymentConfirmation: 'Are you sure that you want to cancel this payment?',
Expand Down Expand Up @@ -624,6 +632,7 @@ export default {
finished: 'Finished',
requestAmount: ({amount}: RequestAmountParams) => `request ${amount}`,
requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `requested ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
trackedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `tracking ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`,
didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `split ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
amountEach: ({amount}: AmountEachParams) => `${amount} each`,
Expand Down Expand Up @@ -655,6 +664,7 @@ export default {
updatedTheDistance: ({newDistanceToDisplay, oldDistanceToDisplay, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceParams) =>
`changed the distance to ${newDistanceToDisplay} (previously ${oldDistanceToDisplay}), which updated the amount to ${newAmountToDisplay} (previously ${oldAmountToDisplay})`,
threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `for ${comment}` : 'request'}`,
threadTrackReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `Tracking ${formattedAmount} ${comment ? `for ${comment}` : ''}`,
threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`,
tagSelection: 'Select a tag to better organize your spend.',
categorySelection: 'Select a category to better organize your spend.',
Expand Down Expand Up @@ -2335,6 +2345,7 @@ export default {
deletedReport: '[Deleted report]',
deletedMessage: '[Deleted message]',
deletedRequest: '[Deleted request]',
deletedExpense: '[Deleted expense]',
reversedTransaction: '[Reversed transaction]',
deletedTask: '[Deleted task]',
hiddenMessage: '[Hidden message]',
Expand Down
Loading

0 comments on commit 2571c8d

Please sign in to comment.