Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2822396
prepare new popups and hooks
bernhardoj Apr 1, 2026
f6d8c02
add style prop and make label optional
bernhardoj Apr 1, 2026
1c355cd
add sentry label
bernhardoj Apr 1, 2026
989d3d4
add leftelement prop
bernhardoj Apr 1, 2026
8f09fae
rename function
bernhardoj Apr 1, 2026
acdf1e6
add more type
bernhardoj Apr 1, 2026
6828bcd
lint
bernhardoj Apr 1, 2026
5722a84
Merge branch 'main' into feat/81558-revamp-search-actions-bar-filters…
bernhardoj Apr 1, 2026
bb32693
remove isExpanded prop
bernhardoj Apr 1, 2026
4d5798f
add new sentry label
bernhardoj Apr 1, 2026
23a04f8
Merge branch 'main' into feat/81558-revamp-search-actions-bar-filters…
bernhardoj Apr 1, 2026
5bdd086
update for the new date range filter
bernhardoj Apr 1, 2026
1590a55
fix type error after merge
bernhardoj Apr 1, 2026
812b185
prettier
bernhardoj Apr 1, 2026
0a7bcac
add comment
bernhardoj Apr 2, 2026
b533a45
use undefined instead of null
bernhardoj Apr 2, 2026
5e5b641
create basepopup to DRY
bernhardoj Apr 2, 2026
6e9e4fa
minor changes
bernhardoj Apr 2, 2026
8f02a00
lint
bernhardoj Apr 2, 2026
cfe7080
make the value generic
bernhardoj Apr 2, 2026
538f99b
prettier
bernhardoj Apr 2, 2026
8c6848e
call openSearchCardFiltersPage when popup is expanded
bernhardoj Apr 3, 2026
0f24839
lint
bernhardoj Apr 3, 2026
dac3536
Merge branch 'main' into feat/81558-revamp-search-actions-bar-filters…
bernhardoj Apr 8, 2026
92f3ce8
fix missing param
bernhardoj Apr 8, 2026
d997af7
use setState function updater
bernhardoj Apr 9, 2026
0150978
move the style to BasePopup
bernhardoj Apr 9, 2026
434b5a8
Merge branch 'main' into feat/81558-revamp-search-actions-bar-filters…
bernhardoj Apr 9, 2026
44a6a85
remove unused import
bernhardoj Apr 9, 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
11 changes: 11 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8787,8 +8787,10 @@ const CONST = {
FILTER_FROM: 'Search-FilterFrom',
FILTER_WORKSPACE: 'Search-FilterWorkspace',
FILTER_GROUP_BY: 'Search-FilterGroupBy',
FILTER_SORT_BY: 'Search-FilterSortBy',
FILTER_GROUP_CURRENCY: 'Search-FilterGroupCurrency',
FILTER_VIEW: 'Search-FilterView',
FILTER_LIMIT: 'Search-FilterLimit',
FILTER_FEED: 'Search-FilterFeed',
FILTER_POSTED: 'Search-FilterPosted',
FILTER_WITHDRAWN: 'Search-FilterWithdrawn',
Expand All @@ -8811,10 +8813,19 @@ const CONST = {
FILTER_POPUP_APPLY_MULTI_SELECT: 'Search-FilterPopupApplyMultiSelect',
FILTER_POPUP_RESET_TEXT_INPUT: 'Search-FilterPopupResetTextInput',
FILTER_POPUP_APPLY_TEXT_INPUT: 'Search-FilterPopupApplyTextInput',
FILTER_POPUP_RESET_AMOUNT: 'Search-FilterPopupResetAmount',
FILTER_POPUP_APPLY_AMOUNT: 'Search-FilterPopupApplyAmount',
FILTER_POPUP_SAVE_AMOUNT: 'Search-FilterPopupSaveAmount',
FILTER_POPUP_RESET_DATE: 'Search-FilterPopupResetDate',
FILTER_POPUP_APPLY_DATE: 'Search-FilterPopupApplyDate',
FILTER_POPUP_RESET_USER: 'Search-FilterPopupResetUser',
FILTER_POPUP_APPLY_USER: 'Search-FilterPopupApplyUser',
FILTER_POPUP_RESET_REPORT: 'Search-FilterPopupResetReport',
FILTER_POPUP_APPLY_REPORT: 'Search-FilterPopupApplyReport',
FILTER_POPUP_RESET_REPORT_FIELD: 'Search-FilterPopupResetReportField',
FILTER_POPUP_APPLY_REPORT_FIELD: 'Search-FilterPopupApplyReportField',
FILTER_POPUP_RESET_CARD: 'Search-FilterPopupResetCard',
FILTER_POPUP_APPLY_CARD: 'Search-FilterPopupApplyCard',
TRANSACTION_LIST_ITEM_CHECKBOX: 'Search-TransactionListItemCheckbox',
EXPANDED_TRANSACTION_ROW: 'Search-ExpandedTransactionRow',
EXPANDED_TRANSACTION_ROW_CHECKBOX: 'Search-ExpandedTransactionRowCheckbox',
Expand Down
153 changes: 153 additions & 0 deletions src/components/Search/FilterDropdowns/AmountPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React, {useState} from 'react';
import type {ValueOf} from 'type-fest';
import AmountWithoutCurrencyInput from '@components/AmountWithoutCurrencyInput';
import MenuItem from '@components/MenuItem';
import type {SearchAmountFilterKeys} from '@components/Search/types';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {convertToBackendAmount, convertToFrontendAmountAsString} from '@libs/CurrencyUtils';
import CONST from '@src/CONST';
import type {SearchAdvancedFiltersForm} from '@src/types/form';
import BasePopup from './BasePopup';

type AmountPopupProps = {
filterKey: SearchAmountFilterKeys;
label: string;
value: Record<ValueOf<typeof CONST.SEARCH.AMOUNT_MODIFIERS>, string | undefined>;
updateFilterForm: (value: Partial<SearchAdvancedFiltersForm>) => void;
closeOverlay: () => void;
};

type AmountInputProps = {
title: string;
value: string;
name: string;
onSave: (value: string) => void;
onBackButtonPress: () => void;
};

function AmountInput({title, value, name, onSave, onBackButtonPress}: AmountInputProps) {
const styles = useThemeStyles();
const [amount, setAmount] = useState(value);

return (
<BasePopup
label={title}
onReset={() => onSave('')}
onApply={() => onSave(amount)}
resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_AMOUNT}
applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_AMOUNT}
onBackButtonPress={onBackButtonPress}
>
<AmountWithoutCurrencyInput
containerStyles={[styles.ph4, styles.mb2]}
defaultValue={amount}
onInputChange={setAmount}
label={title}
accessibilityLabel={title}
name={name}
role={CONST.ROLE.PRESENTATION}
inputMode={CONST.INPUT_MODE.DECIMAL}
shouldAllowNegative
autoFocus
/>
</BasePopup>
);
}

function AmountPopup({filterKey, label, value, closeOverlay, updateFilterForm}: AmountPopupProps) {
const {translate} = useLocalize();
const [selectedModifier, setSelectedModifier] = useState<ValueOf<typeof CONST.SEARCH.AMOUNT_MODIFIERS> | null>(null);
const [amountValues, setAmountValues] = useState(value);

const title = {
[CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO]: translate('search.filters.amount.equalTo'),
[CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN]: translate('search.filters.amount.greaterThan'),
[CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN]: translate('search.filters.amount.lessThan'),
};

const modifierConfig = [CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO, CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN, CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN];

const formatAmount = (amount: string | undefined) => {
return amount ? convertToFrontendAmountAsString(Number(amount), CONST.DEFAULT_CURRENCY_DECIMALS) : '';
};

if (selectedModifier) {
const goBack = () => {
setSelectedModifier(null);
};

const save = (rawAmount: string) => {
if (rawAmount.trim() === '') {
setAmountValues((prevAmountValues) => ({...prevAmountValues, [selectedModifier]: undefined}));
goBack();
return;
}

const newAmount = convertToBackendAmount(Number(rawAmount)).toString();

// When setting an Equal To value, clear Greater Than and Less Than to avoid conflicting filters.
if (selectedModifier === CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO) {
setAmountValues({[CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN]: '', [CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN]: '', [selectedModifier]: newAmount});
}

// When setting Greater Than or Less Than, clear Equal To to avoid conflicting filters.
if (selectedModifier === CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN || selectedModifier === CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN) {
setAmountValues((prevAmountValues) => ({...prevAmountValues, [CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO]: '', [selectedModifier]: newAmount}));
}
goBack();
};

return (
<AmountInput
title={title[selectedModifier]}
value={formatAmount(amountValues[selectedModifier])}
name={`${filterKey}${selectedModifier}`}
onBackButtonPress={goBack}
onSave={save}
/>
);
}

const onChange = (values: Record<ValueOf<typeof CONST.SEARCH.AMOUNT_MODIFIERS>, string | undefined>) => {
const formValues: Record<string, string | undefined> = {};
formValues[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO}`] = values[CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO];
formValues[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN}`] = values[CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN];
formValues[`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN}`] = values[CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN];
updateFilterForm(formValues);
closeOverlay();
};

const applyChanges = () => onChange(amountValues);

const resetChanges = () => {
onChange({
[CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO]: undefined,
[CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN]: undefined,
[CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN]: undefined,
});
};

return (
<BasePopup
label={label}
onReset={resetChanges}
onApply={applyChanges}
resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_AMOUNT}
applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_AMOUNT}
>
{modifierConfig.map((modifier) => (
<MenuItem
key={modifier}
title={title[modifier]}
description={formatAmount(amountValues[modifier])}
onPress={() => setSelectedModifier(modifier)}
shouldShowRightIcon
viewMode={CONST.OPTION_MODE.COMPACT}
/>
))}
</BasePopup>
);
}

export default AmountPopup;
49 changes: 49 additions & 0 deletions src/components/Search/FilterDropdowns/BasePopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Text from '@components/Text';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import ActionButtons from './ActionButtons';

type BasePopupProps = React.PropsWithChildren & {
label?: string;
applySentryLabel: string;
resetSentryLabel: string;
style?: StyleProp<ViewStyle>;
onApply: () => void;
onReset: () => void;
onBackButtonPress?: () => void;
};

function BasePopup({children, label, applySentryLabel, resetSentryLabel, style, onApply, onReset, onBackButtonPress}: BasePopupProps) {
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we disabling this rule instead of using shouldUseNarrowLayout?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this from the existing popup and there is no explanation.

// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();

const {isSmallScreenWidth} = useResponsiveLayout();
const styles = useThemeStyles();

return (
<View style={[!isSmallScreenWidth && styles.pv4, style]}>
{onBackButtonPress ? (
<HeaderWithBackButton
shouldDisplayHelpButton={false}
style={[styles.h10, styles.pv1, styles.mb2]}
subtitle={label}
onBackButtonPress={onBackButtonPress}
/>
) : (
isSmallScreenWidth && !!label && <Text style={[styles.textLabel, styles.textSupporting, styles.ph5, styles.pv1, styles.mb2]}>{label}</Text>
)}
{children}
<ActionButtons
containerStyle={[styles.flexRow, styles.gap2, styles.ph5, styles.mt2]}
onReset={onReset}
onApply={onApply}
applySentryLabel={applySentryLabel}
resetSentryLabel={resetSentryLabel}
/>
</View>
);
}

export default BasePopup;
Loading
Loading