Skip to content

Commit 6efe129

Browse files
authored
Merge pull request Expensify#64616 from Expensify/nikki-add-export-options
[Export Templates] Add Basic IS Templates as export options
2 parents 2ec5a48 + 46804f7 commit 6efe129

27 files changed

Lines changed: 391 additions & 106 deletions

src/CONST/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,11 +1431,17 @@ const CONST = {
14311431
EXPORT_TO_INTEGRATION: 'exportToIntegration',
14321432
MARK_AS_EXPORTED: 'markAsExported',
14331433
DOWNLOAD_CSV: 'downloadCSV',
1434+
REPORT_LEVEL_EXPORT: 'report_level_export',
1435+
EXPENSE_LEVEL_EXPORT: 'detailed_export',
14341436
},
14351437
ROOM_MEMBERS_BULK_ACTION_TYPES: {
14361438
REMOVE: 'remove',
14371439
},
14381440
},
1441+
EXPORT_TEMPLATE_TYPES: {
1442+
INTEGRATIONS: 'integrations',
1443+
IN_APP: 'in-app',
1444+
},
14391445
NEXT_STEP: {
14401446
ICONS: {
14411447
HOURGLASS: 'hourglass',

src/components/MoneyReportHeader.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import useTheme from '@hooks/useTheme';
1818
import useThemeStyles from '@hooks/useThemeStyles';
1919
import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
2020
import {deleteAppReport, downloadReportPDF, exportReportToCSV, exportReportToPDF, exportToIntegration, markAsManuallyExported, openReport, openUnreportedExpense} from '@libs/actions/Report';
21+
import {queueExportSearchWithTemplate} from '@libs/actions/Search';
2122
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
2223
import getPlatform from '@libs/getPlatform';
2324
import {getThreadReportIDsForTransactions, getTotalAmountForIOUReportPreviewButton} from '@libs/MoneyRequestReportUtils';
@@ -209,6 +210,7 @@ function MoneyReportHeader({
209210
const [isUnapproveModalVisible, setIsUnapproveModalVisible] = useState(false);
210211
const [isReopenWarningModalVisible, setIsReopenWarningModalVisible] = useState(false);
211212
const [isPDFModalVisible, setIsPDFModalVisible] = useState(false);
213+
const [isExportWithTemplateModalVisible, setIsExportWithTemplateModalVisible] = useState(false);
212214

213215
const [exportModalStatus, setExportModalStatus] = useState<ExportType | null>(null);
214216

@@ -273,6 +275,25 @@ function MoneyReportHeader({
273275

274276
const {selectedTransactionIDs, clearSelectedTransactions} = useSearchContext();
275277

278+
const beginExportWithTemplate = useCallback(
279+
(templateName: string, templateType: string, transactionIDList: string[]) => {
280+
if (!moneyRequestReport) {
281+
return;
282+
}
283+
284+
setIsExportWithTemplateModalVisible(true);
285+
queueExportSearchWithTemplate({
286+
templateName,
287+
templateType,
288+
jsonQuery: '{}',
289+
reportIDList: [moneyRequestReport.reportID],
290+
transactionIDList,
291+
policyID: policy?.id,
292+
});
293+
},
294+
[moneyRequestReport, policy],
295+
);
296+
276297
const {
277298
options: selectedTransactionsOptions,
278299
handleDeleteTransactions,
@@ -284,6 +305,7 @@ function MoneyReportHeader({
284305
allTransactionsLength: transactions.length,
285306
session,
286307
onExportFailed: () => setIsDownloadErrorModalVisible(true),
308+
beginExportWithTemplate: (templateName, templateType, transactionIDList) => beginExportWithTemplate(templateName, templateType, transactionIDList),
287309
});
288310

289311
const shouldShowSelectedTransactionsButton = !!selectedTransactionsOptions.length && !transactionThreadReportID;
@@ -527,10 +549,10 @@ function MoneyReportHeader({
527549

528550
const [offlineModalVisible, setOfflineModalVisible] = useState(false);
529551

530-
const exportDropdownOptions: Record<ValueOf<typeof CONST.REPORT.EXPORT_OPTIONS>, DropdownOption<ValueOf<typeof CONST.REPORT.EXPORT_OPTIONS>>> = useMemo(
552+
const exportSubMenuOptions: Record<ValueOf<typeof CONST.REPORT.EXPORT_OPTIONS>, DropdownOption<ValueOf<typeof CONST.REPORT.EXPORT_OPTIONS>>> = useMemo(
531553
() => ({
532554
[CONST.REPORT.EXPORT_OPTIONS.DOWNLOAD_CSV]: {
533-
text: translate('common.basicExport'),
555+
text: translate('export.basicExport'),
534556
icon: Expensicons.Table,
535557
value: CONST.REPORT.EXPORT_OPTIONS.DOWNLOAD_CSV,
536558
onSelected: () => {
@@ -546,6 +568,18 @@ function MoneyReportHeader({
546568
});
547569
},
548570
},
571+
[CONST.REPORT.EXPORT_OPTIONS.EXPENSE_LEVEL_EXPORT]: {
572+
text: translate('export.expenseLevelExport'),
573+
icon: Expensicons.Table,
574+
value: CONST.REPORT.EXPORT_OPTIONS.EXPENSE_LEVEL_EXPORT,
575+
onSelected: () => beginExportWithTemplate(CONST.REPORT.EXPORT_OPTIONS.EXPENSE_LEVEL_EXPORT, CONST.EXPORT_TEMPLATE_TYPES.INTEGRATIONS, transactionIDs),
576+
},
577+
[CONST.REPORT.EXPORT_OPTIONS.REPORT_LEVEL_EXPORT]: {
578+
text: translate('export.reportLevelExport'),
579+
icon: Expensicons.Table,
580+
value: CONST.REPORT.EXPORT_OPTIONS.REPORT_LEVEL_EXPORT,
581+
onSelected: () => beginExportWithTemplate(CONST.REPORT.EXPORT_OPTIONS.REPORT_LEVEL_EXPORT, CONST.EXPORT_TEMPLATE_TYPES.INTEGRATIONS, transactionIDs),
582+
},
549583
[CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION]: {
550584
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
551585
text: translate('workspace.common.exportIntegrationSelected', {connectionName: connectedIntegrationFallback!}),
@@ -578,7 +612,7 @@ function MoneyReportHeader({
578612
},
579613
},
580614
}),
581-
[translate, connectedIntegrationFallback, connectedIntegration, moneyRequestReport, isOffline, transactionIDs, isExported],
615+
[translate, connectedIntegrationFallback, connectedIntegration, moneyRequestReport, isOffline, transactionIDs, isExported, beginExportWithTemplate],
582616
);
583617

584618
const primaryActionsImplementation = {
@@ -745,7 +779,7 @@ function MoneyReportHeader({
745779
text: translate('common.export'),
746780
backButtonText: translate('common.export'),
747781
icon: Expensicons.Export,
748-
subMenuItems: secondaryExportActions.map((action) => exportDropdownOptions[action]),
782+
subMenuItems: secondaryExportActions.map((action) => exportSubMenuOptions[action]),
749783
},
750784
[CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_PDF]: {
751785
value: CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_PDF,
@@ -1281,6 +1315,14 @@ function MoneyReportHeader({
12811315
)}
12821316
</View>
12831317
</Modal>
1318+
<ConfirmModal
1319+
onConfirm={() => setIsExportWithTemplateModalVisible(false)}
1320+
isVisible={isExportWithTemplateModalVisible}
1321+
title={translate('export.exportInProgress')}
1322+
prompt={translate('export.conciergeWillSend')}
1323+
confirmText={translate('common.buttonConfirm')}
1324+
shouldShowCancelButton={false}
1325+
/>
12841326
</View>
12851327
);
12861328
}

src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import useReportScrollManager from '@hooks/useReportScrollManager';
2626
import useResponsiveLayout from '@hooks/useResponsiveLayout';
2727
import useSelectedTransactionsActions from '@hooks/useSelectedTransactionsActions';
2828
import useThemeStyles from '@hooks/useThemeStyles';
29+
import {queueExportSearchWithTemplate} from '@libs/actions/Search';
2930
import DateUtils from '@libs/DateUtils';
3031
import {parseFSAttributes} from '@libs/Fullstory';
3132
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
@@ -166,12 +167,39 @@ function MoneyRequestReportActionsList({
166167
const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext();
167168

168169
const isMobileSelectionModeEnabled = useMobileSelectionMode();
170+
const [isExportWithTemplateModalVisible, setIsExportWithTemplateModalVisible] = useState(false);
171+
const beginExportWithTemplate = useCallback(
172+
(templateName: string, templateType: string, transactionIDList: string[]) => {
173+
if (!report) {
174+
return;
175+
}
176+
177+
setIsExportWithTemplateModalVisible(true);
178+
queueExportSearchWithTemplate({
179+
templateName,
180+
templateType,
181+
jsonQuery: '{}',
182+
reportIDList: [report.reportID],
183+
transactionIDList,
184+
policyID: policy?.id,
185+
});
186+
},
187+
[report, policy],
188+
);
189+
169190
const {
170191
options: selectedTransactionsOptions,
171192
handleDeleteTransactions,
172193
isDeleteModalVisible,
173194
hideDeleteModal,
174-
} = useSelectedTransactionsActions({report, reportActions, allTransactionsLength: transactions.length, session, onExportFailed: () => setIsDownloadErrorModalVisible(true)});
195+
} = useSelectedTransactionsActions({
196+
report,
197+
reportActions,
198+
allTransactionsLength: transactions.length,
199+
session,
200+
onExportFailed: () => setIsDownloadErrorModalVisible(true),
201+
beginExportWithTemplate: (templateName, templateType, transactionIDList) => beginExportWithTemplate(templateName, templateType, transactionIDList),
202+
});
175203

176204
// We are reversing actions because in this View we are starting at the top and don't use Inverted list
177205
const visibleReportActions = useMemo(() => {
@@ -707,6 +735,14 @@ function MoneyRequestReportActionsList({
707735
isVisible={isDownloadErrorModalVisible}
708736
onClose={() => setIsDownloadErrorModalVisible(false)}
709737
/>
738+
<ConfirmModal
739+
onConfirm={() => setIsExportWithTemplateModalVisible(false)}
740+
isVisible={isExportWithTemplateModalVisible}
741+
title={translate('export.exportInProgress')}
742+
prompt={translate('export.conciergeWillSend')}
743+
confirmText={translate('common.buttonConfirm')}
744+
shouldShowCancelButton={false}
745+
/>
710746
</View>
711747
);
712748
}

src/components/Search/SearchContext.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,24 @@ const defaultSearchContextData: SearchContextData = {
2020
const defaultSearchContext: SearchContext = {
2121
...defaultSearchContextData,
2222
lastSearchType: undefined,
23-
isExportMode: false,
24-
shouldShowExportModeOption: false,
23+
areAllMatchingItemsSelected: false,
24+
showSelectAllMatchingItems: false,
2525
shouldShowFiltersBarLoading: false,
2626
setLastSearchType: () => {},
2727
setCurrentSearchHashAndKey: () => {},
2828
setSelectedTransactions: () => {},
2929
removeTransaction: () => {},
3030
clearSelectedTransactions: () => {},
3131
setShouldShowFiltersBarLoading: () => {},
32-
setShouldShowExportModeOption: () => {},
33-
setExportMode: () => {},
32+
shouldShowSelectAllMatchingItems: () => {},
33+
selectAllMatchingItems: () => {},
3434
};
3535

3636
const Context = React.createContext<SearchContext>(defaultSearchContext);
3737

3838
function SearchContextProvider({children}: ChildrenProps) {
39-
const [shouldShowExportModeOption, setShouldShowExportModeOption] = useState(false);
40-
const [isExportMode, setExportMode] = useState(false);
39+
const [showSelectAllMatchingItems, shouldShowSelectAllMatchingItems] = useState(false);
40+
const [areAllMatchingItemsSelected, selectAllMatchingItems] = useState(false);
4141
const [shouldShowFiltersBarLoading, setShouldShowFiltersBarLoading] = useState(false);
4242
const [lastSearchType, setLastSearchType] = useState<string | undefined>(undefined);
4343
const [searchContextData, setSearchContextData] = useState(defaultSearchContextData);
@@ -75,7 +75,7 @@ function SearchContextProvider({children}: ChildrenProps) {
7575

7676
if (data.length && data.every(isTransactionReportGroupListItemType)) {
7777
selectedReports = data
78-
.filter((item) => isMoneyRequestReport(item) && item.transactions.every(({keyForList}) => selectedTransactions[keyForList]?.isSelected))
78+
.filter((item) => isMoneyRequestReport(item) && item.transactions.length > 0 && item.transactions.every(({keyForList}) => selectedTransactions[keyForList]?.isSelected))
7979
.map(({reportID, action = CONST.SEARCH.ACTION_TYPES.VIEW, total = CONST.DEFAULT_NUMBER_ID, policyID}) => ({reportID, action, total, policyID}));
8080
}
8181

@@ -127,8 +127,10 @@ function SearchContextProvider({children}: ChildrenProps) {
127127
selectedTransactions: {},
128128
selectedReports: [],
129129
}));
130-
setShouldShowExportModeOption(false);
131-
setExportMode(false);
130+
131+
// Unselect all transactions and hide the "select all matching items" option
132+
shouldShowSelectAllMatchingItems(false);
133+
selectAllMatchingItems(false);
132134
},
133135
[
134136
searchContextData.currentSearchHash,
@@ -182,10 +184,10 @@ function SearchContextProvider({children}: ChildrenProps) {
182184
setShouldShowFiltersBarLoading,
183185
lastSearchType,
184186
setLastSearchType,
185-
shouldShowExportModeOption,
186-
setShouldShowExportModeOption,
187-
isExportMode,
188-
setExportMode,
187+
showSelectAllMatchingItems,
188+
shouldShowSelectAllMatchingItems,
189+
areAllMatchingItemsSelected,
190+
selectAllMatchingItems,
189191
}),
190192
[
191193
searchContextData,
@@ -195,8 +197,9 @@ function SearchContextProvider({children}: ChildrenProps) {
195197
clearSelectedTransactions,
196198
shouldShowFiltersBarLoading,
197199
lastSearchType,
198-
shouldShowExportModeOption,
199-
isExportMode,
200+
shouldShowSelectAllMatchingItems,
201+
showSelectAllMatchingItems,
202+
areAllMatchingItemsSelected,
200203
],
201204
);
202205

src/components/Search/SearchPageHeader/SearchFiltersBar.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions, isMobileSelectionMod
7070
const {isOffline} = useNetwork();
7171
const personalDetails = usePersonalDetails();
7272
const {shouldUseNarrowLayout} = useResponsiveLayout();
73-
const {selectedTransactions, setExportMode, isExportMode, shouldShowExportModeOption, shouldShowFiltersBarLoading} = useSearchContext();
73+
const {selectedTransactions, selectAllMatchingItems, areAllMatchingItemsSelected, showSelectAllMatchingItems, shouldShowFiltersBarLoading} = useSearchContext();
7474

7575
const [email] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true, selector: (onyxSession) => onyxSession?.email});
7676
const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true});
@@ -425,7 +425,9 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions, isMobileSelectionMod
425425
return <SearchFiltersSkeleton shouldAnimate />;
426426
}
427427

428-
const selectionButtonText = isExportMode ? translate('search.exportAll.allMatchingItemsSelected') : translate('workspace.common.selected', {count: selectedTransactionsKeys.length});
428+
const selectionButtonText = areAllMatchingItemsSelected
429+
? translate('search.exportAll.allMatchingItemsSelected')
430+
: translate('workspace.common.selected', {count: selectedTransactionsKeys.length});
429431

430432
return (
431433
<View style={[shouldShowSelectedDropdown && styles.ph5, styles.mb2, styles.searchFiltersBarContainer]}>
@@ -444,13 +446,13 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions, isMobileSelectionMod
444446
}}
445447
popoverHorizontalOffsetType={CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT}
446448
/>
447-
{!isExportMode && shouldShowExportModeOption && (
449+
{!areAllMatchingItemsSelected && showSelectAllMatchingItems && (
448450
<Button
449451
link
450452
small
451453
shouldUseDefaultHover={false}
452454
innerStyles={styles.p0}
453-
onPress={() => setExportMode(true)}
455+
onPress={() => selectAllMatchingItems(true)}
454456
text={translate('search.exportAll.selectAllMatchingItems')}
455457
/>
456458
)}

0 commit comments

Comments
 (0)