diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 26c45ff95853..900c57c4f8e0 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -6330,6 +6330,7 @@ const CONST = { REPORTS: 'reports', FROM: 'from', CARD: 'card', + WITHDRAWAL_ID: 'withdrawal-id', }, BOOLEAN: { YES: 'yes', diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index d768dbe56510..271a2644f20f 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; import {isMoneyRequestReport} from '@libs/ReportUtils'; -import {isTransactionCardGroupListItemType, isTransactionListItemType, isTransactionMemberGroupListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; +import {isTransactionListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; import type {SearchKey} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -79,23 +79,7 @@ function SearchContextProvider({children}: ChildrenProps) { selectedReports = data .filter((item) => isMoneyRequestReport(item) && item.transactions.length > 0 && item.transactions.every(({keyForList}) => selectedTransactions[keyForList]?.isSelected)) .map(({reportID, action = CONST.SEARCH.ACTION_TYPES.VIEW, total = CONST.DEFAULT_NUMBER_ID, policyID}) => ({reportID, action, total, policyID})); - } - - if (data.length && data.every(isTransactionMemberGroupListItemType)) { - selectedReports = data - .flatMap((item) => item.transactions) - .filter(({keyForList}) => !!keyForList && selectedTransactions[keyForList]?.isSelected) - .map(({reportID, action = CONST.SEARCH.ACTION_TYPES.VIEW, amount: total = CONST.DEFAULT_NUMBER_ID, policyID}) => ({reportID, action, total, policyID})); - } - - if (data.length && data.every(isTransactionCardGroupListItemType)) { - selectedReports = data - .flatMap((item) => item.transactions) - .filter(({keyForList}) => !!keyForList && selectedTransactions[keyForList]?.isSelected) - .map(({reportID, action = CONST.SEARCH.ACTION_TYPES.VIEW, amount: total = CONST.DEFAULT_NUMBER_ID, policyID}) => ({reportID, action, total, policyID})); - } - - if (data.length && data.every(isTransactionListItemType)) { + } else if (data.length && data.every(isTransactionListItemType)) { selectedReports = data .filter(({keyForList}) => !!keyForList && selectedTransactions[keyForList]?.isSelected) .map(({reportID, action = CONST.SEARCH.ACTION_TYPES.VIEW, amount: total = CONST.DEFAULT_NUMBER_ID, policyID}) => ({reportID, action, total, policyID})); diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx index d67a91471a0e..fcb99c9936e0 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx @@ -18,7 +18,7 @@ import SingleSelectPopup from '@components/Search/FilterDropdowns/SingleSelectPo import UserSelectPopup from '@components/Search/FilterDropdowns/UserSelectPopup'; import {useSearchContext} from '@components/Search/SearchContext'; import type {SearchDateValues} from '@components/Search/SearchDatePresetFilterBase'; -import type {SearchDateFilterKeys, SearchGroupBy, SearchQueryJSON, SingularSearchStatus} from '@components/Search/types'; +import type {SearchDateFilterKeys, SearchQueryJSON, SingularSearchStatus} from '@components/Search/types'; import SearchFiltersSkeleton from '@components/Skeletons/SearchFiltersSkeleton'; import useAdvancedSearchFilters from '@hooks/useAdvancedSearchFilters'; import useLocalize from '@hooks/useLocalize'; @@ -396,12 +396,11 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions, isMobileSelectionMod const filters = useMemo(() => { const fromValue = filterFormValues.from?.map((accountID) => personalDetails?.[accountID]?.displayName ?? accountID) ?? []; - const shouldDisplayGroupByFilter = groupBy?.value === CONST.SEARCH.GROUP_BY.FROM || groupBy?.value === CONST.SEARCH.GROUP_BY.CARD; - const shouldDisplayGroupCurrencyFilter = (groupBy?.value === CONST.SEARCH.GROUP_BY.FROM || groupBy?.value === CONST.SEARCH.GROUP_BY.CARD) && hasMultipleOutputCurrency; + const shouldDisplayGroupByFilter = !!groupBy?.value && groupBy?.value !== CONST.SEARCH.GROUP_BY.REPORTS; + const shouldDisplayGroupCurrencyFilter = shouldDisplayGroupByFilter && hasMultipleOutputCurrency; const shouldDisplayFeedFilter = feedOptions.length > 1 && !!filterFormValues.feed; const shouldDisplayPostedFilter = !!filterFormValues.feed && (!!filterFormValues.postedOn || !!filterFormValues.postedAfter || !!filterFormValues.postedBefore); - // We'll refactor this to use a const in https://github.com/Expensify/App/issues/68227 - const shouldDisplayWithdrawalTypeFilter = groupBy?.value === ('withdrawalID' as SearchGroupBy) && !!filterFormValues.withdrawalType; + const shouldDisplayWithdrawalTypeFilter = groupBy?.value === CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID && !!filterFormValues.withdrawalType; const shouldDisplayWithdrawnFilter = !!filterFormValues.withdrawnOn || !!filterFormValues.withdrawnAfter || !!filterFormValues.withdrawnBefore; const filterList = [ diff --git a/src/components/SelectionList/Search/TransactionGroupListItem.tsx b/src/components/SelectionList/Search/TransactionGroupListItem.tsx index f119fe715ef2..c11569db81b8 100644 --- a/src/components/SelectionList/Search/TransactionGroupListItem.tsx +++ b/src/components/SelectionList/Search/TransactionGroupListItem.tsx @@ -137,6 +137,10 @@ function TransactionGroupListItem({ canSelectMultiple={canSelectMultiple} /> ), + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: ( + // Will be implemented as part of https://github.com/Expensify/App/pull/66078 + + ), }; if (!groupBy) { diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index ae947cfa5d4d..0c8cc8429cb7 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -25,7 +25,16 @@ import type CONST from '@src/CONST'; import type {PersonalDetailsList, Policy, Report, TransactionViolation} from '@src/types/onyx'; import type {Attendee, SplitExpense} from '@src/types/onyx/IOU'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type {SearchCardGroup, SearchMemberGroup, SearchPersonalDetails, SearchReport, SearchReportAction, SearchTask, SearchTransaction} from '@src/types/onyx/SearchResults'; +import type { + SearchCardGroup, + SearchMemberGroup, + SearchPersonalDetails, + SearchReport, + SearchReportAction, + SearchTask, + SearchTransaction, + SearchWithdrawalIDGroup, +} from '@src/types/onyx/SearchResults'; import type {ReceiptErrors} from '@src/types/onyx/Transaction'; import type Transaction from '@src/types/onyx/Transaction'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -339,6 +348,8 @@ type TransactionMemberGroupListItemType = TransactionGroupListItemType & {groupe type TransactionCardGroupListItemType = TransactionGroupListItemType & {groupedBy: typeof CONST.SEARCH.GROUP_BY.CARD} & SearchPersonalDetails & SearchCardGroup; +type TransactionWithdrawalIDGroupListItemType = TransactionGroupListItemType & {groupedBy: typeof CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID} & SearchPersonalDetails & SearchWithdrawalIDGroup; + type ListItemProps = CommonListItemProps & { /** The section list item */ item: TItem; @@ -916,6 +927,7 @@ export type { TransactionReportGroupListItemType, TransactionMemberGroupListItemType, TransactionCardGroupListItemType, + TransactionWithdrawalIDGroupListItemType, Section, SectionListDataType, SectionWithIndexOffset, diff --git a/src/hooks/useAdvancedSearchFilters.ts b/src/hooks/useAdvancedSearchFilters.ts index cec8fd8c95c1..eba0c779aab3 100644 --- a/src/hooks/useAdvancedSearchFilters.ts +++ b/src/hooks/useAdvancedSearchFilters.ts @@ -208,7 +208,7 @@ function useAdvancedSearchFilters() { const shouldDisplayCardFilter = shouldDisplayFilter(Object.keys(allCards).length, areCardsEnabled); const shouldDisplayTaxFilter = shouldDisplayFilter(Object.keys(taxRates).length, areTaxEnabled); const shouldDisplayWorkspaceFilter = workspaces.some((section) => section.data.length !== 0); - const shouldDisplayGroupByFilter = groupBy === CONST.SEARCH.GROUP_BY.FROM || groupBy === CONST.SEARCH.GROUP_BY.CARD; + const shouldDisplayGroupByFilter = !!groupBy && groupBy !== CONST.SEARCH.GROUP_BY.REPORTS; let currentType = searchAdvancedFilters?.type ?? CONST.SEARCH.DATA_TYPES.EXPENSE; diff --git a/src/languages/de.ts b/src/languages/de.ts index 2962b78f45c8..24075db4e304 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6082,9 +6082,10 @@ const translations = { billable: 'Abrechenbar', reimbursable: 'Erstattungsfähig', groupBy: { - reports: 'Bericht', - from: 'Von', - card: 'Karte', + [CONST.SEARCH.GROUP_BY.REPORTS]: 'Bericht', + [CONST.SEARCH.GROUP_BY.FROM]: 'Von', + [CONST.SEARCH.GROUP_BY.CARD]: 'Karte', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Auszahlungs-ID', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 0e128b0b0ff5..6b3aa34a76f3 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6057,9 +6057,10 @@ const translations = { billable: 'Billable', reimbursable: 'Reimbursable', groupBy: { - reports: 'Report', // s77rt use singular key name - from: 'From', - card: 'Card', + [CONST.SEARCH.GROUP_BY.REPORTS]: 'Report', + [CONST.SEARCH.GROUP_BY.FROM]: 'From', + [CONST.SEARCH.GROUP_BY.CARD]: 'Card', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Withdrawal ID', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/es.ts b/src/languages/es.ts index b4a8f2ef7c30..2d5402374455 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6077,9 +6077,10 @@ const translations = { billable: 'Facturable', reimbursable: 'Reembolsable', groupBy: { - reports: 'Informe', - from: 'De', - card: 'Tarjeta', + [CONST.SEARCH.GROUP_BY.REPORTS]: 'Informe', + [CONST.SEARCH.GROUP_BY.FROM]: 'De', + [CONST.SEARCH.GROUP_BY.CARD]: 'Tarjeta', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID de retiro', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/fr.ts b/src/languages/fr.ts index d36062263e52..44204f4c8b12 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6094,9 +6094,10 @@ const translations = { billable: 'Facturable', reimbursable: 'Remboursable', groupBy: { - reports: 'Rapport', - from: 'De', - card: 'Carte', + [CONST.SEARCH.GROUP_BY.REPORTS]: 'Rapport', + [CONST.SEARCH.GROUP_BY.FROM]: 'De', + [CONST.SEARCH.GROUP_BY.CARD]: 'Carte', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID de retrait', }, feed: 'Flux', withdrawalType: { diff --git a/src/languages/it.ts b/src/languages/it.ts index 568dffb32a5b..da0c3fd5da1a 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6097,9 +6097,10 @@ const translations = { billable: 'Fatturabile', reimbursable: 'Rimborsabile', groupBy: { - reports: 'Rapporto', - from: 'Da', - card: 'Carta', + [CONST.SEARCH.GROUP_BY.REPORTS]: 'Rapporto', + [CONST.SEARCH.GROUP_BY.FROM]: 'Da', + [CONST.SEARCH.GROUP_BY.CARD]: 'Carta', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID di prelievo', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 069646f4681c..3a839d07010c 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -6055,9 +6055,10 @@ const translations = { billable: 'ビラブル', reimbursable: '払い戻し可能', groupBy: { - reports: '報告', - from: 'から', - card: 'カード', + [CONST.SEARCH.GROUP_BY.REPORTS]: '報告', + [CONST.SEARCH.GROUP_BY.FROM]: 'から', + [CONST.SEARCH.GROUP_BY.CARD]: 'カード', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: '出金ID', }, feed: 'フィード', withdrawalType: { diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 69252cc2a850..9f9dc1f9b1ac 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6090,9 +6090,10 @@ const translations = { billable: 'Factureerbaar', reimbursable: 'Vergoedbaar', groupBy: { - reports: 'Verslag', - from: 'Van', - card: 'Kaart', + [CONST.SEARCH.GROUP_BY.REPORTS]: 'Verslag', + [CONST.SEARCH.GROUP_BY.FROM]: 'Van', + [CONST.SEARCH.GROUP_BY.CARD]: 'Kaart', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Opname-ID', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/pl.ts b/src/languages/pl.ts index a673e3fb270f..7bbd38eb0e91 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6075,9 +6075,10 @@ const translations = { billable: 'Podlegające fakturowaniu', reimbursable: 'Podlegające zwrotowi', groupBy: { - reports: 'Raport', - from: 'Od', - card: 'Karta', + [CONST.SEARCH.GROUP_BY.REPORTS]: 'Raport', + [CONST.SEARCH.GROUP_BY.FROM]: 'Od', + [CONST.SEARCH.GROUP_BY.CARD]: 'Karta', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Identyfikator wypłaty', }, feed: 'Kanal', withdrawalType: { diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 99d8c1c1ff01..344b3bd485f2 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6089,9 +6089,10 @@ const translations = { billable: 'Faturável', reimbursable: 'Reembolsável', groupBy: { - reports: 'Relatório', - from: 'De', - card: 'Cartão', + [CONST.SEARCH.GROUP_BY.REPORTS]: 'Relatório', + [CONST.SEARCH.GROUP_BY.FROM]: 'De', + [CONST.SEARCH.GROUP_BY.CARD]: 'Cartão', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID de retirada', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index c58f485b8cfb..5e0f238759f2 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5974,9 +5974,10 @@ const translations = { billable: '可计费的', reimbursable: '可报销的', groupBy: { - reports: '报告', - from: '从', - card: '卡片', + [CONST.SEARCH.GROUP_BY.REPORTS]: '报告', + [CONST.SEARCH.GROUP_BY.FROM]: '从', + [CONST.SEARCH.GROUP_BY.CARD]: '卡片', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: '提现ID', }, feed: '通道', withdrawalType: { diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 5c1bb46dd5ef..d7c651300026 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -32,6 +32,7 @@ import type { TransactionListItemType, TransactionMemberGroupListItemType, TransactionReportGroupListItemType, + TransactionWithdrawalIDGroupListItemType, } from '@components/SelectionList/types'; import * as Expensicons from '@src/components/Icon/Expensicons'; import CONST from '@src/CONST'; @@ -637,6 +638,13 @@ function isTransactionCardGroupListItemType(item: ListItem): item is Transaction return isTransactionGroupListItemType(item) && 'groupedBy' in item && item.groupedBy === CONST.SEARCH.GROUP_BY.CARD; } +/** + * Type guard that checks if something is a TransactionWithdrawalIDGroupListItemType + */ +function isTransactionWithdrawalIDGroupListItemType(item: ListItem): item is TransactionWithdrawalIDGroupListItemType { + return isTransactionGroupListItemType(item) && 'groupedBy' in item && item.groupedBy === CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID; +} + /** * Type guard that checks if something is a TransactionListItemType */ @@ -1427,6 +1435,17 @@ function getCardSections(data: OnyxTypes.SearchResults['data']): TransactionCard return Object.values(cardSections); } +/** + * @private + * Organizes data into List Sections grouped by card for display, for the TransactionWithdrawalIDGroupListItemType of Search Results. + * + * Do not use directly, use only via `getSections()` facade. + */ +function getWithdrawalIDSections(data: OnyxTypes.SearchResults['data']): TransactionWithdrawalIDGroupListItemType[] { + // Will be implemented as part of https://github.com/Expensify/App/pull/66078 + return data ? [] : []; +} + /** * Returns the appropriate list item component based on the type and status of the search data. */ @@ -1474,6 +1493,8 @@ function getSections( return getMemberSections(data); case CONST.SEARCH.GROUP_BY.CARD: return getCardSections(data); + case CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID: + return getWithdrawalIDSections(data); } } @@ -1509,6 +1530,8 @@ function getSortedSections( return getSortedMemberData(data as TransactionMemberGroupListItemType[], localeCompare); case CONST.SEARCH.GROUP_BY.CARD: return getSortedCardData(data as TransactionCardGroupListItemType[], localeCompare); + case CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID: + return getSortedWithdrawalIDData(data as TransactionWithdrawalIDGroupListItemType[]); } } @@ -1617,6 +1640,15 @@ function getSortedCardData(data: TransactionCardGroupListItemType[], localeCompa return data.sort((a, b) => localeCompare(a.displayName ?? a.login ?? '', b.displayName ?? b.login ?? '')); } +/** + * @private + * Sorts report sections based on a specified column and sort order. + */ +function getSortedWithdrawalIDData(data: TransactionWithdrawalIDGroupListItemType[]) { + // Will be implemented as part of https://github.com/Expensify/App/pull/66078 + return data ? [] : []; +} + /** * @private * Sorts report actions sections based on a specified column and sort order. @@ -1998,6 +2030,7 @@ export { isTransactionReportGroupListItemType, isTransactionMemberGroupListItemType, isTransactionCardGroupListItemType, + isTransactionWithdrawalIDGroupListItemType, isSearchResultsEmpty, isTransactionListItemType, isReportActionListItemType, diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index e4aac1e3534e..8535dfaf722a 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -503,6 +503,30 @@ type SearchCardGroup = { lastFourPAN: string; }; +/** Model of withdrawal ID grouped search result */ +type SearchWithdrawalIDGroup = { + /** Withdrawal ID */ + entryID: number; + + /** Number of transactions */ + count: number; + + /** Total value of transactions */ + total: number; + + /** Currency of total value */ + currency: string; + + /** Masked account number */ + accountNumber: string; + + /** Bank name */ + addressName: string; + + /** When the withdrawal completed */ + debitPosted: string; +}; + /** Types of searchable transactions */ type SearchTransactionType = ValueOf; @@ -526,7 +550,7 @@ type SearchResults = { PrefixedRecord & PrefixedRecord & PrefixedRecord & - PrefixedRecord; + PrefixedRecord; /** Whether search data is being fetched from server */ isLoading?: boolean; @@ -552,4 +576,5 @@ export type { SearchResultsInfo, SearchMemberGroup, SearchCardGroup, + SearchWithdrawalIDGroup, };