Skip to content

Commit

Permalink
Merge pull request #24533 from fedirjh/Implment_Details_Page_For_IOU
Browse files Browse the repository at this point in the history
Implement details page for expense/iouReport
  • Loading branch information
grgia authored Oct 3, 2023
2 parents 82fe5a6 + 498d224 commit d4746a8
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 63 deletions.
29 changes: 26 additions & 3 deletions src/components/AvatarWithDisplayName.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const propTypes = {
/** Whether if it's an unauthenticated user */
isAnonymous: PropTypes.bool,

shouldEnableDetailPageNavigation: PropTypes.bool,

...windowDimensionsPropTypes,
...withLocalizePropTypes,
};
Expand All @@ -53,9 +55,15 @@ const defaultProps = {
report: {},
isAnonymous: false,
size: CONST.AVATAR_SIZE.DEFAULT,
shouldEnableDetailPageNavigation: false,
};

const showActorDetails = (report) => {
const showActorDetails = (report, shouldEnableDetailPageNavigation = false) => {
// We should navigate to the details page if the report is a IOU/expense report
if (shouldEnableDetailPageNavigation) {
return ReportUtils.navigateToDetailsPage(report);
}

if (ReportUtils.isExpenseReport(report)) {
Navigation.navigate(ROUTES.PROFILE.getRoute(report.ownerAccountID));
return;
Expand Down Expand Up @@ -93,12 +101,12 @@ function AvatarWithDisplayName(props) {
const defaultSubscriptSize = isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : props.size;
const avatarBorderColor = props.isAnonymous ? themeColors.highlightBG : themeColors.componentBG;

return (
const headerView = (
<View style={[styles.appContentHeaderTitle, styles.flex1]}>
{Boolean(props.report && title) && (
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween]}>
<PressableWithoutFeedback
onPress={() => showActorDetails(props.report)}
onPress={() => showActorDetails(props.report, props.shouldEnableDetailPageNavigation)}
accessibilityLabel={title}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
>
Expand Down Expand Up @@ -145,6 +153,21 @@ function AvatarWithDisplayName(props) {
)}
</View>
);

if (!props.shouldEnableDetailPageNavigation) {
return headerView;
}

return (
<PressableWithoutFeedback
onPress={() => ReportUtils.navigateToDetailsPage(props.report)}
style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]}
accessibilityLabel={title}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
>
{headerView}
</PressableWithoutFeedback>
);
}
AvatarWithDisplayName.propTypes = propTypes;
AvatarWithDisplayName.displayName = 'AvatarWithDisplayName';
Expand Down
7 changes: 4 additions & 3 deletions src/components/HeaderWithBackButton/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function HeaderWithBackButton({
horizontal: 0,
},
threeDotsMenuItems = [],
shouldEnableDetailPageNavigation = false,
children = null,
onModalHide = () => {},
shouldOverlay = false,
Expand Down Expand Up @@ -76,14 +77,14 @@ function HeaderWithBackButton({
</PressableWithoutFeedback>
</Tooltip>
)}
{shouldShowAvatarWithDisplay && (
{shouldShowAvatarWithDisplay ? (
<AvatarWithDisplayName
report={report}
policy={policy}
personalDetails={personalDetails}
shouldEnableDetailPageNavigation={shouldEnableDetailPageNavigation}
/>
)}
{!shouldShowAvatarWithDisplay && (
) : (
<Header
title={title}
subtitle={stepCounter ? translate('stepCounter', stepCounter) : subtitle}
Expand Down
1 change: 1 addition & 0 deletions src/components/MoneyReportHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report
<View style={[styles.pt0]}>
<HeaderWithBackButton
shouldShowAvatarWithDisplay
shouldEnableDetailPageNavigation
shouldShowPinButton={false}
report={moneyRequestReport}
policy={policy}
Expand Down
45 changes: 40 additions & 5 deletions src/components/MultipleAvatars.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ const defaultProps = {
maxAvatarsInRow: CONST.AVATAR_ROW_SIZE.DEFAULT,
};

const avatarSizeToStylesMap = {
[CONST.AVATAR_SIZE.SMALL]: {
singleAvatarStyle: styles.singleAvatarSmall,
secondAvatarStyles: styles.secondAvatarSmall,
},
[CONST.AVATAR_SIZE.LARGE]: {
singleAvatarStyle: styles.singleAvatarMedium,
secondAvatarStyles: styles.secondAvatarMedium,
},
default: {
singleAvatarStyle: styles.singleAvatar,
secondAvatarStyles: styles.secondAvatar,
},
};

function getContainerStyles(size, isInReportAction) {
let containerStyles;

Expand All @@ -84,6 +99,9 @@ function getContainerStyles(size, isInReportAction) {
case CONST.AVATAR_SIZE.MEDIUM:
containerStyles = [styles.emptyAvatarMedium, styles.emptyAvatarMargin];
break;
case CONST.AVATAR_SIZE.LARGE:
containerStyles = [styles.emptyAvatarLarge, styles.mb2, styles.mr2];
break;
default:
containerStyles = [styles.emptyAvatar, isInReportAction ? styles.emptyAvatarMarginChat : styles.emptyAvatarMargin];
}
Expand All @@ -92,9 +110,20 @@ function getContainerStyles(size, isInReportAction) {
}
function MultipleAvatars(props) {
let avatarContainerStyles = getContainerStyles(props.size, props.isInReportAction);
const singleAvatarStyle = props.size === CONST.AVATAR_SIZE.SMALL ? styles.singleAvatarSmall : styles.singleAvatar;
const secondAvatarStyles = [props.size === CONST.AVATAR_SIZE.SMALL ? styles.secondAvatarSmall : styles.secondAvatar, ...props.secondAvatarStyle];
const {singleAvatarStyle, secondAvatarStyles} = useMemo(() => avatarSizeToStylesMap[props.size] || avatarSizeToStylesMap.default, [props.size]);

const tooltipTexts = props.shouldShowTooltip ? _.pluck(props.icons, 'name') : [''];
const avatarSize = useMemo(() => {
if (props.isFocusMode) {
return CONST.AVATAR_SIZE.MID_SUBSCRIPT;
}

if (props.size === CONST.AVATAR_SIZE.LARGE) {
return CONST.AVATAR_SIZE.MEDIUM;
}

return CONST.AVATAR_SIZE.SMALLER;
}, [props.isFocusMode, props.size]);

const avatarRows = useMemo(() => {
// If we're not displaying avatars in rows or the number of icons is less than or equal to the max avatars in a row, return a single row
Expand Down Expand Up @@ -247,15 +276,21 @@ function MultipleAvatars(props) {
<Avatar
source={props.icons[0].source || props.fallbackIcon}
fill={themeColors.iconSuccessFill}
size={props.isFocusMode ? CONST.AVATAR_SIZE.MID_SUBSCRIPT : CONST.AVATAR_SIZE.SMALLER}
size={avatarSize}
imageStyles={[singleAvatarStyle]}
name={props.icons[0].name}
type={props.icons[0].type}
fallbackIcon={props.icons[0].fallbackIcon}
/>
</View>
</UserDetailsTooltip>
<View style={[...secondAvatarStyles, props.icons[1].type === CONST.ICON_TYPE_WORKSPACE ? StyleUtils.getAvatarBorderRadius(props.size, props.icons[1].type) : {}]}>
<View
style={[
secondAvatarStyles,
...props.secondAvatarStyle,
props.icons[1].type === CONST.ICON_TYPE_WORKSPACE ? StyleUtils.getAvatarBorderRadius(props.size, props.icons[1].type) : {},
]}
>
{props.icons.length === 2 ? (
<UserDetailsTooltip
accountID={props.icons[1].id}
Expand All @@ -269,7 +304,7 @@ function MultipleAvatars(props) {
<Avatar
source={props.icons[1].source || props.fallbackIcon}
fill={themeColors.iconSuccessFill}
size={props.isFocusMode ? CONST.AVATAR_SIZE.MID_SUBSCRIPT : CONST.AVATAR_SIZE.SMALLER}
size={avatarSize}
imageStyles={[singleAvatarStyle]}
name={props.icons[1].name}
type={props.icons[1].type}
Expand Down
34 changes: 30 additions & 4 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1666,7 +1666,7 @@ function getModifiedExpenseMessage(reportAction) {
*
* @param {Object} oldTransaction
* @param {Object} transactionChanges
* @param {Boolen} isFromExpenseReport
* @param {Boolean} isFromExpenseReport
* @returns {Object}
*/
function getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, isFromExpenseReport) {
Expand Down Expand Up @@ -1893,7 +1893,7 @@ function getParentNavigationSubtitle(report) {
function navigateToDetailsPage(report) {
const participantAccountIDs = lodashGet(report, 'participantAccountIDs', []);

if (isChatRoom(report) || isPolicyExpenseChat(report) || isChatThread(report) || isTaskReport(report)) {
if (isChatRoom(report) || isPolicyExpenseChat(report) || isChatThread(report) || isTaskReport(report) || isMoneyRequestReport(report)) {
Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
return;
}
Expand Down Expand Up @@ -2119,6 +2119,7 @@ function buildOptimisticIOUReport(payeeAccountID, payerAccountID, total, chatRep

// We don't translate reportName because the server response is always in English
reportName: `${payerEmail} owes ${formattedTotal}`,
notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
parentReportID: chatReportID,
};
}
Expand Down Expand Up @@ -2157,6 +2158,7 @@ function buildOptimisticExpenseReport(chatReportID, policyID, payeeAccountID, to
state: CONST.REPORT.STATE.SUBMITTED,
stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
total: storedTotal,
notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
parentReportID: chatReportID,
};
}
Expand Down Expand Up @@ -3535,7 +3537,7 @@ function getPolicyExpenseChatReportIDByOwner(policyOwner) {
* @returns {Boolean}
*/
function shouldDisableSettings(report) {
return !isPolicyExpenseChat(report) && !isChatRoom(report) && !isChatThread(report);
return !isMoneyRequestReport(report) && !isPolicyExpenseChat(report) && !isChatRoom(report) && !isChatThread(report);
}

/**
Expand All @@ -3544,7 +3546,7 @@ function shouldDisableSettings(report) {
* @returns {Boolean}
*/
function shouldDisableRename(report, policy) {
if (isDefaultRoom(report) || isArchivedRoom(report) || isChatThread(report)) {
if (isDefaultRoom(report) || isArchivedRoom(report) || isChatThread(report) || isMoneyRequestReport(report) || isPolicyExpenseChat(report)) {
return true;
}

Expand Down Expand Up @@ -3676,6 +3678,29 @@ function getTaskAssigneeChatOnyxData(accountID, assigneeEmail, assigneeAccountID
};
}

/**
* Returns an array of the participants Ids of a report
*
* @param {Object} report
* @returns {Array}
*/
function getParticipantsIDs(report) {
if (!report) {
return [];
}

const participants = report.participantAccountIDs || [];

// Build participants list for IOU/expense reports
if (isMoneyRequestReport(report)) {
return _.chain([report.managerID, report.ownerAccountID, ...participants])
.compact()
.uniq()
.value();
}
return participants;
}

/**
* Get the last 3 transactions with receipts of an IOU report that will be displayed on the report preview
*
Expand Down Expand Up @@ -3871,6 +3896,7 @@ export {
getTransactionReportName,
getTransactionDetails,
getTaskAssigneeChatOnyxData,
getParticipantsIDs,
canEditMoneyRequest,
buildTransactionThread,
areAllRequestsBeingSmartScanned,
Expand Down
29 changes: 24 additions & 5 deletions src/pages/ReportDetailsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import {View, ScrollView} from 'react-native';
import lodashGet from 'lodash/get';
import RoomHeaderAvatars from '../components/RoomHeaderAvatars';
import compose from '../libs/compose';
import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
Expand All @@ -27,6 +26,8 @@ import reportPropTypes from './reportPropTypes';
import withReportOrNotFound from './home/report/withReportOrNotFound';
import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView';
import PressableWithoutFeedback from '../components/Pressable/PressableWithoutFeedback';
import ParentNavigationSubtitle from '../components/ParentNavigationSubtitle';
import MultipleAvatars from '../components/MultipleAvatars';

const propTypes = {
...withLocalizePropTypes,
Expand Down Expand Up @@ -66,11 +67,13 @@ function ReportDetailsPage(props) {
const isThread = useMemo(() => ReportUtils.isChatThread(props.report), [props.report]);
const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(props.report), [props.report]);
const isArchivedRoom = useMemo(() => ReportUtils.isArchivedRoom(props.report), [props.report]);
const isMoneyRequestReport = useMemo(() => ReportUtils.isMoneyRequestReport(props.report), [props.report]);

// eslint-disable-next-line react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx
const chatRoomSubtitle = useMemo(() => ReportUtils.getChatRoomSubtitle(props.report), [props.report, policy]);
const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report);
const canLeaveRoom = useMemo(() => ReportUtils.canLeaveRoom(props.report, !_.isEmpty(policy)), [policy, props.report]);
const participants = useMemo(() => lodashGet(props.report, 'participantAccountIDs', []), [props.report]);
const participants = useMemo(() => ReportUtils.getParticipantsIDs(props.report), [props.report]);

const menuItems = useMemo(() => {
const items = [
Expand Down Expand Up @@ -113,7 +116,7 @@ function ReportDetailsPage(props) {
}

// Prevent displaying private notes option for threads and task reports
if (!isThread && !ReportUtils.isTaskReport(props.report)) {
if (!isThread && !isMoneyRequestReport && !ReportUtils.isTaskReport(props.report)) {
items.push({
key: CONST.REPORT_DETAILS_MENU_ITEM.PRIVATE_NOTES,
translationKey: 'privateNotes.title',
Expand All @@ -135,13 +138,15 @@ function ReportDetailsPage(props) {
}

return items;
}, [props.report, participants, isArchivedRoom, shouldDisableSettings, isThread, isUserCreatedPolicyRoom, canLeaveRoom]);
}, [isArchivedRoom, participants.length, shouldDisableSettings, isThread, isMoneyRequestReport, props.report, isUserCreatedPolicyRoom, canLeaveRoom]);

const displayNamesWithTooltips = useMemo(() => {
const hasMultipleParticipants = participants.length > 1;
return ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails), hasMultipleParticipants);
}, [participants, props.personalDetails]);

const icons = useMemo(() => ReportUtils.getIcons(props.report, props.personalDetails, props.policies), [props.report, props.personalDetails, props.policies]);

const chatRoomSubtitleText = chatRoomSubtitle ? (
<Text
style={[styles.sidebarLinkText, styles.textLabelSupporting, styles.pre, styles.mt1]}
Expand All @@ -158,7 +163,14 @@ function ReportDetailsPage(props) {
<ScrollView style={[styles.flex1]}>
<View style={styles.reportDetailsTitleContainer}>
<View style={styles.mb3}>
<RoomHeaderAvatars icons={ReportUtils.getIcons(props.report, props.personalDetails, props.policies)} />
{isMoneyRequestReport ? (
<MultipleAvatars
icons={icons}
size={CONST.AVATAR_SIZE.LARGE}
/>
) : (
<RoomHeaderAvatars icons={icons} />
)}
</View>
<View style={[styles.reportDetailsRoomInfo, styles.mw100]}>
<View style={[styles.alignSelfCenter, styles.w100, styles.mt1]}>
Expand All @@ -185,6 +197,13 @@ function ReportDetailsPage(props) {
) : (
chatRoomSubtitleText
)}
{!_.isEmpty(parentNavigationSubtitleData) && isMoneyRequestReport && (
<ParentNavigationSubtitle
parentNavigationSubtitleData={parentNavigationSubtitleData}
parentReportID={props.report.parentReportID}
pressableStyles={[styles.mt1, styles.mw100]}
/>
)}
</View>
</View>
{_.map(menuItems, (item) => {
Expand Down
Loading

0 comments on commit d4746a8

Please sign in to comment.