Skip to content

Commit

Permalink
Merge pull request #51329 from Expensify/feature-attendeeTracking
Browse files Browse the repository at this point in the history
[NO QA] Merge the attendee tracking changes, but block use
  • Loading branch information
tgolen authored Oct 24, 2024
2 parents 81e877a + d3b96df commit 642fd97
Show file tree
Hide file tree
Showing 37 changed files with 821 additions and 35 deletions.
3 changes: 3 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ const CONST = {
// Regex to get link in href prop inside of <a/> component
REGEX_LINK_IN_ANCHOR: /<a\s+(?:[^>]*?\s+)?href="([^"]*)"/gi,

// Regex to read violation value from string given by backend
VIOLATION_LIMIT_REGEX: /[^0-9]+/g,

MERCHANT_NAME_MAX_LENGTH: 255,

MASKED_PAN_PREFIX: 'XXXXXXXXXXXX',
Expand Down
5 changes: 5 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type CONST from './CONST';
import type {OnboardingCompanySizeType, OnboardingPurposeType} from './CONST';
import type * as FormTypes from './types/form';
import type * as OnyxTypes from './types/onyx';
import type {Attendee} from './types/onyx/IOU';
import type Onboarding from './types/onyx/Onboarding';
import type AssertTypesEqual from './types/utils/AssertTypesEqual';
import type DeepValueOf from './types/utils/DeepValueOf';
Expand Down Expand Up @@ -112,6 +113,9 @@ const ONYXKEYS = {
/** Boolean flag only true when first set */
NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'nvp_isFirstTimeNewExpensifyUser',

/** This NVP contains list of at most 5 recent attendees */
NVP_RECENT_ATTENDEES: 'nvp_expensify_recentAttendees',

/** This NVP contains information about whether the onboarding flow was completed or not */
NVP_ONBOARDING: 'nvp_onboarding',

Expand Down Expand Up @@ -905,6 +909,7 @@ type OnyxValuesMapping = {
// The value of this nvp is a string representation of the date when the block expires, or an empty string if the user is not blocked
[ONYXKEYS.NVP_BLOCKED_FROM_CHAT]: string;
[ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]: string;
[ONYXKEYS.NVP_RECENT_ATTENDEES]: Attendee[];
[ONYXKEYS.NVP_TRY_FOCUS_MODE]: boolean;
[ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION]: boolean;
[ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean;
Expand Down
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,11 @@ const ROUTES = {
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '', reportActionID?: string) =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/category/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo),
},
MONEY_REQUEST_ATTENDEE: {
route: ':action/:iouType/attendees/:transactionID/:reportID',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/attendees/${transactionID}/${reportID}`, backTo),
},
SETTINGS_TAGS_ROOT: {
route: 'settings/:policyID/tags',
getRoute: (policyID: string, backTo = '') => getUrlWithBackToParam(`settings/${policyID}/tags`, backTo),
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ const SCREENS = {
EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint',
RECEIPT: 'Money_Request_Receipt',
STATE_SELECTOR: 'Money_Request_State_Selector',
STEP_ATTENDEES: 'Money_Request_Attendee',
},

TRANSACTION_DUPLICATE: {
Expand Down
16 changes: 14 additions & 2 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';
import type {Attendee, Participant} from '@src/types/onyx/IOU';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type {SplitShares} from '@src/types/onyx/Transaction';
import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
Expand Down Expand Up @@ -85,6 +85,9 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps &
/** IOU amount */
iouAmount: number;

/** IOU attendees list */
iouAttendees?: Attendee[];

/** IOU comment */
iouComment?: string;

Expand Down Expand Up @@ -194,6 +197,7 @@ function MoneyRequestConfirmationList({
policyID = '',
reportID = '',
receiptPath = '',
iouAttendees,
iouComment,
receiptFilename = '',
iouCreated,
Expand Down Expand Up @@ -288,7 +292,12 @@ function MoneyRequestConfirmationList({
const formattedAmount = isDistanceRequestWithPendingRoute
? ''
: CurrencyUtils.convertToDisplayString(shouldCalculateDistanceAmount ? distanceRequestAmount : iouAmount, isDistanceRequest ? currency : iouCurrencyCode);

const formattedAmountPerAttendee = isDistanceRequestWithPendingRoute
? ''
: CurrencyUtils.convertToDisplayString(
(shouldCalculateDistanceAmount ? distanceRequestAmount : iouAmount) / (iouAttendees?.length && iouAttendees.length > 0 ? iouAttendees.length : 1),
isDistanceRequest ? currency : iouCurrencyCode,
);
const isFocused = useIsFocused();
const [formError, debouncedFormError, setFormError] = useDebouncedState<TranslationPaths | ''>('');

Expand Down Expand Up @@ -899,8 +908,10 @@ function MoneyRequestConfirmationList({
didConfirm={!!didConfirm}
distance={distance}
formattedAmount={formattedAmount}
formattedAmountPerAttendee={formattedAmountPerAttendee}
formError={formError}
hasRoute={hasRoute}
iouAttendees={iouAttendees}
iouCategory={iouCategory}
iouComment={iouComment}
iouCreated={iouCreated}
Expand Down Expand Up @@ -1010,6 +1021,7 @@ export default withOnyx<MoneyRequestConfirmationListProps, MoneyRequestConfirmat
prevProps.policyID === nextProps.policyID &&
prevProps.reportID === nextProps.reportID &&
prevProps.receiptPath === nextProps.receiptPath &&
prevProps.iouAttendees === nextProps.iouAttendees &&
prevProps.iouComment === nextProps.iouComment &&
prevProps.receiptFilename === nextProps.receiptFilename &&
prevProps.iouCreated === nextProps.iouCreated &&
Expand Down
30 changes: 29 additions & 1 deletion src/components/MoneyRequestConfirmationListFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type {IOUAction, IOUType} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';
import type {Attendee, Participant} from '@src/types/onyx/IOU';
import type {Unit} from '@src/types/onyx/Policy';
import ConfirmedRoute from './ConfirmedRoute';
import MentionReportContext from './HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext';
Expand Down Expand Up @@ -57,6 +57,9 @@ type MoneyRequestConfirmationListFooterProps = {
/** The formatted amount of the transaction */
formattedAmount: string;

/** The formatted amount of the transaction per 1 attendee */
formattedAmountPerAttendee: string;

/** The error message for the form */
formError: string;

Expand All @@ -66,6 +69,9 @@ type MoneyRequestConfirmationListFooterProps = {
/** The category of the IOU */
iouCategory: string;

/** The list of attendees */
iouAttendees: Attendee[] | undefined;

/** The comment of the IOU */
iouComment: string | undefined;

Expand Down Expand Up @@ -176,8 +182,10 @@ function MoneyRequestConfirmationListFooter({
didConfirm,
distance,
formattedAmount,
formattedAmountPerAttendee,
formError,
hasRoute,
iouAttendees,
iouCategory,
iouComment,
iouCreated,
Expand Down Expand Up @@ -226,6 +234,7 @@ function MoneyRequestConfirmationListFooter({

const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]);
const isMultilevelTags = useMemo(() => PolicyUtils.isMultiLevelTags(policyTags), [policyTags]);
const shouldShowAttendees = useMemo(() => TransactionUtils.shouldShowAttendees(iouType, policy), [iouType, policy]);

const senderWorkspace = useMemo(() => {
const senderWorkspaceParticipant = selectedParticipants.find((participant) => participant.isSender);
Expand Down Expand Up @@ -514,6 +523,25 @@ function MoneyRequestConfirmationListFooter({
shouldShow: shouldShowTax,
isSupplementary: true,
},
{
item: (
<MenuItemWithTopDescription
key="attendees"
shouldShowRightIcon
title={iouAttendees?.map((item) => item?.displayName ?? item?.login).join(', ')}
description={`${translate('iou.attendees')} ${
iouAttendees?.length && iouAttendees.length > 1 ? `\u00B7 ${formattedAmountPerAttendee} ${translate('common.perPerson')}` : ''
}`}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_ATTENDEE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
interactive
shouldRenderAsHTML
/>
),
shouldShow: shouldShowAttendees,
isSupplementary: true,
},
{
item: (
<View
Expand Down
22 changes: 22 additions & 0 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
const {
created: transactionDate,
amount: transactionAmount,
attendees: transactionAttendees,
taxAmount: transactionTaxAmount,
currency: transactionCurrency,
comment: transactionDescription,
Expand All @@ -128,6 +129,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction);
const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : '';
const formattedPerAttendeeAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount / (transactionAttendees?.length ?? 1), transactionCurrency) : '';
const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && CurrencyUtils.convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency);
const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
const cardProgramName = TransactionUtils.getCardName(transaction);
Expand Down Expand Up @@ -184,6 +186,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists));
const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true) || !!updatedTransaction?.billable);
const shouldShowAttendees = useMemo(() => TransactionUtils.shouldShowAttendees(iouType, policy), [iouType, policy]);

const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest);
const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport);
Expand Down Expand Up @@ -721,6 +724,25 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
}}
/>
)}
{shouldShowAttendees && (
<OfflineWithFeedback pendingAction={getPendingFieldAction('attendees')}>
<MenuItemWithTopDescription
key="attendees"
shouldShowRightIcon
title={transactionAttendees?.map((item) => item?.displayName ?? item?.login).join(', ')}
description={`${translate('iou.attendees')} ${
transactionAttendees?.length && transactionAttendees.length > 1 ? `${formattedPerAttendeeAmount} ${translate('common.perPerson')}` : ''
}`}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() =>
Navigation.navigate(ROUTES.MONEY_REQUEST_ATTENDEE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
}
interactive
shouldRenderAsHTML
/>
</OfflineWithFeedback>
)}
{shouldShowBillable && (
<View style={[styles.flexRow, styles.optionRow, styles.justifyContentBetween, styles.alignItemsCenter, styles.ml5, styles.mr8]}>
<View>
Expand Down
4 changes: 4 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
// eslint-disable-next-line no-restricted-imports
import type CursorStyles from '@styles/utils/cursor/types';
import type CONST from '@src/CONST';
import type {Attendee} from '@src/types/onyx/IOU';
import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {SearchPersonalDetails, SearchReport, SearchReportAction, SearchTransaction} from '@src/types/onyx/SearchResults';
import type {ReceiptErrors} from '@src/types/onyx/Transaction';
Expand Down Expand Up @@ -234,6 +235,9 @@ type TransactionListItemType = ListItem &

/** Key used internally by React */
keyForList: string;

/** Attendees in the transaction */
attendees?: Attendee[];
};

type ReportActionListItemType = ListItem &
Expand Down
3 changes: 3 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ const translations = {
firstName: 'First name',
lastName: 'Last name',
addCardTermsOfService: 'Expensify Terms of Service',
perPerson: 'per person',
phone: 'Phone',
phoneNumber: 'Phone number',
phoneNumberPlaceholder: '(xxx) xxx-xxxx',
Expand Down Expand Up @@ -974,6 +975,7 @@ const translations = {
atLeastTwoDifferentWaypoints: 'Please enter at least two different addresses.',
splitExpenseMultipleParticipantsErrorMessage: 'An expense cannot be split between a workspace and other members. Please update your selection.',
invalidMerchant: 'Please enter a correct merchant.',
atLeastOneAttendee: 'At least one attendee must be selected',
},
waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up. Payment is on hold until ${submitterDisplayName} enables their wallet.`,
enableWallet: 'Enable wallet',
Expand Down Expand Up @@ -1029,6 +1031,7 @@ const translations = {
bookingPendingDescription: "This booking is pending because it hasn't been paid yet.",
bookingArchived: 'This booking is archived',
bookingArchivedDescription: 'This booking is archived because the trip date has passed. Add an expense for the final amount if needed.',
attendees: 'Attendees',
paymentComplete: 'Payment complete',
justTrackIt: 'Just track it (don’t submit it)',
},
Expand Down
3 changes: 3 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ const translations = {
not: 'No',
privacyPolicy: 'la Política de Privacidad de Expensify',
addCardTermsOfService: 'Términos de Servicio',
perPerson: 'por persona',
signIn: 'Conectarse',
signInWithGoogle: 'Iniciar sesión con Google',
signInWithApple: 'Iniciar sesión con Apple',
Expand Down Expand Up @@ -971,6 +972,7 @@ const translations = {
atLeastTwoDifferentWaypoints: 'Por favor, introduce al menos dos direcciones diferentes.',
splitExpenseMultipleParticipantsErrorMessage: 'Solo puedes dividir un gasto entre un único espacio de trabajo o con miembros individuales. Por favor, actualiza tu selección.',
invalidMerchant: 'Por favor, introduce un comerciante correcto.',
atLeastOneAttendee: 'Debe seleccionarse al menos un asistente',
},
waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inició el pago, pero no se procesará hasta que ${submitterDisplayName} active su billetera`,
enableWallet: 'Habilitar billetera',
Expand Down Expand Up @@ -1026,6 +1028,7 @@ const translations = {
bookingPendingDescription: 'Esta reserva está pendiente porque aún no se ha pagado.',
bookingArchived: 'Esta reserva está archivada',
bookingArchivedDescription: 'Esta reserva está archivada porque la fecha del viaje ha pasado. Agregue un gasto por el monto final si es necesario.',
attendees: 'Asistentes',
paymentComplete: 'Pago completo',
justTrackIt: 'Solo guardarlo (no enviarlo)',
},
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ const WRITE_COMMANDS = {
DELETE_REPORT_FIELD: 'RemoveReportField',
SET_REPORT_NAME: 'RenameReport',
COMPLETE_SPLIT_BILL: 'CompleteSplitBill',
UPDATE_MONEY_REQUEST_ATTENDEES: 'UpdateMoneyRequestAttendees',
UPDATE_MONEY_REQUEST_DATE: 'UpdateMoneyRequestDate',
UPDATE_MONEY_REQUEST_BILLABLE: 'UpdateMoneyRequestBillable',
UPDATE_MONEY_REQUEST_MERCHANT: 'UpdateMoneyRequestMerchant',
Expand Down Expand Up @@ -592,6 +593,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams;
[WRITE_COMMANDS.DELETE_REPORT_FIELD]: Parameters.DeleteReportFieldParams;
[WRITE_COMMANDS.COMPLETE_SPLIT_BILL]: Parameters.CompleteSplitBillParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_ATTENDEES]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DATE]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_MERCHANT]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE]: Parameters.UpdateMoneyRequestParams;
Expand Down
22 changes: 21 additions & 1 deletion src/libs/IOUUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {OnyxInputOrEntry, Report, Transaction} from '@src/types/onyx';
import type {OnyxInputOrEntry, PersonalDetails, Report, Transaction} from '@src/types/onyx';
import type {Attendee} from '@src/types/onyx/IOU';
import type {IOURequestType} from './actions/IOU';
import * as CurrencyUtils from './CurrencyUtils';
import DateUtils from './DateUtils';
Expand Down Expand Up @@ -162,6 +163,24 @@ function shouldUseTransactionDraft(action: IOUAction | undefined) {
return action === CONST.IOU.ACTION.CREATE || isMovingTransactionFromTrackExpense(action);
}

function formatCurrentUserToAttendee(currentUser?: PersonalDetails, reportID?: string) {
if (!currentUser) {
return;
}
const initialAttendee: Attendee = {
email: currentUser?.login,
login: currentUser?.login,
displayName: currentUser.displayName,
avatarUrl: currentUser.avatar?.toString(),
accountID: currentUser.accountID,
text: currentUser.login,
selected: true,
reportID,
};

return [initialAttendee];
}

function shouldStartLocationPermissionFlow() {
return (
!lastLocationPermissionPrompt ||
Expand All @@ -179,5 +198,6 @@ export {
isValidMoneyRequestType,
navigateToStartMoneyRequestStep,
updateIOUOwnerAndTotal,
formatCurrentUserToAttendee,
shouldStartLocationPermissionFlow,
};
13 changes: 13 additions & 0 deletions src/libs/ModifiedExpenseMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,19 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr
);
}

const hasModifiedAttendees = isReportActionOriginalMessageAnObject && 'oldAttendees' in reportActionOriginalMessage && 'attendees' in reportActionOriginalMessage;
if (hasModifiedAttendees) {
buildMessageFragmentForValue(
reportActionOriginalMessage.oldAttendees ?? '',
reportActionOriginalMessage.attendees ?? '',
Localize.translateLocal('iou.attendees'),
false,
setFragments,
removalFragments,
changeFragments,
);
}

const message =
getMessageLine(`\n${Localize.translateLocal('iou.changed')}`, changeFragments) +
getMessageLine(`\n${Localize.translateLocal('iou.set')}`, setFragments) +
Expand Down
Loading

0 comments on commit 642fd97

Please sign in to comment.