Skip to content

Commit 583bda5

Browse files
authored
Merge pull request Expensify#73855 from ZhenjaHorbach/add-split-to-select-search-menu
Add Split to the select menu when 1 expense is selected, editable and not the child of an existing Split expense
2 parents d6c2eee + 962f3d3 commit 583bda5

15 files changed

Lines changed: 1275 additions & 62 deletions

cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,8 @@
794794
"setuptools",
795795
"DYNAMICEXTERNAL",
796796
"RNCORE",
797-
"Wooo"
797+
"Wooo",
798+
"Splittable"
798799
],
799800
"ignorePaths": [
800801
"src/languages/de.ts",

src/CONST/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6656,6 +6656,7 @@ const CONST = {
66566656
DELETE: 'delete',
66576657
REJECT: 'reject',
66586658
CHANGE_REPORT: 'changeReport',
6659+
SPLIT: 'split',
66596660
},
66606661
TRANSACTION_TYPE: {
66616662
CASH: 'cash',

src/components/Search/index.tsx

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as Sentry from '@sentry/react-native';
33
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
44
import type {NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native';
55
import {View} from 'react-native';
6+
import type {OnyxEntry} from 'react-native-onyx';
67
import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
78
import FullPageErrorView from '@components/BlockingViews/FullPageErrorView';
89
import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
@@ -32,6 +33,7 @@ import Log from '@libs/Log';
3233
import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute';
3334
import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types';
3435
import Performance from '@libs/Performance';
36+
import {isSplitAction} from '@libs/ReportSecondaryActionUtils';
3537
import {canAddOrDeleteTransactions, canEditFieldOfMoneyRequest, canHoldUnholdReportAction, isOneTransactionReport, selectFilteredReportActions} from '@libs/ReportUtils';
3638
import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
3739
import {
@@ -55,7 +57,7 @@ import {
5557
shouldShowYear as shouldShowYearUtil,
5658
} from '@libs/SearchUIUtils';
5759
import {cancelSpan, endSpan, startSpan} from '@libs/telemetry/activeSpans';
58-
import {isOnHold, isTransactionPendingDelete, mergeProhibitedViolations, shouldShowViolation} from '@libs/TransactionUtils';
60+
import {getOriginalTransactionWithSplitInfo, isOnHold, isTransactionPendingDelete, mergeProhibitedViolations, shouldShowViolation} from '@libs/TransactionUtils';
5961
import Navigation, {navigationRef} from '@navigation/Navigation';
6062
import type {SearchFullscreenNavigatorParamList} from '@navigation/types';
6163
import EmptySearchView from '@pages/Search/EmptySearchView';
@@ -65,7 +67,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
6567
import ROUTES from '@src/ROUTES';
6668
import SCREENS from '@src/SCREENS';
6769
import {isActionLoadingSetSelector} from '@src/selectors/ReportMetaData';
68-
import type {OutstandingReportsByPolicyIDDerivedValue} from '@src/types/onyx';
70+
import type {OutstandingReportsByPolicyIDDerivedValue, Transaction} from '@src/types/onyx';
6971
import type SearchResults from '@src/types/onyx/SearchResults';
7072
import type {SearchTransaction} from '@src/types/onyx/SearchResults';
7173
import type {TransactionViolation} from '@src/types/onyx/TransactionViolation';
@@ -90,9 +92,13 @@ type SearchProps = {
9092

9193
const expenseHeaders = getExpenseHeaders();
9294

93-
function mapTransactionItemToSelectedEntry(item: TransactionListItemType, outstandingReportsByPolicyID?: OutstandingReportsByPolicyIDDerivedValue): [string, SelectedTransactionInfo] {
95+
function mapTransactionItemToSelectedEntry(
96+
item: TransactionListItemType,
97+
itemTransaction: OnyxEntry<Transaction>,
98+
originalItemTransaction: OnyxEntry<Transaction>,
99+
outstandingReportsByPolicyID?: OutstandingReportsByPolicyIDDerivedValue,
100+
): [string, SelectedTransactionInfo] {
94101
const {canHoldRequest, canUnholdRequest} = canHoldUnholdReportAction(item.report, item.reportAction, item.holdReportAction, item, item.policy);
95-
96102
return [
97103
item.keyForList,
98104
{
@@ -101,6 +107,8 @@ function mapTransactionItemToSelectedEntry(item: TransactionListItemType, outsta
101107
canHold: canHoldRequest,
102108
isHeld: isOnHold(item),
103109
canUnhold: canUnholdRequest,
110+
canSplit: isSplitAction(item.report, [itemTransaction], originalItemTransaction, item.policy),
111+
hasBeenSplit: getOriginalTransactionWithSplitInfo(itemTransaction, originalItemTransaction).isExpenseSplit,
104112
canChangeReport: canEditFieldOfMoneyRequest(
105113
item.reportAction,
106114
CONST.EDIT_REQUEST_FIELD.REPORT,
@@ -166,7 +174,13 @@ function mapToItemWithAdditionalInfo(item: SearchListItem, selectedTransactions:
166174
};
167175
}
168176

169-
function prepareTransactionsList(item: TransactionListItemType, selectedTransactions: SelectedTransactions, outstandingReportsByPolicyID?: OutstandingReportsByPolicyIDDerivedValue) {
177+
function prepareTransactionsList(
178+
item: TransactionListItemType,
179+
itemTransaction: OnyxEntry<Transaction>,
180+
originalItemTransaction: OnyxEntry<Transaction>,
181+
selectedTransactions: SelectedTransactions,
182+
outstandingReportsByPolicyID?: OutstandingReportsByPolicyIDDerivedValue,
183+
) {
170184
if (selectedTransactions[item.keyForList]?.isSelected) {
171185
const {[item.keyForList]: omittedTransaction, ...transactions} = selectedTransactions;
172186

@@ -183,6 +197,8 @@ function prepareTransactionsList(item: TransactionListItemType, selectedTransact
183197
canHold: canHoldRequest,
184198
isHeld: isOnHold(item),
185199
canUnhold: canUnholdRequest,
200+
canSplit: isSplitAction(item.report, [itemTransaction], originalItemTransaction, item.policy),
201+
hasBeenSplit: getOriginalTransactionWithSplitInfo(itemTransaction, originalItemTransaction).isExpenseSplit,
186202
canChangeReport: canEditFieldOfMoneyRequest(
187203
item.reportAction,
188204
CONST.EDIT_REQUEST_FIELD.REPORT,
@@ -497,11 +513,16 @@ function Search({
497513
transactionItem.policy,
498514
);
499515

516+
const itemTransaction = searchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionItem.transactionID}`] as OnyxEntry<Transaction>;
517+
const originalItemTransaction = searchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${itemTransaction?.comment?.originalTransactionID}`];
518+
500519
newTransactionList[transactionItem.transactionID] = {
501520
action: transactionItem.action,
502521
canHold: canHoldRequest,
503522
isHeld: isOnHold(transactionItem),
504523
canUnhold: canUnholdRequest,
524+
canSplit: isSplitAction(transactionItem.report, [itemTransaction], originalItemTransaction, transactionItem.policy),
525+
hasBeenSplit: getOriginalTransactionWithSplitInfo(itemTransaction, originalItemTransaction).isExpenseSplit,
505526
canChangeReport: canEditFieldOfMoneyRequest(
506527
transactionItem.reportAction,
507528
CONST.EDIT_REQUEST_FIELD.REPORT,
@@ -543,11 +564,16 @@ function Search({
543564
transactionItem.policy,
544565
);
545566

567+
const itemTransaction = searchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionItem.transactionID}`] as OnyxEntry<Transaction>;
568+
const originalItemTransaction = searchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${itemTransaction?.comment?.originalTransactionID}`];
569+
546570
newTransactionList[transactionItem.transactionID] = {
547571
action: transactionItem.action,
548572
canHold: canHoldRequest,
549573
isHeld: isOnHold(transactionItem),
550574
canUnhold: canUnholdRequest,
575+
canSplit: isSplitAction(transactionItem.report, [itemTransaction], originalItemTransaction, transactionItem.policy),
576+
hasBeenSplit: getOriginalTransactionWithSplitInfo(itemTransaction, originalItemTransaction).isExpenseSplit,
551577
canChangeReport: canEditFieldOfMoneyRequest(
552578
transactionItem.reportAction,
553579
CONST.EDIT_REQUEST_FIELD.REPORT,
@@ -640,7 +666,9 @@ function Search({
640666
if (isTransactionPendingDelete(item)) {
641667
return;
642668
}
643-
setSelectedTransactions(prepareTransactionsList(item, selectedTransactions, outstandingReportsByPolicyID), filteredData);
669+
const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${item.transactionID}`] as OnyxEntry<Transaction>;
670+
const originalItemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${itemTransaction?.comment?.originalTransactionID}`];
671+
setSelectedTransactions(prepareTransactionsList(item, itemTransaction, originalItemTransaction, selectedTransactions, outstandingReportsByPolicyID), filteredData);
644672
return;
645673
}
646674

@@ -662,13 +690,17 @@ function Search({
662690
...Object.fromEntries(
663691
currentTransactions
664692
.filter((t) => !isTransactionPendingDelete(t))
665-
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, outstandingReportsByPolicyID)),
693+
.map((transactionItem) => {
694+
const itemTransaction = searchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionItem.transactionID}`] as OnyxEntry<Transaction>;
695+
const originalItemTransaction = searchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${itemTransaction?.comment?.originalTransactionID}`];
696+
return mapTransactionItemToSelectedEntry(transactionItem, itemTransaction, originalItemTransaction, outstandingReportsByPolicyID);
697+
}),
666698
),
667699
},
668700
filteredData,
669701
);
670702
},
671-
[filteredData, selectedTransactions, outstandingReportsByPolicyID, setSelectedTransactions],
703+
[setSelectedTransactions, selectedTransactions, filteredData, transactions, outstandingReportsByPolicyID, searchResults?.data],
672704
);
673705

674706
const onSelectRow = useCallback(
@@ -882,7 +914,11 @@ function Search({
882914
(filteredData as TransactionGroupListItemType[]).flatMap((item) =>
883915
item.transactions
884916
.filter((t) => !isTransactionPendingDelete(t))
885-
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, outstandingReportsByPolicyID)),
917+
.map((transactionItem) => {
918+
const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionItem.transactionID}`] as OnyxEntry<Transaction>;
919+
const originalItemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${itemTransaction?.comment?.originalTransactionID}`];
920+
return mapTransactionItemToSelectedEntry(transactionItem, itemTransaction, originalItemTransaction, outstandingReportsByPolicyID);
921+
}),
886922
),
887923
),
888924
filteredData,
@@ -895,11 +931,25 @@ function Search({
895931
Object.fromEntries(
896932
(filteredData as TransactionListItemType[])
897933
.filter((t) => !isTransactionPendingDelete(t))
898-
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, outstandingReportsByPolicyID)),
934+
.map((transactionItem) => {
935+
const itemTransaction = searchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionItem.transactionID}`] as OnyxEntry<Transaction>;
936+
const originalItemTransaction = searchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${itemTransaction?.comment?.originalTransactionID}`];
937+
return mapTransactionItemToSelectedEntry(transactionItem, itemTransaction, originalItemTransaction, outstandingReportsByPolicyID);
938+
}),
899939
),
900940
filteredData,
901941
);
902-
}, [clearSelectedTransactions, filteredData, validGroupBy, selectedTransactions, setSelectedTransactions, outstandingReportsByPolicyID, isExpenseReportType]);
942+
}, [
943+
validGroupBy,
944+
isExpenseReportType,
945+
filteredData,
946+
selectedTransactions,
947+
setSelectedTransactions,
948+
clearSelectedTransactions,
949+
transactions,
950+
outstandingReportsByPolicyID,
951+
searchResults?.data,
952+
]);
903953

904954
const onLayout = useCallback(() => {
905955
endSpan(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS_TAB);

src/components/Search/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ type SelectedTransactionInfo = {
1717
/** If the transaction can be put on hold */
1818
canHold: boolean;
1919

20+
/** If the transaction can be splitted */
21+
canSplit: boolean;
22+
23+
/** If the transaction has been splitted */
24+
hasBeenSplit: boolean;
25+
2026
/** If the transaction can be moved to other report */
2127
canChangeReport: boolean;
2228

src/hooks/useAllTransactions.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {useMemo} from 'react';
2+
import type {OnyxEntry} from 'react-native-onyx';
3+
import {useSearchContext} from '@components/Search/SearchContext';
4+
import CONST from '@src/CONST';
5+
import ONYXKEYS from '@src/ONYXKEYS';
6+
import type {Transaction} from '@src/types/onyx';
7+
import useOnyx from './useOnyx';
8+
9+
/**
10+
* Hook that returns all transactions, filtered by current search results if a search data is available
11+
*/
12+
function useAllTransactions() {
13+
const searchContext = useSearchContext();
14+
const searchHash = searchContext?.currentSearchHash ?? CONST.DEFAULT_NUMBER_ID;
15+
const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, {canBeMissing: true});
16+
const [allTransactionsCollection] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false});
17+
18+
const allTransactions = useMemo(() => {
19+
const data = currentSearchResults?.data;
20+
if (!data) {
21+
return allTransactionsCollection;
22+
}
23+
24+
const filteredSearchTransactions = Object.keys(data)
25+
.filter((key): key is `${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}` => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION))
26+
.reduce(
27+
(acc, key) => {
28+
const value = data?.[key] as OnyxEntry<Transaction> | undefined;
29+
if (value) {
30+
acc[key] = value;
31+
}
32+
return acc;
33+
},
34+
{} as Record<string, OnyxEntry<Transaction>>,
35+
);
36+
37+
return {
38+
...filteredSearchTransactions,
39+
...allTransactionsCollection,
40+
};
41+
}, [currentSearchResults?.data, allTransactionsCollection]);
42+
43+
return allTransactions;
44+
}
45+
46+
export default useAllTransactions;

src/hooks/useDeleteTransactions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac
120120
originalTransactionID: transactionID,
121121
splitExpenses: childTransactions.map((childTransaction) => initSplitExpenseItemData(childTransaction)),
122122
},
123-
hash: currentSearchHash ?? 0,
123+
searchContext: {
124+
currentSearchHash: currentSearchHash ?? 0,
125+
},
124126
policyCategories,
125127
policy,
126128
policyRecentlyUsedCategories,

0 commit comments

Comments
 (0)