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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {isTrackIntentUserSelector} from '@selectors/Onboarding';
import {transactionViolationsByIDsSelector} from '@selectors/TransactionViolations';
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
// We need direct access to useOnyx to fetch live policy data at render time
Expand Down Expand Up @@ -31,6 +32,7 @@ import {syncMissingAttendeesViolation} from '@libs/AttendeeUtils';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {isAttendeeTrackingEnabled} from '@libs/PolicyUtils';
import {getNonHeldAndFullAmount, isInvoiceReport, isOpenExpenseReport, isProcessingReport, isReportPendingDelete, shouldShowMarkAsDone} from '@libs/ReportUtils';
import {hasVisibleViolations} from '@libs/SearchUIUtils';
import {isOnHold, isViolationDismissed, shouldShowViolation, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -179,8 +181,27 @@ function ExpenseReportListItem<TItem extends ListItem>({
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
const {showConfirmModal} = useConfirmModal();
const {showHoldMenu} = useHoldMenuModal();
const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(reportItem.reportID);
const {transactions: reportTransactions, violations: reportViolations} = useTransactionsAndViolationsForReport(reportItem.reportID);
const liveReportTransactions = useMemo(() => Object.values(reportTransactions), [reportTransactions]);

// Recompute the violations badge from live data at the row, replacing the screen-level
// violations merge that getSections previously did. Policy comes from the live `policyForViolations`
// (parentPolicy ?? snapshot); violations + transactions come from the report's live Onyx data.
const liveHasVisibleViolations = hasVisibleViolations(
reportForViolations,
reportViolations,
currentUserDetails.email ?? '',
currentUserDetails.accountID,
liveReportTransactions,
policyForViolations,
);

// Scoped live violations for the report's snapshot transactions: before the report's transactions
// hydrate into the live collection, rule/category changes still push violation updates that must
// reflect on the badge (per-row selector, not the screen-level collection merge this slice removed).
const snapshotTransactionIDs = (reportItem.transactions ?? []).map((transaction) => transaction.transactionID);
const liveViolationsSelector = transactionViolationsByIDsSelector(snapshotTransactionIDs);
const [liveViolationsForSnapshotTransactions] = originalUseOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {selector: liveViolationsSelector}, [liveViolationsSelector]);
const {currentUserAccountID, currentUserLogin, introSelected, betas, isSelfTourViewed, activePolicy, nextStep, chatReportPolicy, amountOwed} = useReportPaymentContext({
reportID: reportItem.reportID,
chatReportPolicyID: chatReport?.policyID,
Expand Down Expand Up @@ -308,11 +329,25 @@ function ExpenseReportListItem<TItem extends ListItem>({
const shouldShowViolationDescription = isOpenExpenseReport(reportItem) || isProcessingReport(reportItem);

// Show violation description if either:
// 1. Pre-computed hasVisibleViolations from search data, OR
// 1. Visible violations recomputed from the report's live transactions, or, before those hydrate,
// from the snapshot transactions joined with the scoped live violations (so a rule change updates
// the badge even for reports never opened this session); the snapshot's pre-computed value only
// covers the window before the violations subscription loads, OR
// 2. Synced missingAttendees violation computed at render time (for stale data)
// We're using || instead of ?? because the variables are boolean
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const hasAnyVisibleViolations = reportItem?.hasVisibleViolations || hasSyncedMissingAttendeesViolation;
const hasLiveTransactions = liveReportTransactions.length > 0;
const fallbackHasVisibleViolations = liveViolationsForSnapshotTransactions
? hasVisibleViolations(
reportForViolations,
liveViolationsForSnapshotTransactions,
currentUserDetails.email ?? '',
currentUserDetails.accountID,
reportItem.transactions ?? [],
policyForViolations,
)
: !!reportItem.hasVisibleViolations;
const hasVisibleReportViolations = hasLiveTransactions ? liveHasVisibleViolations : fallbackHasVisibleViolations;
const hasAnyVisibleViolations = hasVisibleReportViolations || hasSyncedMissingAttendeesViolation;

const getDescription = useMemo(() => {
if (reportItem?.isRejectedReport) {
Expand Down
6 changes: 0 additions & 6 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,6 @@ function Search({
const previousTransactions = usePrevious(transactions);
const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID);
const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
const {accountID, email, login} = useCurrentUserPersonalDetails();
const selfDMReport = useSelfDMReport();
Expand Down Expand Up @@ -591,7 +589,6 @@ function Search({
const [filteredData1, allLength, hasDeletedTransactionFromSections] = getSections({
type,
data: searchDataWithOptimisticTransaction,
policies,
currentAccountID: accountID,
currentUserEmail: email ?? '',
translate,
Expand All @@ -607,7 +604,6 @@ function Search({
cardList: personalAndWorkspaceCards,
nonPersonalAndWorkspaceCardList: nonPersonalAndWorkspaceCards,
isOffline,
allTransactionViolations: violations,
customCardNames,
conciergeReportID,
onyxPersonalDetailsList,
Expand Down Expand Up @@ -642,9 +638,7 @@ function Search({
cardFeeds,
personalAndWorkspaceCards,
nonPersonalAndWorkspaceCards,
policies,
bankAccountList,
violations,
customCardNames,
conciergeReportID,
onyxPersonalDetailsList,
Expand Down
16 changes: 3 additions & 13 deletions src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ type TransactionQuarterGroupSorting = ColumnSortMapping<TransactionQuarterGroupL

type GetReportSectionsParams = {
data: OnyxTypes.SearchResults['data'];
policies: OnyxCollection<OnyxTypes.Policy>;
currentSearch: SearchKey;
currentAccountID: number;
currentUserEmail: string;
Expand All @@ -245,7 +244,6 @@ type GetReportSectionsParams = {
convertToDisplayString: CurrencyListActionsContextType['convertToDisplayString'];
isActionLoadingSet: ReadonlySet<string> | undefined;
isOffline: boolean | undefined;
allTransactionViolations: OnyxCollection<OnyxTypes.TransactionViolation[]>;
bankAccountList: OnyxEntry<OnyxTypes.BankAccountList>;
reportActions?: Record<string, OnyxTypes.ReportAction[]>;
queryJSON?: SearchQueryJSON;
Expand Down Expand Up @@ -587,7 +585,6 @@ type GetSectionsResult = [
type GetSectionsParams = {
type: SearchDataTypes;
data: OnyxTypes.SearchResults['data'];
policies?: OnyxCollection<OnyxTypes.Policy>;
currentAccountID: number;
currentUserEmail: string;
translate: LocalizedTranslate;
Expand All @@ -605,7 +602,6 @@ type GetSectionsParams = {
cardList?: OnyxEntry<OnyxTypes.CardList>;
nonPersonalAndWorkspaceCardList?: OnyxEntry<OnyxTypes.CardList>;
customCardNames?: Record<number, string>;
allTransactionViolations?: OnyxCollection<OnyxTypes.TransactionViolation[]>;
visibleReportActionsData?: OnyxTypes.VisibleReportActionsDerivedValue;
conciergeReportID: string | undefined;
onyxPersonalDetailsList?: OnyxTypes.PersonalDetailsList;
Expand Down Expand Up @@ -2736,15 +2732,13 @@ function getReportActionsSections(
*/
function getReportSections({
data,
policies,
currentSearch,
currentAccountID,
currentUserEmail,
translate,
isOffline,
formatPhoneNumber,
isActionLoadingSet,
allTransactionViolations,
bankAccountList,
reportActions = {},
queryJSON,
Expand Down Expand Up @@ -2827,14 +2821,13 @@ function getReportSections({
const formattedTo = !shouldShowBlankTo ? formatPhoneNumber(getDisplayNameOrDefault(toDetails)) : '';

const formattedStatus = getReportStatusTranslation({stateNum: reportItem.stateNum, statusNum: reportItem.statusNum, translate});
const policyFromKey = getPolicyFromKey(data, reportItem);
const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportItem?.policyID ?? String(CONST.DEFAULT_NUMBER_ID)}`] ?? policyFromKey;
const policy = getPolicyFromKey(data, reportItem);

const shouldShowStatusAsPending = !!isOffline && reportItem?.pendingFields?.nextStep === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE;

const hasVisibleViolationsForReport = hasVisibleViolations(
reportItem,
allTransactionViolations ?? allViolations,
allViolations,
currentUserEmail,
currentAccountID ?? CONST.DEFAULT_NUMBER_ID,
allReportTransactions,
Expand Down Expand Up @@ -3582,7 +3575,6 @@ function getListItem(type: SearchDataTypes, status: SearchStatus, groupBy?: Sear
function getSections({
type,
data,
policies,
currentAccountID,
currentUserEmail,
translate,
Expand All @@ -3599,7 +3591,6 @@ function getSections({
cardList,
nonPersonalAndWorkspaceCardList,
customCardNames,
allTransactionViolations,
visibleReportActionsData,
conciergeReportID,
onyxPersonalDetailsList,
Expand All @@ -3618,15 +3609,13 @@ function getSections({
if (type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
return getReportSections({
data,
policies,
currentSearch,
currentAccountID,
currentUserEmail,
translate,
isOffline,
formatPhoneNumber,
isActionLoadingSet,
allTransactionViolations,
bankAccountList,
reportActions,
queryJSON,
Expand Down Expand Up @@ -6284,6 +6273,7 @@ export {
shouldShowDeleteOption,
getToFieldValueForTransaction,
getSearchReportAvatarProps,
hasVisibleViolations,
isTodoSearch,
getActiveGroupSearchHashes,
getSelectedGroupFilterEntry,
Expand Down
Loading