From c48f5a62ef2d17110d3dc40eddff1e49e405bd96 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Thu, 7 Aug 2025 15:43:26 +0200 Subject: [PATCH 1/8] Check if the policy was changed for report previews --- .../useReportActionAvatars.ts | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionAvatars/useReportActionAvatars.ts b/src/components/ReportActionAvatars/useReportActionAvatars.ts index 590bb9480e87..06cb1d980502 100644 --- a/src/components/ReportActionAvatars/useReportActionAvatars.ts +++ b/src/components/ReportActionAvatars/useReportActionAvatars.ts @@ -3,7 +3,7 @@ import type {ValueOf} from 'type-fest'; import {FallbackAvatar} from '@components/Icon/Expensicons'; import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; -import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {getOriginalMessage, getReportAction, getSortedReportActions, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import { getDisplayNameForParticipant, getIcons, @@ -19,6 +19,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {OnyxInputOrEntry, Report, ReportAction} from '@src/types/onyx'; import type {Icon as IconType} from '@src/types/onyx/OnyxCommon'; +import type {OriginalMessageChangePolicy} from '@src/types/onyx/OriginalMessage'; import useReportPreviewSenderID from './useReportPreviewSenderID'; function useReportActionAvatars({ @@ -59,6 +60,14 @@ function useReportActionAvatars({ action = getReportAction(reportChatReport?.reportID, chatReport.parentReportActionID); } + const [latestChangePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID}`, { + canBeMissing: true, + selector: (reportActions) => + getSortedReportActions(Object.values(reportActions ?? {}).filter((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CHANGE_POLICY)).at(-1), + }); + + const isAReportPreviewAction = action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW; + const isReportArchived = useReportIsArchived(iouReport?.reportID); const reportPreviewSenderID = useReportPreviewSenderID({ @@ -67,11 +76,20 @@ 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 latestChangePolicyOM = getOriginalMessage(latestChangePolicy as ReportAction) ?? {}; + const {fromPolicy: policyIDBeforeChange, toPolicy: changedPolicyID} = latestChangePolicyOM as OriginalMessageChangePolicy; + const shouldUseChangedPolicyID = !!changedPolicyID; + const retrievedPolicyID = chatReportPolicyIDExists ? reportPolicyID : chatReport?.policyID; + + const policyID = shouldUseChangedPolicyID ? changedPolicyID : (passedPolicyID ?? retrievedPolicyID); const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; 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}); if (passedPolicyID) { const policyChatReportAvatar = {...getWorkspaceIcon(policyChatReport, policy), id: policyID, name: policy?.name}; @@ -99,7 +117,6 @@ function useReportActionAvatars({ const isWorkspaceWithoutChatReportProp = !chatReport && isWorkspacePolicy; const isAWorkspaceChat = isPolicyExpenseChat(chatReport) || isWorkspaceWithoutChatReportProp; const isATripPreview = action?.actionName === CONST.REPORT.ACTIONS.TYPE.TRIP_PREVIEW; - const isAReportPreviewAction = action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW; const isReportPreviewOrNoAction = !action || isAReportPreviewAction; const isReportPreviewInTripRoom = isAReportPreviewAction && isATripRoom; @@ -228,6 +245,17 @@ function useReportActionAvatars({ avatars = avatarsForAccountIDs; } + 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; + // eslint-disable-next-line rulesdir/no-negated-variables + const wasReportPreviewMovedToDifferentPolicy = chatReport?.policyID === policyIDBeforeChange && policyIDBeforeChange !== changedPolicyID && isAReportPreviewAction; + + if (isUserWithWorkspaceAvatar && wasReportPreviewMovedToDifferentPolicy) { + const policyChatReportIcon = {...getWorkspaceIcon(policyChatReport, policy), id: policyID, name: policy?.name}; + const [firstAvatar] = avatars; + avatars = [firstAvatar, policyChatReportIcon]; + } + return { avatars, avatarType, From 90491ab51497590599f72228f254050ba0346213 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Fri, 8 Aug 2025 18:24:51 +0200 Subject: [PATCH 2/8] Fix reactions modal fallback avatar --- src/components/OptionRow.tsx | 5 ++++- src/pages/home/report/ReactionList/BaseReactionList.tsx | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/OptionRow.tsx b/src/components/OptionRow.tsx index 6027d3468a71..00559163d9a2 100644 --- a/src/components/OptionRow.tsx +++ b/src/components/OptionRow.tsx @@ -154,6 +154,8 @@ function OptionRow({ subscriptColor = focusedBackgroundColor; } + const reportID = (option.iouReportID ?? option.reportID) || undefined; + return ( {(hovered) => ( @@ -209,7 +211,8 @@ function OptionRow({ {!!option.icons?.length && !!firstIcon && ( Date: Fri, 8 Aug 2025 18:40:21 +0200 Subject: [PATCH 3/8] Fix getAllNonDeletedTransactions --- src/libs/MoneyRequestReportUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/MoneyRequestReportUtils.ts b/src/libs/MoneyRequestReportUtils.ts index edad81f52a12..163ca91cc7b2 100644 --- a/src/libs/MoneyRequestReportUtils.ts +++ b/src/libs/MoneyRequestReportUtils.ts @@ -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, @@ -69,7 +69,7 @@ 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, reportActions: ReportAction[]) { return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => { @@ -77,7 +77,7 @@ function getAllNonDeletedTransactions(transactions: OnyxCollection, return false; } const action = getIOUActionForTransactionID(reportActions, transaction.transactionID); - return !isDeletedParentAction(action); + return !isDeletedParentAction(action) && !isDeletedAction(action); }); } From 49ebfc1215203e3da00c8f4389077431dc2e2b52 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Fri, 8 Aug 2025 19:15:35 +0200 Subject: [PATCH 4/8] Small fix to getAllNonDeletedTransactions --- src/libs/MoneyRequestReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/MoneyRequestReportUtils.ts b/src/libs/MoneyRequestReportUtils.ts index 163ca91cc7b2..0d920f2fc3d6 100644 --- a/src/libs/MoneyRequestReportUtils.ts +++ b/src/libs/MoneyRequestReportUtils.ts @@ -77,7 +77,7 @@ function getAllNonDeletedTransactions(transactions: OnyxCollection, return false; } const action = getIOUActionForTransactionID(reportActions, transaction.transactionID); - return !isDeletedParentAction(action) && !isDeletedAction(action); + return !isDeletedParentAction(action) && (reportActions.length === 0 || !isDeletedAction(action)); }); } From c7512eeb26a9267f19f66590c9805cad87072e82 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Mon, 11 Aug 2025 09:51:35 +0200 Subject: [PATCH 5/8] Fix Invoice avatar missing --- src/components/SelectionList/UserListItem.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx index a26b55286f02..51d15977f957 100644 --- a/src/components/SelectionList/UserListItem.tsx +++ b/src/components/SelectionList/UserListItem.tsx @@ -50,6 +50,10 @@ function UserListItem({ } }, [item, onCheckboxPress, onSelectRow]); + 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; + return ( ({ )} - {(!!item.reportID || !!item.accountID || !!item.policyID) && ( + {(!!item.reportID || !!item.accountID || !!policyID) && ( ({ ]} reportID={item.reportID} accountIDs={[Number(item.accountID)]} - policyID={!item.reportID && !item.accountID ? item.policyID : undefined} + policyID={!item.reportID && !item.accountID ? policyID : undefined} singleAvatarContainerStyle={[styles.actionAvatar, styles.mr3]} fallbackDisplayName={item.text ?? item.alternateText ?? undefined} /> From ecd25e12b0286e86fdb6720ecf3f066dbfc69f13 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Mon, 11 Aug 2025 15:00:49 +0200 Subject: [PATCH 6/8] Make reportID optional in OptionRow --- src/components/OptionRow.tsx | 17 ++++++++++++----- .../report/ReactionList/BaseReactionList.tsx | 1 - 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/OptionRow.tsx b/src/components/OptionRow.tsx index 00559163d9a2..d856eddb536a 100644 --- a/src/components/OptionRow.tsx +++ b/src/components/OptionRow.tsx @@ -22,18 +22,20 @@ import ReportActionAvatars from './ReportActionAvatars'; import SelectCircle from './SelectCircle'; import Text from './Text'; +type OptionDataWithOptionalReportID = Omit & {reportID?: string}; + type OptionRowProps = { /** Style for hovered state */ hoverStyle?: StyleProp; /** 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; + onSelectRow?: (option: OptionDataWithOptionalReportID, refElement: View | HTMLDivElement | null) => void | Promise; /** Whether we should show the selected state */ showSelectedState?: boolean; @@ -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; @@ -148,12 +150,17 @@ 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), + 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; return ( @@ -215,7 +222,7 @@ function OptionRow({ 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)} /> )} diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index b691ce70633c..3fb3d66d39a4 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -65,7 +65,6 @@ function BaseReactionList({hasUserReacted = false, users, isVisible = false, emo }); }} option={{ - reportID: '', accountID: item.accountID, text: Str.removeSMSDomain(item.displayName ?? ''), alternateText: Str.removeSMSDomain(item.login ?? ''), From f542ddd5c3fad5d6bb1a0e154fee4a0bfd0c5ebc Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Tue, 12 Aug 2025 12:33:26 +0200 Subject: [PATCH 7/8] Fix changed policy offline issue --- .../useReportActionAvatars.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/components/ReportActionAvatars/useReportActionAvatars.ts b/src/components/ReportActionAvatars/useReportActionAvatars.ts index d3b5d8b93a30..1a4c6e724b80 100644 --- a/src/components/ReportActionAvatars/useReportActionAvatars.ts +++ b/src/components/ReportActionAvatars/useReportActionAvatars.ts @@ -4,7 +4,7 @@ import {FallbackAvatar} from '@components/Icon/Expensicons'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useReportIsArchived from '@hooks/useReportIsArchived'; -import {getOriginalMessage, getReportAction, getSortedReportActions, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import { getDefaultWorkspaceAvatar, getDisplayNameForParticipant, @@ -21,7 +21,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {OnyxInputOrEntry, Report, ReportAction} from '@src/types/onyx'; import type {Icon as IconType} from '@src/types/onyx/OnyxCommon'; -import type {OriginalMessageChangePolicy} from '@src/types/onyx/OriginalMessage'; import useReportPreviewSenderID from './useReportPreviewSenderID'; function useReportActionAvatars({ @@ -63,11 +62,7 @@ function useReportActionAvatars({ action = getReportAction(reportChatReport?.reportID, chatReport.parentReportActionID); } - const [latestChangePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID}`, { - canBeMissing: true, - selector: (reportActions) => - getSortedReportActions(Object.values(reportActions ?? {}).filter((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CHANGE_POLICY)).at(-1), - }); + const [actionChildReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${action?.childReportID}`, {canBeMissing: true}); const isAReportPreviewAction = action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW; @@ -81,9 +76,8 @@ function useReportActionAvatars({ const reportPolicyID = iouReport?.policyID ?? chatReport?.policyID; const chatReportPolicyIDExists = chatReport?.policyID === CONST.POLICY.ID_FAKE || !chatReport?.policyID; - const latestChangePolicyOM = getOriginalMessage(latestChangePolicy as ReportAction) ?? {}; - const {fromPolicy: policyIDBeforeChange, toPolicy: changedPolicyID} = latestChangePolicyOM as OriginalMessageChangePolicy; - const shouldUseChangedPolicyID = !!changedPolicyID; + const changedPolicyID = actionChildReport?.policyID ?? iouReport?.policyID; + const shouldUseChangedPolicyID = !!changedPolicyID && changedPolicyID !== chatReport?.policyID; const retrievedPolicyID = chatReportPolicyIDExists ? reportPolicyID : chatReport?.policyID; const policyID = shouldUseChangedPolicyID ? changedPolicyID : (passedPolicyID ?? retrievedPolicyID); @@ -289,7 +283,7 @@ function useReportActionAvatars({ 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 = chatReport?.policyID === policyIDBeforeChange && policyIDBeforeChange !== changedPolicyID && isAReportPreviewAction; + const wasReportPreviewMovedToDifferentPolicy = shouldUseChangedPolicyID && isAReportPreviewAction; if (shouldUseInvoiceExpenseIcons) { avatars = getIconsWithDefaults(invoiceReport); From c225aeb7a0acaa977e034dfb7dc5bb3431acf900 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Tue, 12 Aug 2025 15:04:57 +0200 Subject: [PATCH 8/8] Allow passing policyID to avatars if accountID is available in UserListItem --- src/components/SelectionList/UserListItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx index 99830a96bd20..daeee5c13435 100644 --- a/src/components/SelectionList/UserListItem.tsx +++ b/src/components/SelectionList/UserListItem.tsx @@ -127,7 +127,7 @@ function UserListItem({ reportID={reportExists ? item.reportID : undefined} /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */ accountIDs={!reportExists && !!itemAccountID ? [itemAccountID] : []} - policyID={!reportExists && !itemAccountID ? policyID : undefined} + policyID={!reportExists && !!policyID ? policyID : undefined} singleAvatarContainerStyle={[styles.actionAvatar, styles.mr3]} fallbackDisplayName={item.text ?? item.alternateText ?? undefined} />