diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index d1eedd560694..59621a8dd58c 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -27,6 +27,7 @@ type DropdownOption = { interactive?: boolean; numberOfLinesTitle?: number; titleStyle?: ViewStyle; + shouldCloseModalOnSelect?: boolean; }; type ButtonWithDropdownMenuProps = { diff --git a/src/components/Search/SearchListWithHeader.tsx b/src/components/Search/SearchListWithHeader.tsx index 02da657609ba..283b68bf17af 100644 --- a/src/components/Search/SearchListWithHeader.tsx +++ b/src/components/Search/SearchListWithHeader.tsx @@ -1,5 +1,6 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useState} from 'react'; +import ConfirmModal from '@components/ConfirmModal'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; @@ -7,6 +8,7 @@ import SelectionList from '@components/SelectionList'; import type {BaseSelectionListProps, ReportListItemType, SelectionListHandle, TransactionListItemType} from '@components/SelectionList/types'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as SearchActions from '@libs/actions/Search'; import * as SearchUtils from '@libs/SearchUtils'; import CONST from '@src/CONST'; import type {SearchDataTypes, SearchQuery} from '@src/types/onyx/SearchResults'; @@ -49,9 +51,31 @@ function SearchListWithHeader( const [isModalVisible, setIsModalVisible] = useState(false); const [longPressedItem, setLongPressedItem] = useState(null); const [selectedItems, setSelectedItems] = useState({}); + const [selectedItemsToDelete, setSelectedItemsToDelete] = useState([]); + const [deleteExpensesConfirmModalVisible, setDeleteExpensesConfirmModalVisible] = useState(false); + + const handleOnSelectDeleteOption = (itemsToDelete: string[]) => { + setSelectedItemsToDelete(itemsToDelete); + setDeleteExpensesConfirmModalVisible(true); + }; + + const handleOnCancelConfirmModal = () => { + setSelectedItemsToDelete([]); + setDeleteExpensesConfirmModalVisible(false); + }; const clearSelectedItems = () => setSelectedItems({}); + const handleDeleteExpenses = () => { + if (selectedItemsToDelete.length === 0) { + return; + } + + clearSelectedItems(); + setDeleteExpensesConfirmModalVisible(false); + SearchActions.deleteMoneyRequestOnSearch(hash, selectedItemsToDelete); + }; + useEffect(() => { clearSelectedItems(); }, [hash]); @@ -151,6 +175,7 @@ function SearchListWithHeader( clearSelectedItems={clearSelectedItems} query={query} hash={hash} + onSelectDeleteOption={handleOnSelectDeleteOption} isMobileSelectionModeActive={isMobileSelectionModeActive} setIsMobileSelectionModeActive={setIsMobileSelectionModeActive} /> @@ -166,7 +191,16 @@ function SearchListWithHeader( onSelectAll={toggleAllTransactions} isMobileSelectionModeActive={isMobileSelectionModeActive} /> - + void; hash: number; + onSelectDeleteOption?: (itemsToDelete: string[]) => void; isMobileSelectionModeActive?: boolean; setIsMobileSelectionModeActive?: (isMobileSelectionModeActive: boolean) => void; }; type SearchHeaderOptionValue = DeepValueOf | undefined; -function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchHeaderProps) { +function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems, onSelectDeleteOption, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchPageHeaderProps) { const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); @@ -51,20 +52,15 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems, return options; } - const itemsToDelete = selectedItemsKeys.filter((id) => selectedItems[id].canDelete); + const itemsToDelete = Object.keys(selectedItems ?? {}).filter((id) => selectedItems[id].canDelete); if (itemsToDelete.length > 0) { options.push({ icon: Expensicons.Trashcan, text: translate('search.bulkActions.delete'), value: CONST.SEARCH.BULK_ACTION_TYPES.DELETE, - onSelected: () => { - clearSelectedItems?.(); - if (isMobileSelectionModeActive) { - setIsMobileSelectionModeActive?.(false); - } - SearchActions.deleteMoneyRequestOnSearch(hash, itemsToDelete); - }, + shouldCloseModalOnSelect: true, + onSelected: () => onSelectDeleteOption?.(itemsToDelete), }); } @@ -121,7 +117,19 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems, } return options; - }, [clearSelectedItems, hash, selectedItems, selectedItemsKeys, styles, theme, translate, isMobileSelectionModeActive, setIsMobileSelectionModeActive]); + }, [ + selectedItemsKeys, + selectedItems, + translate, + onSelectDeleteOption, + clearSelectedItems, + isMobileSelectionModeActive, + hash, + setIsMobileSelectionModeActive, + theme.icon, + styles.colorMuted, + styles.fontWeightNormal, + ]); if (isSmallScreenWidth) { if (isMobileSelectionModeActive) { diff --git a/src/languages/en.ts b/src/languages/en.ts index a0ec026f79e4..f127fa434145 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -24,6 +24,7 @@ import type { DelegateSubmitParams, DeleteActionParams, DeleteConfirmationParams, + DeleteExpenseTranslationParams, DidSplitAmountMessageParams, DistanceRateOperationsParams, EditActionParams, @@ -702,8 +703,8 @@ export default { `${count} ${Str.pluralize('expense', 'expenses', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pending` : '' }`, - deleteExpense: 'Delete expense', - deleteConfirmation: 'Are you sure that you want to delete this expense?', + deleteExpense: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Delete ${Str.pluralize('expense', 'expenses', count)}`, + deleteConfirmation: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Are you sure that you want to delete ${Str.pluralize('this expense', 'these expenses', count)}?`, settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', individual: 'Individual', diff --git a/src/languages/es.ts b/src/languages/es.ts index a1c1b75d4c86..a4ce53f1fe05 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -22,6 +22,7 @@ import type { DelegateSubmitParams, DeleteActionParams, DeleteConfirmationParams, + DeleteExpenseTranslationParams, DidSplitAmountMessageParams, DistanceRateOperationsParams, EditActionParams, @@ -695,8 +696,9 @@ export default { `${count} ${Str.pluralize('gasto', 'gastos', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : '' }`, - deleteExpense: 'Eliminar gasto', - deleteConfirmation: '¿Estás seguro de que quieres eliminar esta solicitud?', + + deleteExpense: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Eliminar ${Str.pluralize('gasto', 'gastos', count)}`, + deleteConfirmation: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `¿Estás seguro de que quieres eliminar ${Str.pluralize('esta solicitud', 'estas solicitudes', count)}?`, settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', individual: 'Individual', diff --git a/src/languages/types.ts b/src/languages/types.ts index 78a711fe8282..ae21a804f06f 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -341,6 +341,10 @@ type RemoveMembersWarningPrompt = { ownerName: string; }; +type DeleteExpenseTranslationParams = { + count: number; +}; + export type { AddressLineParams, AdminCanceledRequestParams, @@ -460,4 +464,5 @@ export type { StripePaidParams, UnapprovedParams, RemoveMembersWarningPrompt, + DeleteExpenseTranslationParams, }; diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx index b90142ff5873..4de6ee1eef57 100644 --- a/src/pages/Search/SearchSelectedNarrow.tsx +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -15,6 +15,7 @@ type SearchSelectedNarrowProps = {options: Array(null); @@ -22,6 +23,27 @@ function SearchSelectedNarrow({options, itemsLength}: SearchSelectedNarrowProps) const openMenu = () => setIsModalVisible(true); const closeMenu = () => setIsModalVisible(false); + const handleOnModalHide = () => { + if (selectedOptionIndexRef.current === -1) { + return; + } + options[selectedOptionIndexRef.current]?.onSelected?.(); + }; + + const handleOnMenuItemPress = (option: DropdownOption, index: number) => { + if (option?.shouldCloseModalOnSelect) { + selectedOptionIndexRef.current = index; + closeMenu(); + return; + } + option?.onSelected?.(); + }; + + const handleOnCloseMenu = () => { + selectedOptionIndexRef.current = -1; + closeMenu(); + }; + return (