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
111 changes: 111 additions & 0 deletions src/hooks/useReportActionsVisibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {getAllNonDeletedTransactions} from '@libs/MoneyRequestReportUtils';
import {isCreatedAction, isDeletedParentAction, isIOUActionMatchingTransactionList, isReportActionVisible} from '@libs/ReportActionsUtils';
import {isConciergeChatReport} from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ReportAction} from '@src/types/onyx';
import useConciergeSidePanelReportActions from './useConciergeSidePanelReportActions';
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useIsInSidePanel from './useIsInSidePanel';
import useLocalize from './useLocalize';
import useNetwork from './useNetwork';
import useOnyx from './useOnyx';
import useSidePanelState from './useSidePanelState';
import useTransactionsAndViolationsForReport from './useTransactionsAndViolationsForReport';

type UseReportActionsVisibilityParams = {
reportID: string | undefined;
reportActions: ReportAction[];
allReportActions: ReportAction[];
canPerformWriteAction: boolean;
hasOlderActions: boolean;
loadOlderChats: (force?: boolean) => void;
};

type UseReportActionsVisibilityResult = {
sortedReportActions: ReportAction[];
sortedVisibleReportActions: ReportAction[];
isConciergeSidePanel: boolean;
showConciergeSidePanelWelcome: boolean;
showFullHistory: boolean;
hasPreviousMessages: boolean;
handleShowPreviousMessages: () => void;
};

function useReportActionsVisibility({
reportID,
reportActions,
allReportActions,
canPerformWriteAction,
hasOlderActions,
loadOlderChats,
}: UseReportActionsVisibilityParams): UseReportActionsVisibilityResult {
const {isOffline} = useNetwork();
const {translate} = useLocalize();
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS);

const isInSidePanel = useIsInSidePanel();
const isConciergeSidePanel = isInSidePanel && isConciergeChatReport(report, conciergeReportID);

const {sessionStartTime} = useSidePanelState();

const hasUserSentMessage =
isConciergeSidePanel && sessionStartTime
? allReportActions.some((action) => !isCreatedAction(action) && action.actorAccountID === currentUserAccountID && action.created >= sessionStartTime)
: false;

const {transactions: reportTransactions, isLoaded: areTransactionsLoaded} = useTransactionsAndViolationsForReport(reportID);
// When transactions haven't loaded yet, pass undefined to skip IOU filtering entirely
// (undefined = "don't filter" in isIOUActionMatchingTransactionList).
// Once loaded, filter normally — even if transactions is empty (genuinely no transactions).
const reportTransactionIDs = areTransactionsLoaded ? getAllNonDeletedTransactions(reportTransactions, allReportActions ?? []).map((transaction) => transaction.transactionID) : undefined;

const visibleReportActions = reportActions.filter((reportAction) => {
Comment thread
adhorodyski marked this conversation as resolved.
const passesOfflineCheck = isOffline || isDeletedParentAction(reportAction) || reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || reportAction.errors;

if (!passesOfflineCheck) {
return false;
}

const actionReportID = reportAction.reportID ?? reportID;
if (!isReportActionVisible(reportAction, actionReportID, canPerformWriteAction, visibleReportActionsData)) {
return false;
}

if (!isIOUActionMatchingTransactionList(reportAction, reportTransactionIDs)) {
return false;
}

return true;
});

const {filteredVisibleActions, filteredReportActions, showConciergeSidePanelWelcome, showFullHistory, hasPreviousMessages, handleShowPreviousMessages} =
useConciergeSidePanelReportActions({
report,
reportActions,
visibleReportActions,
isConciergeSidePanel,
hasUserSentMessage,
hasOlderActions,
sessionStartTime,
currentUserAccountID,
greetingText: translate('common.concierge.sidePanelGreeting'),
loadOlderChats,
});

return {
sortedReportActions: filteredReportActions,
sortedVisibleReportActions: filteredVisibleActions,
isConciergeSidePanel,
showConciergeSidePanelWelcome,
showFullHistory,
hasPreviousMessages,
handleShowPreviousMessages,
};
}

export default useReportActionsVisibility;
2 changes: 1 addition & 1 deletion src/hooks/useTransactionsAndViolationsForReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function useTransactionsAndViolationsForReport(reportID?: string) {
filteredViolations[transactionViolationKey] = getTransactionViolations(transaction, violations, currentUserDetails.email ?? '', currentUserDetails.accountID, report, policy) ?? [];
}

return {transactions, violations: filteredViolations};
return {transactions, violations: filteredViolations, isLoaded: allReportsTransactionsAndViolations !== undefined};
Comment thread
adhorodyski marked this conversation as resolved.
}

export default useTransactionsAndViolationsForReport;
100 changes: 18 additions & 82 deletions src/pages/inbox/report/ReportActionsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,20 @@ import {useRoute} from '@react-navigation/native';
import React, {useEffect, useMemo, useRef} from 'react';
import type {LayoutChangeEvent} from 'react-native';
import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView';
import useConciergeSidePanelReportActions from '@hooks/useConciergeSidePanelReportActions';
import useCopySelectionHelper from '@hooks/useCopySelectionHelper';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useIsInSidePanel from '@hooks/useIsInSidePanel';
import useLoadReportActions from '@hooks/useLoadReportActions';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useParentReportAction from '@hooks/useParentReportAction';
import usePendingConciergeResponse from '@hooks/usePendingConciergeResponse';
import useReportActionsPagination from '@hooks/useReportActionsPagination';
import useReportActionsVisibility from '@hooks/useReportActionsVisibility';
import useReportIsArchived from '@hooks/useReportIsArchived';
import useSidePanelState from '@hooks/useSidePanelState';
import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport';
import {getReportPreviewAction} from '@libs/actions/IOU/MoneyRequestBuilder';
import {updateLoadingInitialReportAction} from '@libs/actions/Report';
import {getAllNonDeletedTransactions} from '@libs/MoneyRequestReportUtils';
import {isCreatedAction, isDeletedParentAction, isIOUActionMatchingTransactionList, isReportActionVisible} from '@libs/ReportActionsUtils';
import {canUserPerformWriteAction, isConciergeChatReport, isReportTransactionThread as isReportTransactionThreadUtil, isUnread} from '@libs/ReportUtils';
import {canUserPerformWriteAction, isReportTransactionThread as isReportTransactionThreadUtil, isUnread} from '@libs/ReportUtils';
import markOpenReportEnd from '@libs/telemetry/markOpenReportEnd';
import type ReportScreenNavigationProps from '@pages/inbox/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import ReportActionsList from './ReportActionsList';
Expand All @@ -42,9 +34,7 @@ function ReportActionsView({reportID, onLayout}: ReportActionsViewProps) {
const reportActionIDFromRoute = route?.params?.reportActionID;

useCopySelectionHelper();
const {translate} = useLocalize();
usePendingConciergeResponse(reportID);
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const {isOffline} = useNetwork();

const [report, reportResult] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
Expand Down Expand Up @@ -72,39 +62,19 @@ function ReportActionsView({reportID, onLayout}: ReportActionsViewProps) {
const isLoadingInitialReportActions = reportLoadingState?.isLoadingInitialReportActions;
const hasOnceLoadedReportActions = reportLoadingState?.hasOnceLoadedReportActions;

const isInSidePanel = useIsInSidePanel();
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const isConciergeSidePanel = isInSidePanel && isConciergeChatReport(report, conciergeReportID);

const {sessionStartTime} = useSidePanelState();

const hasUserSentMessage = useMemo(() => {
if (!isConciergeSidePanel || !sessionStartTime) {
return false;
}
return allReportActions.some((action) => !isCreatedAction(action) && action.actorAccountID === currentUserAccountID && action.created >= sessionStartTime);
}, [isConciergeSidePanel, allReportActions, currentUserAccountID, sessionStartTime]);

const isReportTransactionThread = isReportTransactionThreadUtil(report);

const isReportArchived = useReportIsArchived(reportID);
const canPerformWriteAction = !!canUserPerformWriteAction(report, isReportArchived);

const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS);
const reportPreviewAction = useMemo(() => getReportPreviewAction(report?.chatReportID, report?.reportID), [report?.chatReportID, report?.reportID]);
const didLayout = useRef(false);

useEffect(() => {
didLayout.current = false;
}, [reportID]);

const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(reportID);
const reportTransactionIDs = useMemo(
() => getAllNonDeletedTransactions(reportTransactions, allReportActions ?? []).map((transaction) => transaction.transactionID),
[reportTransactions, allReportActions],
);

useEffect(() => {
// When we linked to message - we do not need to wait for initial actions - they already exists
if (!reportActionIDFromRoute || !isOffline) {
Expand All @@ -116,35 +86,6 @@ function ReportActionsView({reportID, onLayout}: ReportActionsViewProps) {
// Remount the list when the deep-linked message or unread anchor changes (scroll positioning), or when the report changes.
const listID = [reportID, reportActionIDFromRoute, hasOnceLoadedReportActions ? undefined : oldestUnreadReportAction?.reportActionID].join(':');

const visibleReportActions = useMemo(
() =>
reportActions.filter((reportAction) => {
const passesOfflineCheck =
isOffline || isDeletedParentAction(reportAction) || reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || reportAction.errors;

if (!passesOfflineCheck) {
return false;
}

const actionReportID = reportAction.reportID ?? reportID;
if (!isReportActionVisible(reportAction, actionReportID, canPerformWriteAction, visibleReportActionsData)) {
return false;
}

if (!isIOUActionMatchingTransactionList(reportAction, reportTransactionIDs)) {
return false;
}

return true;
}),
[canPerformWriteAction, isOffline, reportActions, reportID, reportTransactionIDs, visibleReportActionsData],
);

const isSingleExpenseReport = reportPreviewAction?.childMoneyRequestCount === 1;
const isMissingTransactionThreadReportID = !transactionThreadReport?.reportID;
const isReportDataIncomplete = isSingleExpenseReport && isMissingTransactionThreadReportID;
const isMissingReportActions = visibleReportActions.length === 0;

const {loadOlderChats, loadNewerChats} = useLoadReportActions({
reportID,
reportActions,
Expand All @@ -154,25 +95,20 @@ function ReportActionsView({reportID, onLayout}: ReportActionsViewProps) {
hasNewerActions,
});

const {
filteredVisibleActions: conciergeSidePanelFilteredVisibleActions,
filteredReportActions: conciergeSidePanelFilteredReportActions,
showConciergeSidePanelWelcome,
showFullHistory,
hasPreviousMessages,
handleShowPreviousMessages,
} = useConciergeSidePanelReportActions({
report,
reportActions,
visibleReportActions,
isConciergeSidePanel,
hasUserSentMessage,
hasOlderActions,
sessionStartTime,
currentUserAccountID,
greetingText: translate('common.concierge.sidePanelGreeting'),
loadOlderChats,
});
const {sortedReportActions, sortedVisibleReportActions, isConciergeSidePanel, showConciergeSidePanelWelcome, showFullHistory, hasPreviousMessages, handleShowPreviousMessages} =
useReportActionsVisibility({
reportID,
reportActions,
allReportActions,
canPerformWriteAction,
hasOlderActions,
loadOlderChats,
});

const isSingleExpenseReport = reportPreviewAction?.childMoneyRequestCount === 1;
const isMissingTransactionThreadReportID = !transactionThreadReport?.reportID;
const isReportDataIncomplete = isSingleExpenseReport && isMissingTransactionThreadReportID;
const isMissingReportActions = sortedVisibleReportActions.length === 0;

/**
* Runs when the FlatList finishes laying out
Expand Down Expand Up @@ -244,8 +180,8 @@ function ReportActionsView({reportID, onLayout}: ReportActionsViewProps) {
parentReportAction={parentReportAction}
parentReportActionForTransactionThread={parentReportActionForTransactionThread}
onLayout={recordTimeToMeasureItemLayout}
sortedReportActions={conciergeSidePanelFilteredReportActions}
sortedVisibleReportActions={conciergeSidePanelFilteredVisibleActions}
sortedReportActions={sortedReportActions}
sortedVisibleReportActions={sortedVisibleReportActions}
loadOlderChats={loadOlderChats}
loadNewerChats={loadNewerChats}
hasNewerActions={hasNewerActions}
Expand Down
1 change: 1 addition & 0 deletions tests/ui/ReportActionsViewTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ describe('ReportActionsView', () => {
mockUseTransactionsAndViolationsForReport.mockReturnValue({
transactions: {},
violations: {},
isLoaded: true,
});

mockUsePaginatedReportActions.mockReturnValue({
Expand Down
Loading