Skip to content
Open
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
15 changes: 12 additions & 3 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -905,8 +905,10 @@ function Search({
}

if (isTransactionMonthGroupListItemType(item)) {
// Extract the existing date filter to check for year-to-date or other date limits
const existingDateFilter = queryJSON.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
const {start: monthStart, end: monthEnd} = adjustTimeRangeToDateFilters(DateUtils.getMonthDateRange(item.year, item.month), existingDateFilter);
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
const {start: monthStart, end: monthEnd} = DateUtils.getMonthDateRange(item.year, item.month);
newFlatFilters.push({
key: CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE,
filters: [
Expand Down Expand Up @@ -954,8 +956,10 @@ function Search({
if (yearGroupItem.year === undefined) {
return;
}
// Extract the existing date filter to check for year-to-date or other date limits
const existingDateFilter = queryJSON.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
const {start: yearStart, end: yearEnd} = adjustTimeRangeToDateFilters(DateUtils.getYearDateRange(yearGroupItem.year), existingDateFilter);
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
const {start: yearStart, end: yearEnd} = DateUtils.getYearDateRange(yearGroupItem.year);
newFlatFilters.push({
key: CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE,
filters: [
Expand All @@ -978,8 +982,13 @@ function Search({
if (quarterGroupItem.year === undefined || quarterGroupItem.quarter === undefined) {
return;
}
// Extract the existing date filter to check for year-to-date or other date limits
const existingDateFilter = queryJSON.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
const {start: quarterStart, end: quarterEnd} = adjustTimeRangeToDateFilters(
DateUtils.getQuarterDateRange(quarterGroupItem.year, quarterGroupItem.quarter),
existingDateFilter,
);
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
const {start: quarterStart, end: quarterEnd} = DateUtils.getQuarterDateRange(quarterGroupItem.year, quarterGroupItem.quarter);
newFlatFilters.push({
key: CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE,
filters: [
Expand Down
95 changes: 57 additions & 38 deletions src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable max-lines */
// TODO: Remove this disable once SearchUIUtils is refactored (see dedicated refactor issue)
import {endOfMonth, format, startOfMonth, startOfYear, subMonths} from 'date-fns';
import {addDays, endOfMonth, format, parse, startOfMonth, startOfYear, subDays, subMonths} from 'date-fns';
import type {TextStyle, ViewStyle} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
Expand Down Expand Up @@ -2472,6 +2472,7 @@ function getTagSections(data: OnyxTypes.SearchResults['data'], queryJSON: Search
*/
function getMonthSections(data: OnyxTypes.SearchResults['data'], queryJSON: SearchQueryJSON | undefined): [TransactionMonthGroupListItemType[], number] {
const monthSections: Record<string, TransactionMonthGroupListItemType> = {};
const dateFilters = queryJSON?.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
for (const key in data) {
if (isGroupEntry(key)) {
const monthGroup = data[key];
Expand All @@ -2480,9 +2481,8 @@ function getMonthSections(data: OnyxTypes.SearchResults['data'], queryJSON: Sear
continue;
}
let transactionsQueryJSON: SearchQueryJSON | undefined;
const {start: monthStart, end: monthEnd} = adjustTimeRangeToDateFilters(DateUtils.getMonthDateRange(monthGroup.year, monthGroup.month), dateFilters);
if (queryJSON && monthGroup.year && monthGroup.month) {
// Create date range for the month (first day to last day of the month)
const {start: monthStart, end: monthEnd} = DateUtils.getMonthDateRange(monthGroup.year, monthGroup.month);
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
newFlatFilters.push({
key: CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE,
Expand Down Expand Up @@ -2521,6 +2521,7 @@ function getMonthSections(data: OnyxTypes.SearchResults['data'], queryJSON: Sear
*/
function getWeekSections(data: OnyxTypes.SearchResults['data'], queryJSON: SearchQueryJSON | undefined): [TransactionWeekGroupListItemType[], number] {
const weekSections: Record<string, TransactionWeekGroupListItemType> = {};
const dateFilters = queryJSON?.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
for (const key in data) {
if (isGroupEntry(key)) {
const weekGroup = data[key];
Expand All @@ -2529,10 +2530,7 @@ function getWeekSections(data: OnyxTypes.SearchResults['data'], queryJSON: Searc
continue;
}
let transactionsQueryJSON: SearchQueryJSON | undefined;
const {start: weekStart, end: weekEnd} = adjustTimeRangeToDateFilters(
DateUtils.getWeekDateRange(weekGroup.week),
queryJSON?.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE),
);
const {start: weekStart, end: weekEnd} = adjustTimeRangeToDateFilters(DateUtils.getWeekDateRange(weekGroup.week), dateFilters);
if (queryJSON && weekGroup.week) {
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
newFlatFilters.push({
Expand Down Expand Up @@ -2568,6 +2566,7 @@ function getWeekSections(data: OnyxTypes.SearchResults['data'], queryJSON: Searc
*/
function getYearSections(data: OnyxTypes.SearchResults['data'], queryJSON: SearchQueryJSON | undefined): [TransactionYearGroupListItemType[], number] {
const yearSections: Record<string, TransactionYearGroupListItemType> = {};
const dateFilters = queryJSON?.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
for (const key in data) {
if (isGroupEntry(key)) {
const yearGroup = data[key];
Expand All @@ -2576,8 +2575,7 @@ function getYearSections(data: OnyxTypes.SearchResults['data'], queryJSON: Searc
continue;
}
let transactionsQueryJSON: SearchQueryJSON | undefined;
const yearStart = `${yearGroup.year}-01-01`;
const yearEnd = `${yearGroup.year}-12-31`;
const {start: yearStart, end: yearEnd} = adjustTimeRangeToDateFilters(DateUtils.getYearDateRange(yearGroup.year), dateFilters);
if (queryJSON && yearGroup.year !== undefined) {
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
newFlatFilters.push({
Expand Down Expand Up @@ -2610,6 +2608,7 @@ function getYearSections(data: OnyxTypes.SearchResults['data'], queryJSON: Searc

function getQuarterSections(data: OnyxTypes.SearchResults['data'], queryJSON: SearchQueryJSON | undefined): [TransactionQuarterGroupListItemType[], number] {
const quarterSections: Record<string, TransactionQuarterGroupListItemType> = {};
const dateFilters = queryJSON?.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
for (const key in data) {
if (isGroupEntry(key)) {
const quarterGroup = data[key];
Expand All @@ -2618,7 +2617,7 @@ function getQuarterSections(data: OnyxTypes.SearchResults['data'], queryJSON: Se
continue;
}
let transactionsQueryJSON: SearchQueryJSON | undefined;
const {start: quarterStart, end: quarterEnd} = DateUtils.getQuarterDateRange(quarterGroup.year, quarterGroup.quarter);
const {start: quarterStart, end: quarterEnd} = adjustTimeRangeToDateFilters(DateUtils.getQuarterDateRange(quarterGroup.year, quarterGroup.quarter), dateFilters);
if (queryJSON && quarterGroup.year !== undefined && quarterGroup.quarter !== undefined) {
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
newFlatFilters.push({
Expand Down Expand Up @@ -3878,57 +3877,77 @@ function isDatePreset(value: string | number | undefined): value is SearchDatePr
return Object.values(CONST.SEARCH.DATE_PRESETS).some((datePreset) => datePreset === value);
}

/**
* Adjusts a time range based on date filters, intersecting preset ranges with additional constraints.
* When combining date presets (e.g., `date:on=last_month`) with constraints (e.g., `date:>=2025-04-01`),
* takes the intersection to narrow the range rather than overwriting it.
*
* @param timeRange - The base time range to adjust (e.g., a year/month/quarter range)
* @param dateFilter - Optional date filter containing preset and/or constraint filters
* @returns Adjusted time range that respects all date filters (intersected, not overwritten)
*/
function adjustTimeRangeToDateFilters(timeRange: {start: string; end: string}, dateFilter: QueryFilters[0] | undefined): {start: string; end: string} {
if (!dateFilter?.filters) {
return timeRange;
}

const {start: timeRangeStart, end: timeRangeEnd} = timeRange;
const startLimitFilter = dateFilter.filters.find((filter) => filter.operator === CONST.SEARCH.SYNTAX_OPERATORS.GREATER_THAN_OR_EQUAL_TO);
const endLimitFilter = dateFilter.filters.find((filter) => filter.operator === CONST.SEARCH.SYNTAX_OPERATORS.LOWER_THAN_OR_EQUAL_TO);
const equalToFilter = dateFilter.filters.find((filter) => filter.operator === CONST.SEARCH.SYNTAX_OPERATORS.EQUAL_TO);

let limitsStart: string | undefined;
let limitsEnd: string | undefined;

if (startLimitFilter?.value) {
const value = String(startLimitFilter.value);
// Date presets come with the equals operator, so we need to check if the value is a date preset
if (equalToFilter?.value) {
const value = String(equalToFilter.value);
if (isDatePreset(value)) {
const presetRange = getDateRangeForPreset(value);
limitsStart = presetRange.start || undefined;
limitsEnd = presetRange.end || undefined;
} else {
limitsStart = value;
limitsEnd = value;
}
}

let startLimitFilter = dateFilter.filters.find((filter) => filter.operator === CONST.SEARCH.SYNTAX_OPERATORS.GREATER_THAN_OR_EQUAL_TO);
if (startLimitFilter?.value) {
const constraintStart = String(startLimitFilter.value);
if (limitsStart) {
limitsStart = limitsStart > constraintStart ? limitsStart : constraintStart;
} else {
limitsStart = constraintStart;
}
}

startLimitFilter = dateFilter.filters.find((filter) => filter.operator === CONST.SEARCH.SYNTAX_OPERATORS.GREATER_THAN);
if (startLimitFilter?.value) {
const date = parse(String(startLimitFilter.value), 'yyyy-MM-dd', new Date());
const constraintStart = format(addDays(date, 1), 'yyyy-MM-dd');
if (limitsStart) {
limitsStart = limitsStart > constraintStart ? limitsStart : constraintStart;
} else {
limitsStart = constraintStart;
}
}

let endLimitFilter = dateFilter.filters.find((filter) => filter.operator === CONST.SEARCH.SYNTAX_OPERATORS.LOWER_THAN_OR_EQUAL_TO);
if (endLimitFilter?.value) {
const value = String(endLimitFilter.value);
if (isDatePreset(value)) {
const presetRange = getDateRangeForPreset(value);
limitsEnd = presetRange.end || undefined;
const constraintEnd = String(endLimitFilter.value);
if (limitsEnd) {
limitsEnd = limitsEnd < constraintEnd ? limitsEnd : constraintEnd;
} else {
limitsEnd = value;
limitsEnd = constraintEnd;
}
}

if (equalToFilter?.value) {
const value = String(equalToFilter.value);
if (isDatePreset(value)) {
const presetRange = getDateRangeForPreset(value);
if (presetRange.start && presetRange.end) {
if (!limitsStart) {
limitsStart = presetRange.start;
}
if (!limitsEnd) {
limitsEnd = presetRange.end;
}
if (limitsStart && presetRange.start > limitsStart) {
limitsStart = presetRange.start;
}
if (limitsEnd && presetRange.end < limitsEnd) {
limitsEnd = presetRange.end;
}
}
endLimitFilter = dateFilter.filters.find((filter) => filter.operator === CONST.SEARCH.SYNTAX_OPERATORS.LOWER_THAN);
if (endLimitFilter?.value) {
const date = parse(String(endLimitFilter.value), 'yyyy-MM-dd', new Date());
const constraintEnd = format(subDays(date, 1), 'yyyy-MM-dd');
if (limitsEnd) {
limitsEnd = limitsEnd < constraintEnd ? limitsEnd : constraintEnd;
} else {
limitsEnd = constraintEnd;
}
}

Expand Down
Loading
Loading