Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement details page for expense/iouReport #24533

Merged
merged 23 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
96b47eb
Add Money Report Share page
fedirjh Aug 13, 2023
095cad4
Add Navigation to details page for IOU reports
fedirjh Aug 14, 2023
c473822
Add Navigation to details page for IOU reports
fedirjh Aug 14, 2023
0b071e5
Add Navigation to details page for IOU reports
fedirjh Aug 14, 2023
97a3307
Add details page for IOU reports
fedirjh Aug 14, 2023
5b0f9d5
Add settings page for IOU reports
fedirjh Aug 14, 2023
59a6531
Merge branch 'main' into Implment_Details_Page_For_IOU
fedirjh Aug 14, 2023
15df3da
Generate subtitle for iou report
fedirjh Aug 14, 2023
0def258
Adjust border Width
fedirjh Aug 14, 2023
78c280d
Simplify Avatar Style Calculation
fedirjh Aug 14, 2023
eb5b02f
[Fix] Get members array for money request reports
fedirjh Aug 19, 2023
1dd318c
[Fix] Add missing notificationPreference prop to iou optimistic report
fedirjh Aug 19, 2023
9a4c0c3
Merge branch 'main' into Implment_Details_Page_For_IOU
fedirjh Aug 22, 2023
592669a
Merge branch 'main' into Implment_Details_Page_For_IOU
fedirjh Aug 29, 2023
53e2aed
[Fix] Open details page for iou/expense reports
fedirjh Aug 29, 2023
dc40929
[Chore] Add empty checks
fedirjh Aug 29, 2023
7315742
[Chore] Remove hidden participants
fedirjh Aug 31, 2023
99d2b6a
[Fix] Display members header for expense reports
fedirjh Aug 31, 2023
0df60dc
[Fix] Build participants list for IOU/expense reports
fedirjh Aug 31, 2023
2962610
Merge branch 'main' into Implment_Details_Page_For_IOU
fedirjh Sep 12, 2023
f3deffd
Merge branch 'main' into Implment_Details_Page_For_IOU
fedirjh Sep 26, 2023
b5c9c8e
replace themeColors with theme
fedirjh Sep 26, 2023
498d224
[Chore] Hide privateNotes for IOU/expense reports
fedirjh Oct 2, 2023
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
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]);
fedirjh marked this conversation as resolved.
Show resolved Hide resolved

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}
fedirjh marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1625,7 +1625,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 @@ -1846,7 +1846,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)) {
fedirjh marked this conversation as resolved.
Show resolved Hide resolved
Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
return;
}
Expand Down Expand Up @@ -2071,6 +2071,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 @@ -2109,6 +2110,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 @@ -3463,7 +3465,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 @@ -3472,7 +3474,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 @@ -3604,6 +3606,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 @@ -3799,6 +3824,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
Loading