Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
c4f6eff
feat: Range UI filter
btkcodedev Feb 9, 2026
24001a6
fix: button style side margin
btkcodedev Feb 9, 2026
310b129
fix: exclusivity of after and before individual filters
btkcodedev Feb 9, 2026
9bef859
chore: prettify
btkcodedev Feb 9, 2026
c355bd7
fix: useEffect perf-10
btkcodedev Feb 10, 2026
5d3b2ef
fix: guard rail after > before for range
btkcodedev Feb 10, 2026
5c12297
prettify
btkcodedev Feb 10, 2026
54a357e
fix ESlints, prettify
btkcodedev Feb 10, 2026
52d75dd
fix: translations
btkcodedev Feb 10, 2026
7a9d860
fix: ESLint, non null assertions, prettify
btkcodedev Feb 10, 2026
c5ff6e7
test: increase code coverage
btkcodedev Feb 10, 2026
aa0b6c8
chore: prettify
btkcodedev Feb 10, 2026
8efa220
fix: ESLint
btkcodedev Feb 10, 2026
62da5cc
Merge remote-tracking branch 'origin' into btkcodedev/81501RangeUI
btkcodedev Feb 11, 2026
ef57b81
fix spacing issues
btkcodedev Feb 11, 2026
cc686e5
fix design issues, mobile auto-scroll on error
btkcodedev Feb 11, 2026
ab0af91
fix: remove stored dates during backbutton press
btkcodedev Feb 11, 2026
d7d7158
fix: remove duplicate props
btkcodedev Feb 11, 2026
d92fc76
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 11, 2026
7c7e8d5
fix: consistent display in filter section
btkcodedev Feb 11, 2026
4009977
fix: eslint
btkcodedev Feb 11, 2026
6624c27
fix: calendar padding, footer styles according to figma
btkcodedev Feb 12, 2026
1d76b54
fix: reset behaviour
btkcodedev Feb 12, 2026
762cdf1
fix: mobile view design issues, range filter inconsistency, mt on cal…
btkcodedev Feb 12, 2026
08a1a28
fix: individual exclusivity
btkcodedev Feb 13, 2026
c2233ec
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 13, 2026
b8f6704
chore: prettify
btkcodedev Feb 13, 2026
8dbc5e0
fix: Unwanted redundancy
btkcodedev Feb 13, 2026
cc1024a
fix: Seperate range filter boundaries from after/before filter, fix m…
btkcodedev Feb 13, 2026
d52326b
fix: spell check
btkcodedev Feb 13, 2026
ce45322
fix: ESLints
btkcodedev Feb 13, 2026
aa2f59c
fix: ESLint
btkcodedev Feb 14, 2026
35c24af
fix: mobile container padding
btkcodedev Feb 14, 2026
0f03d4c
fix: dynamic calendar padding for both range selectors, optimal butto…
btkcodedev Feb 17, 2026
20984f9
fix: route validating logic to useImperativeHandle block, remove unne…
btkcodedev Feb 18, 2026
7b21dcb
fix: update selected day style to use green background and white text…
btkcodedev Feb 18, 2026
9aaf017
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 18, 2026
f80d4f1
fix: ESLint
btkcodedev Feb 18, 2026
1eee665
fix: error margin to 20px, fix selected day
btkcodedev Feb 19, 2026
37c3bd4
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 19, 2026
4b7a725
chore: prettify
btkcodedev Feb 19, 2026
f447226
fix: const, searchfilterbar function, improve consistency
btkcodedev Feb 20, 2026
8871800
fix: useRangeLayout for refactor
btkcodedev Feb 20, 2026
2f01350
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 20, 2026
4c423e1
fix: conflict
btkcodedev Feb 20, 2026
e628d59
fix: mobile view selected dates
btkcodedev Feb 20, 2026
d7d3511
chore: prettify
btkcodedev Feb 20, 2026
4c81477
fix: redundancy
btkcodedev Feb 20, 2026
bf5a99d
fix: remove redundancy
btkcodedev Feb 20, 2026
5e2ee0c
update test file
btkcodedev Feb 20, 2026
6714552
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 24, 2026
10f076b
fix typecheck
btkcodedev Feb 24, 2026
6e98910
fix: esLint
btkcodedev Feb 25, 2026
7a6bc0d
fix: reuse common function
btkcodedev Feb 26, 2026
04906d6
fix: optimize block
btkcodedev Feb 26, 2026
aeee473
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 26, 2026
3ffa7d3
fix: reset range values in datapresetfilterbase
btkcodedev Feb 26, 2026
d34fd77
Update src/components/Search/FilterDropdowns/DateSelectPopup.tsx
btkcodedev Feb 26, 2026
369ae7c
fix: unnecessary duplications
btkcodedev Feb 26, 2026
1713a91
fix: advanced filters
btkcodedev Feb 26, 2026
f105985
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 26, 2026
18802cf
fix: reset in advanced filters, fix mobile
btkcodedev Feb 26, 2026
b7ba22a
fix: prettify
btkcodedev Feb 26, 2026
68ae93b
Merge branch 'main' into btkcodedev/81501RangeUI
btkcodedev Feb 26, 2026
bc24e81
move const
btkcodedev Feb 26, 2026
76d5391
fix: tsc
btkcodedev Feb 27, 2026
e889662
fix: tsc
btkcodedev Feb 27, 2026
3480619
fix: global smallscreen
btkcodedev Feb 27, 2026
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
4 changes: 4 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ const CONST = {
POPOVER_MENU_MAX_HEIGHT: 496,
POPOVER_MENU_MAX_HEIGHT_MOBILE: 432,
POPOVER_DATE_WIDTH: 338,
POPOVER_DATE_RANGE_WIDTH: 672,
POPOVER_DATE_MAX_HEIGHT: 366,
POPOVER_DATE_MIN_HEIGHT: 322,
TOOLTIP_ANIMATION_DURATION: 500,
Expand Down Expand Up @@ -7408,6 +7409,7 @@ const CONST = {
EQUAL_TO: 'eq',
CONTAINS: 'contains',
NOT_EQUAL_TO: 'neq',
RANGE: 'range',
GREATER_THAN: 'gt',
GREATER_THAN_OR_EQUAL_TO: 'gte',
LOWER_THAN: 'lt',
Expand Down Expand Up @@ -7482,6 +7484,7 @@ const CONST = {
ON_PREFIX: 'reportFieldOn-',
AFTER_PREFIX: 'reportFieldAfter-',
BEFORE_PREFIX: 'reportFieldBefore-',
RANGE_PREFIX: 'reportFieldRange-',
},
TAG_EMPTY_VALUE: 'none',
CATEGORY_EMPTY_VALUE: 'none',
Expand Down Expand Up @@ -7610,6 +7613,7 @@ const CONST = {
ON: 'On',
AFTER: 'After',
BEFORE: 'Before',
RANGE: 'Range',
},
AMOUNT_MODIFIERS: {
LESS_THAN: 'LessThan',
Expand Down
8 changes: 2 additions & 6 deletions src/components/DatePicker/CalendarPicker/Day.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,9 @@ function Day({disabled, selected, pressed, hovered, children}: DayProps) {
const StyleUtils = useStyleUtils();
return (
<View
style={[
themeStyles.calendarDayContainer,
selected ? themeStyles.buttonDefaultBG : {},
!disabled ? StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed)) : {},
]}
style={[themeStyles.calendarDayContainer, selected ? themeStyles.buttonSuccess : {}, !disabled ? StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed)) : {}]}
>
<Text style={disabled ? themeStyles.buttonOpacityDisabled : {}}>{children}</Text>
<Text style={[disabled ? themeStyles.buttonOpacityDisabled : {}, selected ? themeStyles.buttonSuccessText : {}]}>{children}</Text>
</View>
);
}
Expand Down
8 changes: 7 additions & 1 deletion src/components/DatePicker/CalendarPicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {addMonths, endOfDay, endOfMonth, format, getYear, isSameDay, parseISO, setDate, setYear, startOfDay, startOfMonth, subMonths} from 'date-fns';
import {Str} from 'expensify-common';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
Expand Down Expand Up @@ -35,6 +36,9 @@ type CalendarPickerProps = {

/** A function called when the date is selected */
onSelected?: (selectedDate: string) => void;

/** Optional style override for the header container */
headerContainerStyle?: StyleProp<ViewStyle>;
};

function getInitialCurrentDateView(value: Date | string, minDate: Date, maxDate: Date) {
Expand All @@ -56,6 +60,7 @@ function CalendarPicker({
onSelected,
DayComponent = Day,
selectableDates,
headerContainerStyle,
}: CalendarPickerProps) {
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();
Expand Down Expand Up @@ -175,13 +180,14 @@ function CalendarPicker({

const webOnlyMarginStyle = isSmallScreenWidth ? {} : styles.mh1;
const calendarContainerStyle = isSmallScreenWidth ? [webOnlyMarginStyle, themeStyles.calendarBodyContainer] : [webOnlyMarginStyle, animatedStyle];
const headerPaddingStyle = headerContainerStyle ?? themeStyles.ph5;

const getAccessibilityState = useCallback((isSelected: boolean) => ({selected: isSelected}), []);

return (
<View style={[themeStyles.pb4]}>
<View
style={[themeStyles.calendarHeader, themeStyles.flexRow, themeStyles.justifyContentBetween, themeStyles.alignItemsCenter, themeStyles.ph5]}
style={[themeStyles.calendarHeader, themeStyles.flexRow, themeStyles.justifyContentBetween, themeStyles.alignItemsCenter, headerPaddingStyle]}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
<PressableWithFeedback
Expand Down
84 changes: 66 additions & 18 deletions src/components/Search/FilterComponents/DateFilterBase.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
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 ScrollView from '@components/ScrollView';
import type {ReportFieldDateKey, SearchDateFilterKeys} from '@components/Search/types';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import {getDateRangeDisplayValueFromFormValue} from '@libs/SearchQueryUtils';
import {getDatePresets} from '@libs/SearchUIUtils';
import type {SearchDateModifier, SearchDateModifierLower} from '@libs/SearchUIUtils';
import CONST from '@src/CONST';
Expand All @@ -30,6 +33,7 @@ function DateFilterBase({title, dateKey, back, onSubmit}: DateFilterBaseProps) {
const [searchAdvancedFiltersForm, searchAdvancedFiltersFormMetadata] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM);
const isSearchAdvancedFiltersFormLoading = isLoadingOnyxValue(searchAdvancedFiltersFormMetadata);
const [selectedDateModifier, setSelectedDateModifier] = useState<SearchDateModifier | null>(null);
const [shouldShowRangeError, setShouldShowRangeError] = useState(false);

const dateOnKey = dateKey.startsWith(CONST.SEARCH.REPORT_FIELD.GLOBAL_PREFIX)
? (dateKey.replace(CONST.SEARCH.REPORT_FIELD.DEFAULT_PREFIX, CONST.SEARCH.REPORT_FIELD.ON_PREFIX) as ReportFieldDateKey)
Expand All @@ -43,19 +47,36 @@ function DateFilterBase({title, dateKey, back, onSubmit}: DateFilterBaseProps) {
? (dateKey.replace(CONST.SEARCH.REPORT_FIELD.DEFAULT_PREFIX, CONST.SEARCH.REPORT_FIELD.AFTER_PREFIX) as ReportFieldDateKey)
: (`${dateKey}${CONST.SEARCH.DATE_MODIFIERS.AFTER}` as const);

const dateRangeKey = dateKey.startsWith(CONST.SEARCH.REPORT_FIELD.GLOBAL_PREFIX)
? (`${CONST.SEARCH.REPORT_FIELD.RANGE_PREFIX}${dateKey.replace(CONST.SEARCH.REPORT_FIELD.DEFAULT_PREFIX, '')}` as ReportFieldDateKey)
: (`${dateKey}${CONST.SEARCH.DATE_MODIFIERS.RANGE}` as const);

const dateOnValue = searchAdvancedFiltersForm?.[dateOnKey];
const dateBeforeValue = searchAdvancedFiltersForm?.[dateBeforeKey];
const dateAfterValue = searchAdvancedFiltersForm?.[dateAfterKey];
const dateRangeValue = searchAdvancedFiltersForm?.[dateRangeKey];

const defaultDateValues = useMemo(
() => ({
[CONST.SEARCH.DATE_MODIFIERS.ON]: dateOnValue,
[CONST.SEARCH.DATE_MODIFIERS.BEFORE]: dateBeforeValue,
[CONST.SEARCH.DATE_MODIFIERS.AFTER]: dateAfterValue,
[CONST.SEARCH.DATE_MODIFIERS.RANGE]: dateRangeValue,
}),
[dateAfterValue, dateBeforeValue, dateOnValue],
[dateAfterValue, dateBeforeValue, dateOnValue, dateRangeValue],
);
const [rangeDisplayText, setRangeDisplayText] = useState(() =>
getDateRangeDisplayValueFromFormValue(
defaultDateValues[CONST.SEARCH.DATE_MODIFIERS.RANGE],
defaultDateValues[CONST.SEARCH.DATE_MODIFIERS.AFTER],
defaultDateValues[CONST.SEARCH.DATE_MODIFIERS.BEFORE],
),
);

const handleDateValuesChange = useCallback(() => {
setRangeDisplayText(searchDatePresetFilterBaseRef.current?.getRangeDisplayText() ?? '');
}, []);

const presets = useMemo(() => {
const hasFeed = !!searchAdvancedFiltersForm?.feed?.length;
return getDatePresets(dateKey, hasFeed);
Expand All @@ -76,11 +97,14 @@ function DateFilterBase({title, dateKey, back, onSubmit}: DateFilterBaseProps) {

if (selectedDateModifier) {
searchDatePresetFilterBaseRef.current.clearDateValueOfSelectedDateModifier();
setRangeDisplayText(searchDatePresetFilterBaseRef.current.getRangeDisplayText());
setSelectedDateModifier(null);
setShouldShowRangeError(false);
return;
}

searchDatePresetFilterBaseRef.current.clearDateValues();
setRangeDisplayText('');
}, [selectedDateModifier]);

const save = useCallback(() => {
Expand All @@ -89,58 +113,82 @@ function DateFilterBase({title, dateKey, back, onSubmit}: DateFilterBaseProps) {
}

if (selectedDateModifier) {
if (!searchDatePresetFilterBaseRef.current.validate()) {
return;
}

searchDatePresetFilterBaseRef.current.setDateValueOfSelectedDateModifier();
setRangeDisplayText(searchDatePresetFilterBaseRef.current.getRangeDisplayText());
setSelectedDateModifier(null);
setShouldShowRangeError(false);
return;
}

const dateValues = searchDatePresetFilterBaseRef.current.getDateValues();

onSubmit({
[dateOnKey]: dateValues[CONST.SEARCH.DATE_MODIFIERS.ON] ?? null,
[dateBeforeKey]: dateValues[CONST.SEARCH.DATE_MODIFIERS.BEFORE] ?? null,
[dateAfterKey]: dateValues[CONST.SEARCH.DATE_MODIFIERS.AFTER] ?? null,
[dateRangeKey]: dateValues[CONST.SEARCH.DATE_MODIFIERS.RANGE] ?? null,
});
}, [selectedDateModifier, dateOnKey, dateBeforeKey, dateAfterKey, onSubmit]);
}, [selectedDateModifier, dateOnKey, dateBeforeKey, dateAfterKey, dateRangeKey, onSubmit]);

const goBack = () => {
if (selectedDateModifier) {
if (searchDatePresetFilterBaseRef.current && selectedDateModifier === CONST.SEARCH.DATE_MODIFIERS.RANGE) {
searchDatePresetFilterBaseRef.current.resetDateValuesToDefault();
setRangeDisplayText(searchDatePresetFilterBaseRef.current.getRangeDisplayText());
}
setSelectedDateModifier(null);
setShouldShowRangeError(false);
return;
}

back();
};

const hasRangeInput = !!rangeDisplayText;

return (
<>
<View style={styles.flex1}>
<HeaderWithBackButton
title={computedTitle}
onBackButtonPress={goBack}
/>
<ScrollView contentContainerStyle={[styles.flexGrow1]}>
<ScrollView contentContainerStyle={[styles.flexGrow1, styles.pb5]}>
<DatePresetFilterBase
ref={searchDatePresetFilterBaseRef}
defaultDateValues={defaultDateValues}
selectedDateModifier={selectedDateModifier}
onSelectDateModifier={setSelectedDateModifier}
presets={presets}
isSearchAdvancedFiltersFormLoading={isSearchAdvancedFiltersFormLoading}
shouldShowRangeError={shouldShowRangeError}
onDateValuesChange={handleDateValuesChange}
onRangeValidationErrorChange={setShouldShowRangeError}
forceVerticalCalendars
/>
{selectedDateModifier === CONST.SEARCH.DATE_MODIFIERS.RANGE && hasRangeInput && (
<Text style={[styles.textLabelSupporting, styles.mh5, styles.mt2]}>
{`${translate('common.range')}: `}
<Text style={[styles.textLabel]}>{rangeDisplayText}</Text>
</Text>
)}
<View style={styles.flexGrow1} />
<Button
text={translate('common.reset')}
onPress={reset}
style={[styles.mh4, styles.mt4]}
large
/>
<FormAlertWithSubmitButton
buttonText={translate('common.save')}
containerStyles={[styles.m4, styles.mt3]}
onSubmit={save}
enabledWhenOffline
/>
</ScrollView>
<Button
text={translate('common.reset')}
onPress={reset}
style={[styles.mh4, styles.mt4]}
large
/>
<FormAlertWithSubmitButton
buttonText={translate('common.save')}
containerStyles={[styles.m4, styles.mt3, styles.mb5]}
onSubmit={save}
enabledWhenOffline
/>
</>
</View>
);
}

Expand Down
Loading
Loading