Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d5746a1
add filter negation feature
bernhardoj Jun 18, 2026
a194ae7
remove unused export
bernhardoj Jun 18, 2026
c968a64
simplify
bernhardoj Jun 18, 2026
9f46783
change font to 13px
bernhardoj Jun 18, 2026
f335f57
fix text based filter negated value is not shown
bernhardoj Jun 18, 2026
5d463f6
lint
bernhardoj Jun 18, 2026
548cef6
Merge branch 'main' into feat/93208-filter-negation
bernhardoj Jun 25, 2026
9b62cc9
use - prefix when filter is negated
bernhardoj Jun 25, 2026
0b87dfa
only show negated filter for negatable filter
bernhardoj Jun 25, 2026
f64fa39
Merge branch 'main' into feat/93208-filter-negation
bernhardoj Jun 25, 2026
5ee5120
restore
bernhardoj Jun 25, 2026
ac1dd57
revert unintended changes
bernhardoj Jun 25, 2026
5e18998
add translation
bernhardoj Jun 25, 2026
731cf74
Merge branch 'main' into feat/93208-filter-negation
bernhardoj Jun 25, 2026
e6b6a9b
Merge branch 'main' into feat/93208-filter-negation
bernhardoj Jun 26, 2026
98c7265
Merge branch 'main' into feat/93208-filter-negation
bernhardoj Jul 14, 2026
601db5f
Merge branch 'main' into feat/93208-filter-negation
bernhardoj Jul 14, 2026
2b23c23
fix error after conflicts
bernhardoj Jul 14, 2026
11b7a2f
show the display value for negated status
bernhardoj Jul 14, 2026
c25bc0c
add back the merchant negation switch
bernhardoj Jul 14, 2026
06dcde7
fix type
bernhardoj Jul 14, 2026
d8f14c7
add back the merchant negation switch
bernhardoj Jul 14, 2026
f16fd16
lint
bernhardoj Jul 14, 2026
f797c1f
rename
bernhardoj Jul 14, 2026
5df81ff
Merge branch 'main' into feat/93208-filter-negation
bernhardoj Jul 14, 2026
e483905
change submodule
bernhardoj Jul 14, 2026
3e9b039
Merge branch 'main' into feat/93208-filter-negation
bernhardoj Jul 15, 2026
7bb704a
clear the negated status and has too
bernhardoj Jul 15, 2026
bcf345b
simplify
bernhardoj Jul 15, 2026
ff4270f
lint
bernhardoj Jul 15, 2026
f394233
lint
bernhardoj Jul 15, 2026
fac08fd
put the not prefix in a const
bernhardoj Jul 15, 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
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7338,6 +7338,7 @@ const CONST = {
[this.TABLE_COLUMNS.GROUP_WITHDRAWAL_STATUS]: 'group-withdrawal-status',
};
},
NOT_PREFIX: '-',
NOT_MODIFIER: 'Not',
DATE_MODIFIERS: {
ON: 'On',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {View} from 'react-native';
const BETWEEN_MODIFIER = 'Between';

type AmountFilterContentProps = {
filterKey: SearchAmountFilterKeys;
baseFilterKey: SearchAmountFilterKeys;
value: SearchAmountValues;
largeButton?: boolean;
autoFocus?: boolean;
Expand All @@ -41,7 +41,7 @@ type AmountFilterHandle = {

type AmountInputProps = {
ref: React.Ref<AmountFilterHandle>;
filterKey: SearchAmountFilterKeys;
baseFilterKey: SearchAmountFilterKeys;
modifier: ValueOf<typeof CONST.SEARCH.AMOUNT_MODIFIERS>;
value: string;
label: string;
Expand All @@ -56,15 +56,15 @@ function getFrontendAmount(amount: string | undefined) {
return amount ? convertToFrontendAmountAsString(Number(amount), CONST.DEFAULT_CURRENCY_DECIMALS) : '';
}

function AmountInput({ref, filterKey, modifier, value, label, autoFocus}: AmountInputProps) {
function AmountInput({ref, baseFilterKey, modifier, value, label, autoFocus}: AmountInputProps) {
const styles = useThemeStyles();
const [amount, setAmount] = useState(value);
const {didScreenTransitionEnd} = useScreenWrapperTransitionStatus();
const inputRef = useRef<BaseTextInputRef>(null);

useImperativeHandle(ref, () => ({
getValue: () => {
const key = `${filterKey}${modifier}`;
const key = `${baseFilterKey}${modifier}`;
return {[key]: getBackendAmount(amount)};
},
}));
Expand Down Expand Up @@ -93,13 +93,13 @@ function AmountInput({ref, filterKey, modifier, value, label, autoFocus}: Amount

type AmountBetweenInputProps = {
ref: React.Ref<AmountFilterHandle>;
filterKey: SearchAmountFilterKeys;
baseFilterKey: SearchAmountFilterKeys;
greaterThanValue: string;
lessThanValue: string;
autoFocus?: boolean;
};

function AmountBetweenInput({ref, filterKey, greaterThanValue, lessThanValue, autoFocus}: AmountBetweenInputProps) {
function AmountBetweenInput({ref, baseFilterKey, greaterThanValue, lessThanValue, autoFocus}: AmountBetweenInputProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const [greaterThanAmount, setGreaterThanAmount] = useState(greaterThanValue);
Expand All @@ -109,8 +109,8 @@ function AmountBetweenInput({ref, filterKey, greaterThanValue, lessThanValue, au

useImperativeHandle(ref, () => ({
getValue: () => {
const greaterThanKey = `${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN}`;
const lessThanKey = `${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN}`;
const greaterThanKey = `${baseFilterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN}`;
const lessThanKey = `${baseFilterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN}`;
return {[greaterThanKey]: getBackendAmount(greaterThanAmount), [lessThanKey]: getBackendAmount(lessThanAmount)};
},
}));
Expand Down Expand Up @@ -152,7 +152,7 @@ function AmountBetweenInput({ref, filterKey, greaterThanValue, lessThanValue, au
);
}

function AmountFilterContent({filterKey, value, autoFocus, largeButton, style, onChange}: AmountFilterContentProps) {
function AmountFilterContent({baseFilterKey, value, autoFocus, largeButton, style, onChange}: AmountFilterContentProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -184,9 +184,9 @@ function AmountFilterContent({filterKey, value, autoFocus, largeButton, style, o
}

const formValues: AmountFilterValues = {};
formValues[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO}`] = undefined;
formValues[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN}`] = undefined;
formValues[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN}`] = undefined;
formValues[`${baseFilterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO}`] = undefined;
formValues[`${baseFilterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN}`] = undefined;
formValues[`${baseFilterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN}`] = undefined;
onChange({
...formValues,
...inputRef.current.getValue(),
Expand All @@ -203,7 +203,7 @@ function AmountFilterContent({filterKey, value, autoFocus, largeButton, style, o
{keyForList: CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN, text: translate('search.filters.amount.lessThan'), isSelected: selectedModifier === CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN},
{keyForList: BETWEEN_MODIFIER, text: translate('search.filters.amount.between'), isSelected: selectedModifier === BETWEEN_MODIFIER},
];
const label = translate(FILTER_VIEW_MAP[filterKey].labelKey);
const label = translate(FILTER_VIEW_MAP[baseFilterKey].labelKey);

return (
<View style={[styles.flex1, styles.justifyContentBetween, style]}>
Expand All @@ -226,15 +226,15 @@ function AmountFilterContent({filterKey, value, autoFocus, largeButton, style, o
(config.keyForList === BETWEEN_MODIFIER ? (
<AmountBetweenInput
ref={inputRef}
filterKey={filterKey}
baseFilterKey={baseFilterKey}
greaterThanValue={getFrontendAmount(value?.[CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN])}
lessThanValue={getFrontendAmount(value?.[CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN])}
autoFocus={autoFocus}
/>
) : (
<AmountInput
ref={inputRef}
filterKey={filterKey}
baseFilterKey={baseFilterKey}
modifier={config.keyForList}
value={getFrontendAmount(value?.[config.keyForList])}
label={label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {StyleProp, ViewStyle} from 'react-native';
import React, {useRef} from 'react';

type DateFilterContentProps = {
filterKey: SearchDateFilterKeys;
baseFilterKey: SearchDateFilterKeys;
value: SearchDateValues;
selectedDateModifier: SearchDateModifier | null;
hasFeed: boolean;
Expand All @@ -27,7 +27,7 @@ type DateFilterContentProps = {
onChange: (values: SearchDateValues) => void;
};

function DateFilterContent({filterKey, value, selectedDateModifier, hasFeed, largeButton, style, onDateModifierSelected, onChange}: DateFilterContentProps) {
function DateFilterContent({baseFilterKey, value, selectedDateModifier, hasFeed, largeButton, style, onDateModifierSelected, onChange}: DateFilterContentProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const dateFilterRef = useRef<DateFilterBaseHandle>(null);
Expand All @@ -54,7 +54,7 @@ function DateFilterContent({filterKey, value, selectedDateModifier, hasFeed, lar
selectedDateModifier={selectedDateModifier}
onSelectDateModifier={onDateModifierSelected}
defaultDateValues={value}
presets={getDatePresets(filterKey, hasFeed)}
presets={getDatePresets(baseFilterKey, hasFeed)}
onSubmit={onChange}
shouldShowActionButtons={false}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type {ListFilterContentProps} from '@components/Search/FilterComponents/ListFilterContent';

import {getFilterFormValues} from '@libs/SearchQueryUtils';
import {getFilterNegatableValue, isAmountFilterKey, isDateFilterKey, isTextFilterKey} from '@libs/SearchUIUtils';
import type {SearchFilter} from '@libs/SearchUIUtils';

Expand All @@ -6,96 +9,82 @@ import type {SearchAdvancedFiltersForm} from '@src/types/form';

import React from 'react';

import type {FilterComponentsProps} from '..';
import type {AmountFilterContentProps} from './AmountFilterContent';
import type {DateFilterContentProps} from './DateFilterContent';
import type {ReportFieldFilterContentProps} from './ReportFieldFilterContent';
import type {TextInputFilterContentProps} from './TextInputFilterContent';

type TextInputFilterContentWrapperProps = Pick<TextInputFilterContentProps, 'filterKey' | 'value' | 'onChange'>;
type AmountFilterContentWrapperProps = Pick<AmountFilterContentProps, 'filterKey' | 'value' | 'onChange'>;
type DateFilterContentWrapperProps = Pick<DateFilterContentProps, 'filterKey' | 'value' | 'hasFeed' | 'onChange'>;
type TextInputFilterContentWrapperProps = Pick<TextInputFilterContentProps, 'baseFilterKey' | 'value' | 'isNegated' | 'onChange'>;
type AmountFilterContentWrapperProps = Pick<AmountFilterContentProps, 'baseFilterKey' | 'value' | 'onChange'>;
type DateFilterContentWrapperProps = Pick<DateFilterContentProps, 'baseFilterKey' | 'value' | 'hasFeed' | 'onChange'>;
type ReportFieldFilterContentWrapperProps = Pick<ReportFieldFilterContentProps, 'values' | 'onChange'>;
type CommonFilterContentWrapperProps = Omit<FilterComponentsProps, 'selectionListTextInputStyle' | 'selectionListStyle' | 'autoFocus' | 'footer'>;
type ListFilterContentWrapperProps = Omit<ListFilterContentProps, 'onChange' | 'onNegationChange' | 'selectionListTextInputStyle' | 'selectionListStyle' | 'autoFocus' | 'footer'> & {
onChange: (value: ListFilterContentProps['value'], isNegated: boolean) => void;
};

type SearchAdvancedFiltersContentProps = {
filterKey: SearchFilter['key'];
baseFilterKey: SearchFilter['key'];
values: Partial<SearchAdvancedFiltersForm> | undefined;
ready?: boolean;
components: {
Text: React.ComponentType<TextInputFilterContentWrapperProps>;
Amount: React.ComponentType<AmountFilterContentWrapperProps>;
Date: React.ComponentType<DateFilterContentWrapperProps>;
ReportField: React.ComponentType<ReportFieldFilterContentWrapperProps>;
Common: React.ComponentType<CommonFilterContentWrapperProps>;
List: React.ComponentType<ListFilterContentWrapperProps>;
};
onChange: (values: Partial<SearchAdvancedFiltersForm>) => void;
};

function getFilterFormValue<K extends FilterComponentsProps['filterKey']>(filterKey: K, value: SearchAdvancedFiltersForm[K] | undefined): Partial<SearchAdvancedFiltersForm> {
const update: Partial<SearchAdvancedFiltersForm> = {};
update[filterKey] = value;
return update;
}

function SearchAdvancedFiltersContent({filterKey, values, ready, components, onChange}: SearchAdvancedFiltersContentProps) {
const {Text: TextFilter, Amount: AmountFilter, Date: DateFilter, ReportField: ReportFieldFilter, Common: CommonFilter} = components;

if (isTextFilterKey(filterKey)) {
return (
<TextFilter
key={filterKey}
filterKey={filterKey}
value={values?.[filterKey]}
onChange={(newValue) => onChange({[filterKey]: newValue})}
/>
);
}

if (isAmountFilterKey(filterKey)) {
function SearchAdvancedFiltersContent({baseFilterKey, values, ready, components, onChange}: SearchAdvancedFiltersContentProps) {
if (isAmountFilterKey(baseFilterKey)) {
const AmountFilter = components.Amount;
return (
<AmountFilter
key={filterKey}
filterKey={filterKey}
key={baseFilterKey}
baseFilterKey={baseFilterKey}
value={{
[CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO]: values?.[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO}`],
[CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN]: values?.[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN}`],
[CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN]: values?.[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN}`],
[CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO]: values?.[`${baseFilterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO}`],
[CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN]: values?.[`${baseFilterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN}`],
[CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN]: values?.[`${baseFilterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN}`],
}}
onChange={onChange}
/>
);
}

if (isDateFilterKey(filterKey)) {
if (isDateFilterKey(baseFilterKey)) {
const DateFilter = components.Date;
const onModifier = CONST.SEARCH.DATE_MODIFIERS.ON;
const afterModifier = CONST.SEARCH.DATE_MODIFIERS.AFTER;
const beforeModifier = CONST.SEARCH.DATE_MODIFIERS.BEFORE;
const rangeModifier = CONST.SEARCH.DATE_MODIFIERS.RANGE;

return (
<DateFilter
key={filterKey}
filterKey={filterKey}
key={baseFilterKey}
baseFilterKey={baseFilterKey}
value={{
[onModifier]: values?.[`${filterKey}${onModifier}`],
[afterModifier]: values?.[`${filterKey}${afterModifier}`],
[beforeModifier]: values?.[`${filterKey}${beforeModifier}`],
[rangeModifier]: values?.[`${filterKey}${rangeModifier}`],
[onModifier]: values?.[`${baseFilterKey}${onModifier}`],
[afterModifier]: values?.[`${baseFilterKey}${afterModifier}`],
[beforeModifier]: values?.[`${baseFilterKey}${beforeModifier}`],
[rangeModifier]: values?.[`${baseFilterKey}${rangeModifier}`],
}}
hasFeed={!!values?.feed}
onChange={(newValues) =>
onChange({
[`${filterKey}${onModifier}`]: newValues[onModifier],
[`${filterKey}${afterModifier}`]: newValues[afterModifier],
[`${filterKey}${beforeModifier}`]: newValues[beforeModifier],
[`${filterKey}${rangeModifier}`]: newValues[rangeModifier],
[`${baseFilterKey}${onModifier}`]: newValues[onModifier],
[`${baseFilterKey}${afterModifier}`]: newValues[afterModifier],
[`${baseFilterKey}${beforeModifier}`]: newValues[beforeModifier],
[`${baseFilterKey}${rangeModifier}`]: newValues[rangeModifier],
})
}
/>
);
}

if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_FIELD) {
if (baseFilterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_FIELD) {
const ReportFieldFilter = components.ReportField;
return (
<ReportFieldFilter
values={values}
Expand All @@ -104,18 +93,35 @@ function SearchAdvancedFiltersContent({filterKey, values, ready, components, onC
);
}

if (isTextFilterKey(baseFilterKey)) {
const {isNegated, value} = getFilterNegatableValue(baseFilterKey, values);
const TextFilter = components.Text;
return (
<TextFilter
key={baseFilterKey}
baseFilterKey={baseFilterKey}
value={value}
isNegated={isNegated}
onChange={(newValue, negated) => onChange(getFilterFormValues(baseFilterKey, newValue, negated))}
/>
);
}

const {isNegated, value} = getFilterNegatableValue(baseFilterKey, values);
const ListFilter = components.List;
return (
<CommonFilter
key={filterKey}
filterKey={filterKey}
value={values?.[filterKey]}
<ListFilter
key={baseFilterKey}
baseFilterKey={baseFilterKey}
value={value}
type={values?.type}
policyID={getFilterNegatableValue(CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID, values)}
ready={ready}
onChange={(newValue) => onChange(getFilterFormValue(filterKey, newValue))}
isNegated={isNegated}
onChange={(newValue, negated) => onChange(getFilterFormValues(baseFilterKey, newValue, negated))}
/>
);
}

export default SearchAdvancedFiltersContent;
export type {TextInputFilterContentWrapperProps, AmountFilterContentWrapperProps, DateFilterContentWrapperProps, ReportFieldFilterContentWrapperProps, CommonFilterContentWrapperProps};
export type {TextInputFilterContentWrapperProps, AmountFilterContentWrapperProps, DateFilterContentWrapperProps, ReportFieldFilterContentWrapperProps, ListFilterContentWrapperProps};
Loading
Loading