1- import { addDays , format , parse } from 'date-fns' ;
1+ import { addDays , endOfMonth , format , parse , startOfMonth , startOfYear , subMonths } from 'date-fns' ;
22import cloneDeep from 'lodash/cloneDeep' ;
33import Onyx from 'react-native-onyx' ;
44import type { OnyxCollection , OnyxUpdate } from 'react-native-onyx' ;
@@ -983,6 +983,56 @@ function getAllPolicyValues<T extends OnyxCollectionKey>(
983983 return policyID . map ( ( id ) => policyData ?. [ `${ key } ${ id } ` ] ) . filter ( ( data ) => ! ! data ) as Array < OnyxCollectionValuesMapping [ T ] > ;
984984}
985985
986+ function getEarlierDate ( someDate : string | undefined , otherDate : string | undefined ) {
987+ if ( someDate && otherDate ) {
988+ return someDate < otherDate ? someDate : otherDate ;
989+ }
990+ return someDate ?? otherDate ;
991+ }
992+
993+ function getLaterDate ( someDate : string | undefined , otherDate : string | undefined ) {
994+ if ( someDate && otherDate ) {
995+ return someDate > otherDate ? someDate : otherDate ;
996+ }
997+ return someDate ?? otherDate ;
998+ }
999+
1000+ /**
1001+ * Returns the start and end date range for a date preset.
1002+ */
1003+ function getDateRangeForPreset ( preset : SearchDatePreset ) : { start : string ; end : string } {
1004+ const now = new Date ( ) ;
1005+ let start : Date ;
1006+ let end : Date ;
1007+ const lastMonth = subMonths ( now , 1 ) ;
1008+
1009+ switch ( preset ) {
1010+ case CONST . SEARCH . DATE_PRESETS . THIS_MONTH :
1011+ start = startOfMonth ( now ) ;
1012+ end = endOfMonth ( now ) ;
1013+ break ;
1014+ case CONST . SEARCH . DATE_PRESETS . LAST_MONTH :
1015+ start = startOfMonth ( lastMonth ) ;
1016+ end = endOfMonth ( lastMonth ) ;
1017+ break ;
1018+ case CONST . SEARCH . DATE_PRESETS . YEAR_TO_DATE :
1019+ start = startOfYear ( now ) ;
1020+ end = now ;
1021+ break ;
1022+ case CONST . SEARCH . DATE_PRESETS . LAST_12_MONTHS :
1023+ start = startOfMonth ( subMonths ( now , 11 ) ) ;
1024+ end = endOfMonth ( now ) ;
1025+ break ;
1026+ default :
1027+ return { start : '' , end : '' } ;
1028+ }
1029+
1030+ return {
1031+ start : format ( start , 'yyyy-MM-dd' ) ,
1032+ end : format ( end , 'yyyy-MM-dd' ) ,
1033+ } ;
1034+ }
1035+
9861036/**
9871037 * Generates object with search filter values, in a format that can be consumed by SearchAdvancedFiltersForm.
9881038 * Main usage of this is to generate the initial values for AdvancedFilters from existing query.
@@ -1135,10 +1185,13 @@ function buildFilterFormValuesFromQuery(
11351185 return filter . operator === CONST . SEARCH . SYNTAX_OPERATORS . EQUAL_TO && ( isValidDate ( filter . value . toString ( ) ) || isSearchDatePreset ( filter . value . toString ( ) ) ) ;
11361186 } ) ;
11371187 const existingRangeBoundaries = parseRangeQueryValue ( filtersForm [ rangeKey ] ) ;
1138- const rangeValue = getRangeQueryValue ( rangeStartFilter ?. value . toString ( ) ?? existingRangeBoundaries . from , rangeEndFilter ?. value . toString ( ) ?? existingRangeBoundaries . to ) ;
1188+ const rangeValue = getRangeQueryValue (
1189+ getLaterDate ( rangeStartFilter ?. value . toString ( ) , existingRangeBoundaries . from ) ,
1190+ getEarlierDate ( rangeEndFilter ?. value . toString ( ) , existingRangeBoundaries . to ) ,
1191+ ) ;
11391192
1140- filtersForm [ beforeKey ] = beforeFilter ?. value . toString ( ) ?? filtersForm [ beforeKey ] ;
1141- filtersForm [ afterKey ] = afterFilter ?. value . toString ( ) ?? filtersForm [ afterKey ] ;
1193+ filtersForm [ beforeKey ] = getEarlierDate ( beforeFilter ?. value . toString ( ) , filtersForm [ beforeKey ] ) ;
1194+ filtersForm [ afterKey ] = getLaterDate ( afterFilter ?. value . toString ( ) , filtersForm [ afterKey ] ) ;
11421195 filtersForm [ onKey ] = onFilter ?. value . toString ( ) ?? filtersForm [ onKey ] ;
11431196 filtersForm [ negatedKey ] = negatedFilter ?. value . toString ( ) ?? filtersForm [ negatedKey ] ;
11441197
@@ -1216,13 +1269,16 @@ function buildFilterFormValuesFromQuery(
12161269 }
12171270
12181271 const existingRangeBoundaries = parseRangeQueryValue ( filtersForm [ dateRangeKey ] ) ;
1219- const rangeValue = getRangeQueryValue ( dateRangeStartFilter ?. value . toString ( ) ?? existingRangeBoundaries . from , dateRangeEndFilter ?. value . toString ( ) ?? existingRangeBoundaries . to ) ;
1272+ const rangeValue = getRangeQueryValue (
1273+ getLaterDate ( dateRangeStartFilter ?. value . toString ( ) , existingRangeBoundaries . from ) ,
1274+ getEarlierDate ( dateRangeEndFilter ?. value . toString ( ) , existingRangeBoundaries . to ) ,
1275+ ) ;
12201276
12211277 filtersForm [ textKey ] = textFilter ?. value . toString ( ) ?? filtersForm [ textKey ] ;
12221278 filtersForm [ negatedKey ] = negatedFilter ?. value . toString ( ) ?? filtersForm [ negatedKey ] ;
12231279 filtersForm [ dateOnKey ] = dateOnFilter ?. value . toString ( ) ?? filtersForm [ dateOnKey ] ;
1224- filtersForm [ dateBeforeKey ] = dateBeforeFilter ?. value . toString ( ) ?? filtersForm [ dateBeforeKey ] ;
1225- filtersForm [ dateAfterKey ] = dateAfterFilter ?. value . toString ( ) ?? filtersForm [ dateAfterKey ] ;
1280+ filtersForm [ dateBeforeKey ] = getEarlierDate ( dateBeforeFilter ?. value . toString ( ) , filtersForm [ dateBeforeKey ] ) ;
1281+ filtersForm [ dateAfterKey ] = getLaterDate ( dateAfterFilter ?. value . toString ( ) , filtersForm [ dateAfterKey ] ) ;
12261282
12271283 if ( rangeValue ) {
12281284 filtersForm [ dateRangeKey ] = rangeValue ;
@@ -1232,6 +1288,34 @@ function buildFilterFormValuesFromQuery(
12321288 }
12331289 }
12341290
1291+ // The UI doesn't combine date presets with other date modifiers, but a raw query string can.
1292+ // When that happens, we resolve the preset to a date range, merge with the explicit constraints,
1293+ // and store as one range.
1294+ for ( const dateKey of DATE_FILTER_KEYS ) {
1295+ const onKey = `${ dateKey } ${ CONST . SEARCH . DATE_MODIFIERS . ON } ` as const ;
1296+ const rangeKey = `${ dateKey } ${ CONST . SEARCH . DATE_MODIFIERS . RANGE } ` as const ;
1297+ const beforeKey = `${ dateKey } ${ CONST . SEARCH . DATE_MODIFIERS . BEFORE } ` as const ;
1298+ const afterKey = `${ dateKey } ${ CONST . SEARCH . DATE_MODIFIERS . AFTER } ` as const ;
1299+
1300+ if ( ! isSearchDatePreset ( filtersForm [ onKey ] ) || ! ( filtersForm [ rangeKey ] ?? filtersForm [ beforeKey ] ?? filtersForm [ afterKey ] ) ) {
1301+ continue ;
1302+ }
1303+
1304+ const presetRange = getDateRangeForPreset ( filtersForm [ onKey ] ) ;
1305+ if ( ! presetRange . start || ! presetRange . end ) {
1306+ continue ;
1307+ }
1308+
1309+ const existingRange = parseRangeQueryValue ( filtersForm [ rangeKey ] ) ;
1310+ const newRangeStart = getLaterDate ( getLaterDate ( presetRange . start , existingRange . from ) , getInclusiveRangeBoundary ( filtersForm [ afterKey ] , 1 ) ) ;
1311+ const newRangeEnd = getEarlierDate ( getEarlierDate ( presetRange . end , existingRange . to ) , getInclusiveRangeBoundary ( filtersForm [ beforeKey ] , - 1 ) ) ;
1312+
1313+ filtersForm [ rangeKey ] = getRangeQueryValue ( newRangeStart , newRangeEnd ) ;
1314+ filtersForm [ onKey ] = undefined ;
1315+ filtersForm [ beforeKey ] = undefined ;
1316+ filtersForm [ afterKey ] = undefined ;
1317+ }
1318+
12351319 const [ typeKey , typeValue ] = Object . entries ( CONST . SEARCH . DATA_TYPES ) . find ( ( [ , value ] ) => value === queryJSON . type ) ?? [ ] ;
12361320 filtersForm [ FILTER_KEYS . TYPE ] = typeValue ? queryJSON . type : CONST . SEARCH . DATA_TYPES . EXPENSE ;
12371321
@@ -2043,6 +2127,7 @@ export {
20432127 getRangeQueryValue ,
20442128 parseRangeQueryValue ,
20452129 isSearchDatePreset ,
2130+ getDateRangeForPreset ,
20462131 isFilterSupported ,
20472132 buildSearchQueryJSON ,
20482133 buildSearchQueryString ,
0 commit comments