From 9473e4d22df4cfabf730e03ac5dcca73a14b4cec Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Thu, 12 Feb 2026 08:01:19 -0800 Subject: [PATCH 01/40] fix: codex review issues --- src/libs/TravelInvoicingUtils.ts | 5 +-- .../WorkspaceTravelInvoicingSection.tsx | 2 +- tests/unit/TravelInvoicingUtilsTest.ts | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/libs/TravelInvoicingUtils.ts b/src/libs/TravelInvoicingUtils.ts index 1fdf8f4ef5bb..ec975c2a0f9b 100644 --- a/src/libs/TravelInvoicingUtils.ts +++ b/src/libs/TravelInvoicingUtils.ts @@ -15,9 +15,10 @@ function getTravelSettings(cardSettings: OnyxEntry): Expe if (!cardSettings) { return undefined; } - // Prefer nested TRAVEL_US if it exists + // Prefer nested TRAVEL_US if it exists, but merge with root settings to ensure we have all properties. + // This handles optimistic updates where only a partial TRAVEL_US object (e.g. enabled state) is written. if (cardSettings.TRAVEL_US) { - return cardSettings.TRAVEL_US; + return {...cardSettings, ...cardSettings.TRAVEL_US}; } // Fall back to root level (for optimistic updates and backward compat) return cardSettings; diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx index 7c419812b49b..4262ae3426aa 100644 --- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx @@ -89,7 +89,7 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec const isSettlementAccountPendingAction = cardSettings?.pendingFields?.paymentBankAccountID === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE; // Only show errors/pending under the toggle if it's a toggle action - const toggleErrors = isTogglePendingAction ? cardSettings?.errors : undefined; + const toggleErrors = cardSettings?.errors; const togglePendingAction = isTogglePendingAction ? cardSettings?.pendingAction : undefined; // Only show errors/pending under the settlement account if it's a settlement account action diff --git a/tests/unit/TravelInvoicingUtilsTest.ts b/tests/unit/TravelInvoicingUtilsTest.ts index 5e3a2b8b7a61..691d75e79b99 100644 --- a/tests/unit/TravelInvoicingUtilsTest.ts +++ b/tests/unit/TravelInvoicingUtilsTest.ts @@ -233,4 +233,38 @@ describe('TravelInvoicingUtils', () => { expect(result?.last4).toBe(''); }); }); + + describe('getTravelSettings (internal usage via other public methods)', () => { + // We test this via getTravelSettlementFrequency which relies on getTravelSettings + it('Should merge root settings with partial nested TRAVEL_US settings', () => { + const cardSettings = { + monthlySettlementDate: new Date('2024-01-01'), // Root level + // eslint-disable-next-line @typescript-eslint/naming-convention + TRAVEL_US: { + isEnabled: true, // Nested level (partial) + }, + } as ExpensifyCardSettings; + + // Should get frequency from root (Monthly) even though TRAVEL_US doesn't have it + const result = getTravelSettlementFrequency(cardSettings); + expect(result).toBe(CONST.EXPENSIFY_CARD.FREQUENCY_SETTING.MONTHLY); + + // Should get enabled state from nested (true) + const isEnabled = getIsTravelInvoicingEnabled(cardSettings); + expect(isEnabled).toBe(true); + }); + + it('Should prioritize nested TRAVEL_US values over root values', () => { + const cardSettings = { + monthlySettlementDate: new Date('2024-01-01'), // Root level (Monthly) + [CONST.TRAVEL.PROGRAM_TRAVEL_US]: { + monthlySettlementDate: null as Date | null, // Nested level (Daily) + }, + } as ExpensifyCardSettings; + + // Nested value (Daily) should win + const result = getTravelSettlementFrequency(cardSettings); + expect(result).toBe(CONST.EXPENSIFY_CARD.FREQUENCY_SETTING.DAILY); + }); + }); }); From 01fb4ee3d348a7c28daace3647f764fbe1484ae1 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Thu, 12 Feb 2026 11:00:52 -0800 Subject: [PATCH 02/40] feat: release 2.5 - export travel invoice statement --- src/CONST/index.ts | 1 + src/ROUTES.ts | 9 + src/SCREENS.ts | 1 + src/languages/en.ts | 2 + .../GetTravelInvoicingPaymentsParams.ts | 7 + src/libs/API/parameters/index.ts | 3 +- src/libs/API/types.ts | 1 + .../ModalStackNavigators/index.tsx | 1 + .../RELATIONS/WORKSPACE_TO_RHP.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 3 + src/libs/SearchUIUtils.ts | 1 + src/libs/actions/TravelInvoicing.ts | 18 ++ .../workspace/travel/PolicyTravelPage.tsx | 28 ++- .../WorkspaceTravelInvoicingExportPage.tsx | 174 ++++++++++++++++++ 15 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 src/libs/API/parameters/GetTravelInvoicingPaymentsParams.ts create mode 100644 src/pages/workspace/travel/WorkspaceTravelInvoicingExportPage.tsx diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 5f0939d8e21b..3bb0b315b881 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3235,6 +3235,7 @@ const CONST = { IMPORT_SPREADSHEET: 'importSpreadsheet', DOWNLOAD_CSV: 'downloadCSV', SETTINGS: 'settings', + EXPORT: 'export', }, MEMBERS_BULK_ACTION_TYPES: { REMOVE: 'remove', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 4f22f01b50d4..a8dcaf10f199 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2595,6 +2595,15 @@ const ROUTES = { route: 'workspaces/:policyID/travel/settings/account', getRoute: (policyID: string) => `workspaces/${policyID}/travel/settings/account` as const, }, + WORKSPACE_TRAVEL_EXPORT: { + route: 'workspaces/:policyID/travel/export', + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the WORKSPACE_TRAVEL_EXPORT route'); + } + return `workspaces/${policyID}/travel/export` as const; + }, + }, WORKSPACE_TRAVEL_SETTINGS_FREQUENCY: { route: 'workspaces/:policyID/travel/settings/frequency', getRoute: (policyID: string) => `workspaces/${policyID}/travel/settings/frequency` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2f5062cfd97c..6fb587dfba90 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -750,6 +750,7 @@ const SCREENS = { TRAVEL: 'Travel', TRAVEL_SETTINGS_ACCOUNT: 'Workspace_Travel_Settings_Account', TRAVEL_SETTINGS_FREQUENCY: 'Workspace_Travel_Settings_Frequency', + TRAVEL_EXPORT: 'Workspace_Travel_Invoicing_Export', CREATE_DISTANCE_RATE: 'Create_Distance_Rate', CREATE_DISTANCE_RATE_UPGRADE: 'Create_Distance_Rate_Upgrade', DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings', diff --git a/src/languages/en.ts b/src/languages/en.ts index fcf0e5f1273b..61928ae2c70f 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5173,6 +5173,8 @@ const translations = { confirm: 'Got it', }, }, + exportToPDF: 'Export to PDF', + exportToCSV: 'Export to CSV', }, expensifyCard: { title: 'Expensify Card', diff --git a/src/libs/API/parameters/GetTravelInvoicingPaymentsParams.ts b/src/libs/API/parameters/GetTravelInvoicingPaymentsParams.ts new file mode 100644 index 000000000000..0d633a9102ab --- /dev/null +++ b/src/libs/API/parameters/GetTravelInvoicingPaymentsParams.ts @@ -0,0 +1,7 @@ +type GetTravelInvoicingPaymentsParams = { + policyID: string; + period: string; + type: 'csv' | 'pdf'; +}; + +export default GetTravelInvoicingPaymentsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 863da989817f..db3fc20bbc1a 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -381,6 +381,8 @@ export type {default as UpdateCardSettlementFrequencyParams} from './UpdateCardS export type {default as UpdateCardSettlementAccountParams} from './UpdateCardSettlementAccountParams'; export type {default as ToggleTravelInvoicingParams} from './ToggleTravelInvoicingParams'; export type {default as SetTravelInvoicingSettlementAccountParams} from './SetTravelInvoicingSettlementAccountParams'; +export type {default as UpdateTravelInvoicingSettlementFrequencyParams} from './UpdateTravelInvoicingSettlementFrequencyParams'; +export type {default as GetTravelInvoicingPaymentsParams} from './GetTravelInvoicingPaymentsParams'; export type {default as SetCompanyCardFeedName} from './SetCompanyCardFeedName'; export type {default as DeleteCompanyCardFeed} from './DeleteCompanyCardFeed'; export type {default as SetCompanyCardTransactionLiability} from './SetCompanyCardTransactionLiability'; @@ -473,7 +475,6 @@ export type {default as RemoveDomainAdminParams} from './RemoveDomainAdminParams export type {default as DeleteDomainMemberParams} from './DeleteDomainMemberParams'; export type {default as DeleteDomainParams} from './DeleteDomainParams'; export type {default as GetDuplicateTransactionDetailsParams} from './GetDuplicateTransactionDetailsParams'; -export type {default as UpdateTravelInvoicingSettlementFrequencyParams} from './UpdateTravelInvoicingSettlementFrequencyParams'; export type {default as SetPolicyCodingRuleParams} from './SetPolicyCodingRuleParams'; export type {default as RegisterAuthenticationKeyParams} from './RegisterAuthenticationKeyParams'; export type {default as TroubleshootMultifactorAuthenticationParams} from './TroubleshootMultifactorAuthenticationParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index b217aa5c4d4a..57e5646f6996 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -472,6 +472,7 @@ const WRITE_COMMANDS = { UPDATE_CARD_SETTLEMENT_ACCOUNT: 'UpdateCardSettlementAccount', TOGGLE_TRAVEL_INVOICING: 'ToggleTravelInvoicing', SET_TRAVEL_INVOICING_SETTLEMENT_ACCOUNT: 'SetTravelInvoicingSettlementAccount', + GET_TRAVEL_INVOICING_PAYMENTS: 'GetTravelInvoicingPayments', UPDATE_TRAVEL_INVOICE_SETTLEMENT_FREQUENCY: 'UpdateTravelInvoiceSettlementFrequency', UPDATE_XERO_IMPORT_TRACKING_CATEGORIES: 'UpdateXeroImportTrackingCategories', UPDATE_XERO_IMPORT_TAX_RATES: 'UpdateXeroImportTaxRates', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index dc6f2dc04db2..1dd266cc1e5a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -795,6 +795,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/expensifyCard/WorkspaceSettlementFrequencyPage').default, [SCREENS.WORKSPACE.TRAVEL_SETTINGS_ACCOUNT]: () => require('../../../../pages/workspace/travel/WorkspaceTravelInvoicingSettlementAccountPage').default, [SCREENS.WORKSPACE.TRAVEL_SETTINGS_FREQUENCY]: () => require('../../../../pages/workspace/travel/WorkspaceTravelInvoicingSettlementFrequencyPage').default, + [SCREENS.WORKSPACE.TRAVEL_EXPORT]: () => require('../../../../pages/workspace/travel/WorkspaceTravelInvoicingExportPage').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_SELECT_FEED]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceExpensifyCardSelectorPage').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_BANK_ACCOUNT]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_DETAILS]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index d629f1a87de1..62fc19849bdf 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -225,7 +225,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.TRAVEL_SETTINGS_FREQUENCY]: { path: ROUTES.WORKSPACE_TRAVEL_SETTINGS_FREQUENCY.route, }, + [SCREENS.WORKSPACE.TRAVEL_EXPORT]: { + path: ROUTES.WORKSPACE_TRAVEL_EXPORT.route, + }, [SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS]: { path: ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 38ea1b1f4047..012a006e91b2 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1283,6 +1283,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.TRAVEL_SETTINGS_FREQUENCY]: { policyID: string; }; + [SCREENS.WORKSPACE.TRAVEL_EXPORT]: { + policyID: string; + }; [SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS]: { policyID: string; }; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 45bd3ad77672..15136b07fade 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -4665,6 +4665,7 @@ export { isTransactionAmountTooLong, isTransactionTaxAmountTooLong, getDatePresets, + getDateRangeForPreset, createAndOpenSearchTransactionThread, getWithdrawalTypeOptions, getActionOptions, diff --git a/src/libs/actions/TravelInvoicing.ts b/src/libs/actions/TravelInvoicing.ts index 563afc05da73..7db4cc870b88 100644 --- a/src/libs/actions/TravelInvoicing.ts +++ b/src/libs/actions/TravelInvoicing.ts @@ -1,10 +1,13 @@ import Onyx from 'react-native-onyx'; import type {OnyxUpdate} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import type {LocalizedTranslate} from '@components/LocaleContextProvider'; import * as API from '@libs/API'; import type {OpenPolicyTravelPageParams, SetTravelInvoicingSettlementAccountParams, ToggleTravelInvoicingParams, UpdateTravelInvoicingSettlementFrequencyParams} from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import {getCommandURL} from '@libs/ApiUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; +import fileDownload from '@libs/fileDownload'; import {getTravelInvoicingCardSettingsKey} from '@libs/TravelInvoicingUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -311,6 +314,20 @@ function clearToggleTravelInvoicingErrors(workspaceAccountID: number) { }); } +/** + * Downloads the Travel Invoice Statement for a policy and period. + */ +function getTravelInvoiceStatement(policyID: string, period: string, type: 'csv' | 'pdf', translate: LocalizedTranslate) { + const formData = new FormData(); + formData.append('policyID', policyID); + formData.append('period', period); + formData.append('type', type); + + const commandURL = getCommandURL({command: WRITE_COMMANDS.GET_TRAVEL_INVOICING_PAYMENTS}); + const filename = `Travel_Statement_${period}.${type}`; + fileDownload(translate, commandURL, filename, '', false, formData, CONST.NETWORK.METHOD.POST); +} + export { openPolicyTravelPage, setTravelInvoicingSettlementAccount, @@ -319,4 +336,5 @@ export { updateTravelInvoiceSettlementFrequency, toggleTravelInvoicing, clearToggleTravelInvoicingErrors, + getTravelInvoiceStatement, }; diff --git a/src/pages/workspace/travel/PolicyTravelPage.tsx b/src/pages/workspace/travel/PolicyTravelPage.tsx index 12ef973414c7..7a2772705da2 100644 --- a/src/pages/workspace/travel/PolicyTravelPage.tsx +++ b/src/pages/workspace/travel/PolicyTravelPage.tsx @@ -1,11 +1,12 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'; import React, {useCallback} from 'react'; import {View} from 'react-native'; +import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollViewWithContext from '@components/ScrollViewWithContext'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; -import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -22,6 +23,7 @@ import {getTravelStep} from '@libs/PolicyUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import BookOrManageYourTrip from './BookOrManageYourTrip'; import GetStartedTravel from './GetStartedTravel'; @@ -40,6 +42,7 @@ function WorkspaceTravelPage({ const styles = useThemeStyles(); const {translate} = useLocalize(); const policy = usePolicy(policyID); + const icons = useMemoizedLazyExpensifyIcons(['Exit'] as const); const illustrations = useMemoizedLazyIllustrations(['Luggage'] as const); const workspaceAccountID = useWorkspaceAccountID(policyID); @@ -80,6 +83,15 @@ function WorkspaceTravelPage({ } })(); + const secondaryActions = [ + { + icon: icons.Exit, + text: translate('common.export'), + value: CONST.POLICY.SECONDARY_ACTIONS.EXPORT, + onSelected: () => Navigation.navigate(ROUTES.WORKSPACE_TRAVEL_EXPORT.getRoute(policyID)), + }, + ]; + return ( + > + {step === CONST.TRAVEL.STEPS.BOOK_OR_MANAGE_YOUR_TRIP && ( + {}} + customText={translate('common.more')} + options={secondaryActions} + isSplitButton={false} + shouldUseOptionIcon + wrapperStyle={styles.flexGrow1} + /> + )} + {mainContent} diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingExportPage.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingExportPage.tsx new file mode 100644 index 000000000000..99555d42a4a0 --- /dev/null +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingExportPage.tsx @@ -0,0 +1,174 @@ +import {format} from 'date-fns'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import DatePresetFilterBase from '@components/Search/FilterComponents/DatePresetFilterBase'; +import type {SearchDatePresetFilterBaseHandle, SearchDateValues} from '@components/Search/FilterComponents/DatePresetFilterBase'; +import type {SearchDatePreset} from '@components/Search/types'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getTravelInvoiceStatement} from '@libs/actions/TravelInvoicing'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import {isSearchDatePreset} from '@libs/SearchQueryUtils'; +import {getDateRangeForPreset, SearchDateModifier} from '@libs/SearchUIUtils'; +import CONST from '@src/CONST'; +import SCREENS from '@src/SCREENS'; + +type WorkspaceTravelInvoicingExportPageProps = PlatformStackScreenProps; + +function WorkspaceTravelInvoicingExportPage({route}: WorkspaceTravelInvoicingExportPageProps) { + const {policyID} = route.params; + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const searchDatePresetFilterBaseRef = useRef(null); + const [selectedDateModifier, setSelectedDateModifier] = useState(null); + + const presets: SearchDatePreset[] = [CONST.SEARCH.DATE_PRESETS.THIS_MONTH, CONST.SEARCH.DATE_PRESETS.LAST_MONTH]; + + const defaultDateValues = useMemo( + (): SearchDateValues => ({ + // Default to This month + [CONST.SEARCH.DATE_MODIFIERS.ON]: CONST.SEARCH.DATE_PRESETS.THIS_MONTH, + [CONST.SEARCH.DATE_MODIFIERS.BEFORE]: undefined, + [CONST.SEARCH.DATE_MODIFIERS.AFTER]: undefined, + }), + [], + ); + + function getComputedTitle() { + if (selectedDateModifier) { + return translate(`common.${selectedDateModifier.toLowerCase() as Lowercase}`); + } + return translate('common.export'); + } + + const goBack = () => { + if (selectedDateModifier) { + setSelectedDateModifier(null); + return; + } + Navigation.goBack(); + }; + + const save = useCallback(() => { + if (!searchDatePresetFilterBaseRef.current || !selectedDateModifier) { + return; + } + + searchDatePresetFilterBaseRef.current.setDateValueOfSelectedDateModifier(); + setSelectedDateModifier(null); + }, [selectedDateModifier]); + + const reset = useCallback(() => { + if (!searchDatePresetFilterBaseRef.current) { + return; + } + + if (selectedDateModifier) { + searchDatePresetFilterBaseRef.current.clearDateValueOfSelectedDateModifier(); + setSelectedDateModifier(null); + return; + } + + searchDatePresetFilterBaseRef.current.clearDateValues(); + }, [selectedDateModifier]); + + const handleDownload = (type: 'csv' | 'pdf') => { + const values = searchDatePresetFilterBaseRef.current?.getDateValues(); + const dateOn = values?.[CONST.SEARCH.DATE_MODIFIERS.ON]; + const dateAfter = values?.[CONST.SEARCH.DATE_MODIFIERS.AFTER]; + const dateBefore = values?.[CONST.SEARCH.DATE_MODIFIERS.BEFORE]; + + let formattedPeriod = ''; + if (dateOn) { + if (isSearchDatePreset(dateOn)) { + const range = getDateRangeForPreset(dateOn); + // For presets like "This month", keep yyyyMM + formattedPeriod = range.start.replace(/-/g, '').substring(0, 6); + } else { + // Specific date "On" -> yyyyMMdd + formattedPeriod = dateOn.replace(/-/g, ''); + } + } else if (dateAfter || dateBefore) { + // Range handling: start-end format with yyyyMMdd + const start = dateAfter ? dateAfter.replace(/-/g, '') : ''; + const end = dateBefore ? dateBefore.replace(/-/g, '') : ''; + formattedPeriod = `${start}-${end}`; + } else { + // Default to this month + formattedPeriod = format(new Date(), 'yyyyMM'); + } + + getTravelInvoiceStatement(policyID, formattedPeriod, type, translate); + }; + + const computedTitle = getComputedTitle(); + + return ( + + + + + + + {/* When date modifier is set (On, After and Before) show Reset / Save buttons, otherwise show Export buttons */} + {!!selectedDateModifier ? ( + <> +