Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 16 additions & 6 deletions src/components/OptionRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@ import ReportActionAvatars from './ReportActionAvatars';
import SelectCircle from './SelectCircle';
import Text from './Text';

type OptionDataWithOptionalReportID = Omit<OptionData, 'reportID'> & {reportID?: string};

type OptionRowProps = {
/** Style for hovered state */
hoverStyle?: StyleProp<ViewStyle>;

/** Option to allow the user to choose from can be type 'report' or 'user' */
option: OptionData;
option: OptionDataWithOptionalReportID;

/** Whether this option is currently in focus so we can modify its style */
optionIsFocused?: boolean;

/** A function that is called when an option is selected. Selected option is passed as a param */
onSelectRow?: (option: OptionData, refElement: View | HTMLDivElement | null) => void | Promise<void>;
onSelectRow?: (option: OptionDataWithOptionalReportID, refElement: View | HTMLDivElement | null) => void | Promise<void>;

/** Whether we should show the selected state */
showSelectedState?: boolean;
Expand All @@ -45,7 +47,7 @@ type OptionRowProps = {
selectedStateButtonText?: string;

/** Callback to fire when the multiple selector (checkbox or button) is clicked */
onSelectedStatePressed?: (option: OptionData) => void;
onSelectedStatePressed?: (option: OptionDataWithOptionalReportID) => void;

/** Whether we highlight selected option */
highlightSelected?: boolean;
Expand Down Expand Up @@ -148,12 +150,19 @@ function OptionRow({
const firstIcon = option?.icons?.at(0);

// We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade.
const displayNamesWithTooltips = getDisplayNamesWithTooltips((option.participantsList ?? (option.accountID ? [option] : [])).slice(0, 10), shouldUseShortFormInTooltip, localeCompare);
const displayNamesWithTooltips = getDisplayNamesWithTooltips(
(option.participantsList ?? (option.accountID ? [option as OptionData] : [])).slice(0, 10),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we can avoid this cast here. Does TS complain if we add OptionDataWithOptionalReportID type too to the argument in getDisplayNamesWithTooltips and shouldOptionShowTooltip?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's why I casted it. These functions doesn't use reportID at all but didn't want to change parameter type for this one case.

shouldUseShortFormInTooltip,
localeCompare,
);
let subscriptColor = theme.appBG;
if (optionIsFocused) {
subscriptColor = focusedBackgroundColor;
}

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const reportID = (option.iouReportID ?? option.reportID) || undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for handling the cases where the report ID is empty string?

@JakubKorytko JakubKorytko Aug 12, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. That would lead to fetch the whole report collection report_


return (
<Hoverable>
{(hovered) => (
Expand Down Expand Up @@ -209,10 +218,11 @@ function OptionRow({
{!!option.icons?.length && !!firstIcon && (
<ReportActionAvatars
subscriptAvatarBorderColor={hovered && !optionIsFocused ? hoveredBackgroundColor : subscriptColor}
reportID={option.iouReportID ?? option.reportID}
reportID={reportID}
accountIDs={!reportID && option.accountID ? [option.accountID] : []}
size={CONST.AVATAR_SIZE.DEFAULT}
secondaryAvatarContainerStyle={[StyleUtils.getBackgroundAndBorderStyle(hovered && !optionIsFocused ? hoveredBackgroundColor : subscriptColor)]}
shouldShowTooltip={showTitleTooltip && shouldOptionShowTooltip(option)}
shouldShowTooltip={showTitleTooltip && shouldOptionShowTooltip(option as OptionData)}
/>
)}
<View style={contentContainerStyles}>
Expand Down
35 changes: 26 additions & 9 deletions src/components/ReportActionAvatars/useReportActionAvatars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ function useReportActionAvatars({
action = getReportAction(reportChatReport?.reportID, chatReport.parentReportActionID);
}

const [actionChildReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${action?.childReportID}`, {canBeMissing: true});

const isAReportPreviewAction = action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW;

const isReportArchived = useReportIsArchived(iouReport?.reportID);

const reportPreviewSenderID = useReportPreviewSenderID({
Expand All @@ -70,19 +74,26 @@ function useReportActionAvatars({
chatReport,
});

const policyID = passedPolicyID ?? (chatReport?.policyID === CONST.POLICY.ID_FAKE || !chatReport?.policyID ? (iouReport?.policyID ?? chatReport?.policyID) : chatReport?.policyID);
const reportPolicyID = iouReport?.policyID ?? chatReport?.policyID;
const chatReportPolicyIDExists = chatReport?.policyID === CONST.POLICY.ID_FAKE || !chatReport?.policyID;
const changedPolicyID = actionChildReport?.policyID ?? iouReport?.policyID;
const shouldUseChangedPolicyID = !!changedPolicyID && changedPolicyID !== chatReport?.policyID;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chatReport might not be available in search snapshot data, we could fallback to iouReport if so. Link to issue: #68489.

const retrievedPolicyID = chatReportPolicyIDExists ? reportPolicyID : chatReport?.policyID;

const policyID = shouldUseChangedPolicyID ? changedPolicyID : (passedPolicyID ?? retrievedPolicyID);
const policy = usePolicy(policyID);

const invoiceReceiverPolicyID = chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined;
const invoiceReceiverPolicy = usePolicy(invoiceReceiverPolicyID);

const {chatReportIDAdmins, chatReportIDAnnounce, workspaceAccountID} = policy ?? {};
const [policyChatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportIDAnnounce ?? chatReportIDAdmins}`, {canBeMissing: true});

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const [policyChatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportIDAnnounce || chatReportIDAdmins}`, {canBeMissing: true});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is chatReportIDAnnounce zero sometimes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, don't know why but it happens rather often.

Screen.Recording.2025-08-12.at.11.24.00.mov


const delegatePersonalDetails = action?.delegateAccountID ? personalDetails?.[action?.delegateAccountID] : undefined;
const actorAccountID = getReportActionActorAccountID(action, iouReport, chatReport, delegatePersonalDetails);

const isAReportPreviewAction = action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW;
const isAInvoiceReport = isInvoiceReport(iouReport ?? null);

const shouldUseActorAccountID = isAInvoiceReport && !isAReportPreviewAction;
Expand Down Expand Up @@ -267,6 +278,13 @@ function useReportActionAvatars({

let avatars = [primaryAvatar, secondaryAvatar];

const isUserWithWorkspaceAvatar =
avatarType === CONST.REPORT_ACTION_AVATARS.TYPE.SUBSCRIPT && avatars.at(0)?.type === CONST.ICON_TYPE_AVATAR && avatars.at(1)?.type === CONST.ICON_TYPE_WORKSPACE;
const isWorkspaceWithUserAvatar =
avatars.at(0)?.type === CONST.ICON_TYPE_WORKSPACE && avatars.at(1)?.type === CONST.ICON_TYPE_AVATAR && avatarType === CONST.REPORT_ACTION_AVATARS.TYPE.MULTIPLE;
// eslint-disable-next-line rulesdir/no-negated-variables
const wasReportPreviewMovedToDifferentPolicy = shouldUseChangedPolicyID && isAReportPreviewAction;

if (shouldUseInvoiceExpenseIcons) {
avatars = getIconsWithDefaults(invoiceReport);
} else if (shouldUseMappedAccountIDs) {
Expand All @@ -278,14 +296,13 @@ function useReportActionAvatars({
if (avatars.every(({type}) => type === CONST.ICON_TYPE_WORKSPACE)) {
avatarType = isAReportPreviewAction ? CONST.REPORT_ACTION_AVATARS.TYPE.MULTIPLE : CONST.REPORT_ACTION_AVATARS.TYPE.SUBSCRIPT;
// But if it is a report preview between workspace and another user it should never be displayed as a multiple avatar
} else if (
avatars.at(0)?.type === CONST.ICON_TYPE_WORKSPACE &&
avatars.at(1)?.type === CONST.ICON_TYPE_AVATAR &&
avatarType === CONST.REPORT_ACTION_AVATARS.TYPE.MULTIPLE &&
isAReportPreviewAction
) {
} else if (isWorkspaceWithUserAvatar && isAReportPreviewAction) {
avatarType = CONST.REPORT_ACTION_AVATARS.TYPE.SUBSCRIPT;
}
} else if (isUserWithWorkspaceAvatar && wasReportPreviewMovedToDifferentPolicy) {
const policyChatReportIcon = {...getWorkspaceIcon(policyChatReport, policy), id: policyID, name: policy?.name};
const [firstAvatar] = avatars;
avatars = [firstAvatar, policyChatReportIcon];
}

return {
Expand Down
8 changes: 6 additions & 2 deletions src/components/SelectionList/UserListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ function UserListItem<TItem extends ListItem>({
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const itemAccountID = Number(item.accountID || item.icons?.at(1)?.id) || 0;

const isThereOnlyWorkspaceIcon = item.icons?.length === 1 && item.icons?.at(0)?.type === CONST.ICON_TYPE_WORKSPACE;
const shouldUseIconPolicyID = !item.reportID && !item.accountID && !item.policyID;
const policyID = isThereOnlyWorkspaceIcon && shouldUseIconPolicyID ? String(item.icons?.at(0)?.id) : item.policyID;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is item.policyID returning the wrong policy in the first place?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not wrong, it's just not stored as policyID in the item in New Invoice -> Send From RHP.

image

It is also in the value but I assumed the icons prop as the source is less error-prone, lmk if you prefer me to change it.


return (
<BaseListItem
item={item}
Expand Down Expand Up @@ -111,7 +115,7 @@ function UserListItem<TItem extends ListItem>({
</View>
</PressableWithFeedback>
)}
{(!!reportExists || !!itemAccountID || !!item.policyID) && (
{(!!reportExists || !!itemAccountID || !!policyID) && (
<ReportActionAvatars
subscriptAvatarBorderColor={hovered && !isFocused ? hoveredBackgroundColor : subscriptAvatarBorderColor}
shouldShowTooltip={showTooltip}
Expand All @@ -123,7 +127,7 @@ function UserListItem<TItem extends ListItem>({
reportID={reportExists ? item.reportID : undefined}
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */
accountIDs={!reportExists && !!itemAccountID ? [itemAccountID] : []}
policyID={!reportExists && !itemAccountID ? item.policyID : undefined}
policyID={!reportExists && !!policyID ? policyID : undefined}
singleAvatarContainerStyle={[styles.actionAvatar, styles.mr3]}
fallbackDisplayName={item.text ?? item.alternateText ?? undefined}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/libs/MoneyRequestReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {TransactionListItemType} from '@components/SelectionList/types';
import CONST from '@src/CONST';
import type {OriginalMessageIOU, Policy, Report, ReportAction, ReportMetadata, Transaction} from '@src/types/onyx';
import {convertToDisplayString} from './CurrencyUtils';
import {getIOUActionForTransactionID, getOriginalMessage, isDeletedParentAction, isMoneyRequestAction} from './ReportActionsUtils';
import {getIOUActionForTransactionID, getOriginalMessage, isDeletedAction, isDeletedParentAction, isMoneyRequestAction} from './ReportActionsUtils';
import {
getMoneyRequestSpendBreakdown,
getNonHeldAndFullAmount,
Expand Down Expand Up @@ -69,15 +69,15 @@ function getReportIDForTransaction(transactionItem: TransactionListItemType) {
}

/**
* Filters all available transactions and returns the ones that belong to not removed parent action.
* Filters all available transactions and returns the ones that belong to not removed action and not removed parent action.
*/
function getAllNonDeletedTransactions(transactions: OnyxCollection<Transaction>, reportActions: ReportAction[]) {
return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => {
if (!transaction) {
return false;
}
const action = getIOUActionForTransactionID(reportActions, transaction.transactionID);
return !isDeletedParentAction(action);
return !isDeletedParentAction(action) && (reportActions.length === 0 || !isDeletedAction(action));
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/home/report/ReactionList/BaseReactionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function BaseReactionList({hasUserReacted = false, users, isVisible = false, emo
});
}}
option={{
reportID: String(item.accountID),
accountID: item.accountID,
text: Str.removeSMSDomain(item.displayName ?? ''),
alternateText: Str.removeSMSDomain(item.login ?? ''),
participantsList: [item],
Expand Down
Loading