Skip to content

Commit 0bf4707

Browse files
authored
Merge pull request Expensify#86946 from callstack-internal/perf/decompose-header-pdf-modal
Extract ReportPDFDownloadModal from MoneyReportHeader
2 parents 317563a + 1069a81 commit 0bf4707

2 files changed

Lines changed: 142 additions & 104 deletions

File tree

src/components/MoneyReportHeader.tsx

Lines changed: 9 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import {duplicateReport as duplicateReportAction, duplicateExpenseTransaction as
5050
import {openOldDotLink} from '@libs/actions/Link';
5151
import {setupMergeTransactionDataAndNavigate} from '@libs/actions/MergeTransaction';
5252
import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
53-
import {deleteAppReport, downloadReportPDF, exportReportToCSV, exportReportToPDF, exportToIntegration, markAsManuallyExported} from '@libs/actions/Report';
53+
import {deleteAppReport, exportReportToCSV, exportReportToPDF, exportToIntegration, markAsManuallyExported} from '@libs/actions/Report';
5454
import {getExportTemplates, queueExportSearchWithTemplate, search} from '@libs/actions/Search';
5555
import initSplitExpense from '@libs/actions/SplitExpenses';
5656
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
@@ -151,18 +151,13 @@ import ROUTES from '@src/ROUTES';
151151
import SCREENS from '@src/SCREENS';
152152
import type * as OnyxTypes from '@src/types/onyx';
153153
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
154-
import ActivityIndicator from './ActivityIndicator';
155-
import Button from './Button';
156154
import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
157155
import type {ButtonWithDropdownMenuRef, DropdownOption} from './ButtonWithDropdownMenu/types';
158156
import {useDelegateNoAccessActions, useDelegateNoAccessState} from './DelegateNoAccessModalProvider';
159-
import Header from './Header';
160157
import HeaderLoadingBar from './HeaderLoadingBar';
161158
import HeaderWithBackButton from './HeaderWithBackButton';
162-
import Icon from './Icon';
163159
import {KYCWallContext} from './KYCWall/KYCWallContext';
164160
import {useLockedAccountActions, useLockedAccountState} from './LockedAccountModalProvider';
165-
import Modal from './Modal';
166161
import {ModalActions} from './Modal/Global/ModalContext';
167162
import MoneyReportHeaderEducationalModals from './MoneyReportHeaderEducationalModals';
168163
import type {RejectModalAction} from './MoneyReportHeaderEducationalModals';
@@ -174,9 +169,9 @@ import MoneyReportHeaderStatusBarSkeleton from './MoneyReportHeaderStatusBarSkel
174169
import MoneyRequestReportNavigation from './MoneyRequestReportView/MoneyRequestReportNavigation';
175170
import {usePersonalDetails} from './OnyxListItemProvider';
176171
import type {PopoverMenuItem} from './PopoverMenu';
177-
import {PressableWithFeedback} from './Pressable';
178172
import type {ActionHandledType} from './ProcessMoneyReportHoldMenu';
179173
import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu';
174+
import ReportPDFDownloadModal from './ReportPDFDownloadModal';
180175
import {useSearchActionsContext, useSearchStateContext} from './Search/SearchContext';
181176
import type {PaymentActionParams} from './SettlementButton/types';
182177
import Text from './Text';
@@ -229,7 +224,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
229224
selector: isUserValidatedSelector,
230225
});
231226
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`);
232-
const [reportPDFFilename] = useOnyx(`${ONYXKEYS.COLLECTION.NVP_EXPENSIFY_REPORT_PDF_FILENAME}${moneyRequestReport?.reportID}`) ?? null;
233227
const [session] = useOnyx(ONYXKEYS.SESSION);
234228
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
235229
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
@@ -270,7 +264,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
270264
'QBDSquare',
271265
'CertiniaSquare',
272266
'Feed',
273-
'Close',
274267
'Location',
275268
'ReceiptPlus',
276269
'ExpenseCopy',
@@ -288,8 +281,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
288281

289282
const {translate, localeCompare, toLocaleDigit} = useLocalize();
290283
const {isProduction} = useEnvironment();
291-
const encryptedAuthToken = session?.encryptedAuthToken ?? '';
292-
293284
const exportTemplates = useMemo(
294285
() => getExportTemplates(integrationsExportTemplates ?? [], csvExportLayouts ?? {}, translate, policy),
295286
[integrationsExportTemplates, csvExportLayouts, policy, translate],
@@ -443,30 +434,12 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
443434
return !!transactions && transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t));
444435
}, [transactions]);
445436
const transactionIDs = useMemo(() => transactions?.map((t) => t.transactionID) ?? [], [transactions]);
446-
// eslint-disable-next-line rulesdir/no-negated-variables
447-
const canTriggerAutomaticPDFDownload = useRef(false);
448-
const hasFinishedPDFDownload = reportPDFFilename && reportPDFFilename !== CONST.REPORT_DETAILS_MENU_ITEM.ERROR;
449-
450437
const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS);
451438
const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE);
452439
const [isSelfTourViewed = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {
453440
selector: hasSeenTourSelector,
454441
});
455442

456-
useEffect(() => {
457-
canTriggerAutomaticPDFDownload.current = isPDFModalVisible;
458-
}, [isPDFModalVisible]);
459-
460-
const messagePDF = useMemo(() => {
461-
if (reportPDFFilename === CONST.REPORT_DETAILS_MENU_ITEM.ERROR) {
462-
return translate('reportDetailsPage.errorPDF');
463-
}
464-
if (!hasFinishedPDFDownload) {
465-
return translate('reportDetailsPage.waitForPDF');
466-
}
467-
return translate('reportDetailsPage.successPDF');
468-
}, [reportPDFFilename, hasFinishedPDFDownload, translate]);
469-
470443
// Check if any transactions have pending RTER violations (for showing the submit confirmation modal)
471444
const hasAnyPendingRTERViolation = useMemo(
472445
() => hasAnyPendingRTERViolationTransactionUtils(transactions, allTransactionViolations, email ?? '', accountID, moneyRequestReport, policy),
@@ -1243,11 +1216,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
12431216
/>
12441217
);
12451218

1246-
const beginPDFExport = (reportID: string) => {
1247-
setIsPDFModalVisible(true);
1248-
exportReportToPDF({reportID});
1249-
};
1250-
12511219
const secondaryActions = useMemo(() => {
12521220
if (!moneyRequestReport) {
12531221
return [];
@@ -1435,10 +1403,11 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
14351403
icon: expensifyIcons.Download,
14361404
sentryLabel: CONST.SENTRY_LABEL.MORE_MENU.DOWNLOAD_PDF,
14371405
onSelected: () => {
1438-
if (!moneyRequestReport) {
1406+
if (!moneyRequestReport?.reportID) {
14391407
return;
14401408
}
1441-
beginPDFExport(moneyRequestReport.reportID);
1409+
setIsPDFModalVisible(true);
1410+
exportReportToPDF({reportID: moneyRequestReport.reportID});
14421411
},
14431412
},
14441413
[CONST.REPORT.SECONDARY_ACTIONS.PRINT]: {
@@ -1940,14 +1909,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
19401909
// eslint-disable-next-line react-hooks/exhaustive-deps
19411910
}, [transactionThreadReportID]);
19421911

1943-
useEffect(() => {
1944-
if (!hasFinishedPDFDownload || !canTriggerAutomaticPDFDownload.current) {
1945-
return;
1946-
}
1947-
downloadReportPDF(reportPDFFilename, moneyRequestReport?.reportName ?? '', translate, currentUserLogin ?? '', encryptedAuthToken);
1948-
canTriggerAutomaticPDFDownload.current = false;
1949-
}, [hasFinishedPDFDownload, reportPDFFilename, moneyRequestReport?.reportName, translate, currentUserLogin, encryptedAuthToken]);
1950-
19511912
const shouldShowBackButton = shouldDisplayBackButton || shouldUseNarrowLayout;
19521913

19531914
const isMobileSelectionModeEnabled = useMobileSelectionMode();
@@ -2168,10 +2129,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
21682129
hasOptimisticNextStep: !!optimisticNextStep,
21692130
};
21702131

2171-
const pdfLoadingReasonAttributes: SkeletonSpanReasonAttributes = {
2172-
context: 'MoneyReportHeader.PDFModal',
2173-
};
2174-
21752132
return (
21762133
<View style={[styles.pt0, styles.borderBottom]}>
21772134
<HeaderWithBackButton
@@ -2293,63 +2250,11 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
22932250
onRejectModalDismissed={() => setRejectModalAction(null)}
22942251
/>
22952252
{nonReimbursablePaymentErrorDecisionModal}
2296-
<Modal
2297-
onClose={() => {
2298-
setIsPDFModalVisible(false);
2299-
}}
2253+
<ReportPDFDownloadModal
2254+
reportID={moneyRequestReport?.reportID}
23002255
isVisible={isPDFModalVisible}
2301-
type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM}
2302-
innerContainerStyle={styles.pv0}
2303-
>
2304-
<View style={[styles.flexRow, styles.m5]}>
2305-
<View style={[styles.flex1]}>
2306-
<View style={[styles.flexRow, styles.mb4]}>
2307-
<View style={[styles.flex1]}>
2308-
<View style={[styles.flexRow]}>
2309-
<Header title={translate('reportDetailsPage.generatingPDF')} />
2310-
</View>
2311-
<Text style={[styles.mt5, styles.textAlignLeft]}>{messagePDF}</Text>
2312-
</View>
2313-
2314-
{!hasFinishedPDFDownload && (
2315-
<View style={[styles.dFlex, styles.justifyContentEnd]}>
2316-
<ActivityIndicator
2317-
size={CONST.ACTIVITY_INDICATOR_SIZE.SMALL}
2318-
color={theme.textSupporting}
2319-
style={styles.ml3}
2320-
reasonAttributes={pdfLoadingReasonAttributes}
2321-
/>
2322-
</View>
2323-
)}
2324-
</View>
2325-
<Button
2326-
style={[styles.mt3, styles.noSelect]}
2327-
onPress={() => {
2328-
if (!hasFinishedPDFDownload) {
2329-
setIsPDFModalVisible(false);
2330-
} else {
2331-
downloadReportPDF(reportPDFFilename, moneyRequestReport?.reportName ?? '', translate, currentUserLogin ?? '', encryptedAuthToken);
2332-
}
2333-
}}
2334-
text={hasFinishedPDFDownload ? translate('common.download') : translate('common.cancel')}
2335-
/>
2336-
</View>
2337-
<PressableWithFeedback
2338-
onPress={() => {
2339-
setIsPDFModalVisible(false);
2340-
}}
2341-
role={CONST.ROLE.BUTTON}
2342-
accessibilityLabel={translate('common.close')}
2343-
wrapperStyle={[styles.pAbsolute, styles.r0]}
2344-
sentryLabel={CONST.SENTRY_LABEL.MORE_MENU.CLOSE_PDF_MODAL}
2345-
>
2346-
<Icon
2347-
src={expensifyIcons.Close}
2348-
fill={theme.icon}
2349-
/>
2350-
</PressableWithFeedback>
2351-
</View>
2352-
</Modal>
2256+
onClose={() => setIsPDFModalVisible(false)}
2257+
/>
23532258
</View>
23542259
);
23552260
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React, {useEffect, useRef} from 'react';
2+
import {View} from 'react-native';
3+
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
4+
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
5+
import useLocalize from '@hooks/useLocalize';
6+
import useOnyx from '@hooks/useOnyx';
7+
import useResponsiveLayout from '@hooks/useResponsiveLayout';
8+
import useTheme from '@hooks/useTheme';
9+
import useThemeStyles from '@hooks/useThemeStyles';
10+
import {downloadReportPDF} from '@libs/actions/Report';
11+
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
12+
import CONST from '@src/CONST';
13+
import ONYXKEYS from '@src/ONYXKEYS';
14+
import ActivityIndicator from './ActivityIndicator';
15+
import Button from './Button';
16+
import Header from './Header';
17+
import Icon from './Icon';
18+
import Modal from './Modal';
19+
import {PressableWithFeedback} from './Pressable';
20+
import Text from './Text';
21+
22+
type ReportPDFDownloadModalProps = {
23+
reportID: string | undefined;
24+
isVisible: boolean;
25+
onClose: () => void;
26+
};
27+
28+
function ReportPDFDownloadModal({reportID, isVisible, onClose}: ReportPDFDownloadModalProps) {
29+
const shouldAutoDownloadPDF = useRef(false);
30+
31+
const [reportPDFFilename] = useOnyx(`${ONYXKEYS.COLLECTION.NVP_EXPENSIFY_REPORT_PDF_FILENAME}${reportID}`);
32+
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
33+
const [session] = useOnyx(ONYXKEYS.SESSION);
34+
35+
const {translate} = useLocalize();
36+
// We need to use isSmallScreenWidth here because the Modal breaks in RHP with shouldUseNarrowLayout.
37+
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
38+
const {isSmallScreenWidth} = useResponsiveLayout();
39+
const styles = useThemeStyles();
40+
const theme = useTheme();
41+
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Close']);
42+
43+
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
44+
const currentUserLogin = currentUserPersonalDetails?.login ?? '';
45+
const encryptedAuthToken = session?.encryptedAuthToken ?? '';
46+
const reportName = report?.reportName ?? '';
47+
48+
const hasFinishedPDFDownload = reportPDFFilename && reportPDFFilename !== CONST.REPORT_DETAILS_MENU_ITEM.ERROR;
49+
50+
const messagePDF = (() => {
51+
if (reportPDFFilename === CONST.REPORT_DETAILS_MENU_ITEM.ERROR) {
52+
return translate('reportDetailsPage.errorPDF');
53+
}
54+
if (!hasFinishedPDFDownload) {
55+
return translate('reportDetailsPage.waitForPDF');
56+
}
57+
return translate('reportDetailsPage.successPDF');
58+
})();
59+
60+
useEffect(() => {
61+
shouldAutoDownloadPDF.current = isVisible;
62+
}, [isVisible]);
63+
64+
useEffect(() => {
65+
if (!hasFinishedPDFDownload || !shouldAutoDownloadPDF.current) {
66+
return;
67+
}
68+
downloadReportPDF(reportPDFFilename, reportName, translate, currentUserLogin ?? '', encryptedAuthToken);
69+
shouldAutoDownloadPDF.current = false;
70+
}, [hasFinishedPDFDownload, reportPDFFilename, reportName, translate, currentUserLogin, encryptedAuthToken]);
71+
72+
const pdfLoadingReasonAttributes: SkeletonSpanReasonAttributes = {
73+
context: 'MoneyReportHeader.PDFModal',
74+
};
75+
76+
return (
77+
<Modal
78+
onClose={onClose}
79+
isVisible={isVisible}
80+
type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM}
81+
innerContainerStyle={styles.pv0}
82+
>
83+
<View style={[styles.flexRow, styles.m5]}>
84+
<View style={[styles.flex1]}>
85+
<View style={[styles.flexRow, styles.mb4]}>
86+
<View style={[styles.flex1]}>
87+
<View style={[styles.flexRow]}>
88+
<Header title={translate('reportDetailsPage.generatingPDF')} />
89+
</View>
90+
<Text style={[styles.mt5, styles.textAlignLeft]}>{messagePDF}</Text>
91+
</View>
92+
93+
{!hasFinishedPDFDownload && (
94+
<View style={[styles.dFlex, styles.justifyContentEnd]}>
95+
<ActivityIndicator
96+
size={CONST.ACTIVITY_INDICATOR_SIZE.SMALL}
97+
color={theme.textSupporting}
98+
style={styles.ml3}
99+
reasonAttributes={pdfLoadingReasonAttributes}
100+
/>
101+
</View>
102+
)}
103+
</View>
104+
<Button
105+
style={[styles.mt3, styles.noSelect]}
106+
onPress={() => {
107+
if (!hasFinishedPDFDownload) {
108+
onClose();
109+
} else {
110+
downloadReportPDF(reportPDFFilename, reportName, translate, currentUserLogin ?? '', encryptedAuthToken);
111+
}
112+
}}
113+
text={hasFinishedPDFDownload ? translate('common.download') : translate('common.cancel')}
114+
/>
115+
</View>
116+
<PressableWithFeedback
117+
onPress={onClose}
118+
role={CONST.ROLE.BUTTON}
119+
accessibilityLabel={translate('common.close')}
120+
wrapperStyle={[styles.pAbsolute, styles.r0]}
121+
sentryLabel={CONST.SENTRY_LABEL.MORE_MENU.CLOSE_PDF_MODAL}
122+
>
123+
<Icon
124+
src={expensifyIcons.Close}
125+
fill={theme.icon}
126+
/>
127+
</PressableWithFeedback>
128+
</View>
129+
</Modal>
130+
);
131+
}
132+
133+
export default ReportPDFDownloadModal;

0 commit comments

Comments
 (0)