Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 12 additions & 17 deletions src/libs/TransactionUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
};

let deprecatedAllReports: OnyxCollection<Report> = {};
Onyx.connect({

Check warning on line 125 in src/libs/TransactionUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -131,7 +131,7 @@
});

let deprecatedAllTransactionViolations: OnyxCollection<TransactionViolations> = {};
Onyx.connect({

Check warning on line 134 in src/libs/TransactionUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
waitForCollectionCallback: true,
callback: (value) => (deprecatedAllTransactionViolations = value),
Expand Down Expand Up @@ -2204,22 +2204,17 @@
/**
* Creates sections data for unreported expenses, marking transactions with DELETE pending action as disabled
*/
function createUnreportedExpenseSections(transactions: Array<Transaction | undefined>): Array<{shouldShow: boolean; data: UnreportedExpenseListItemType[]}> {
return [
{
shouldShow: true,
data: transactions
.filter((t): t is Transaction => t !== undefined)
.map(
(transaction): UnreportedExpenseListItemType => ({
...transaction,
isDisabled: isTransactionPendingDelete(transaction),
keyForList: transaction.transactionID,
errors: transaction.errors as Errors | undefined,
}),
),
},
];
function createUnreportedExpenses(transactions: Array<Transaction | undefined>): UnreportedExpenseListItemType[] {
return transactions
.filter((t): t is Transaction => t !== undefined)
.map(
(transaction): UnreportedExpenseListItemType => ({
...transaction,
isDisabled: isTransactionPendingDelete(transaction),
keyForList: transaction.transactionID,
errors: transaction.errors as Errors | undefined,
}),
);
}

function isExpenseUnreported(transaction?: Transaction): transaction is UnreportedTransaction {
Expand Down Expand Up @@ -2331,7 +2326,7 @@
getTransactionPendingAction,
isTransactionPendingDelete,
getChildTransactions,
createUnreportedExpenseSections,
createUnreportedExpenses,
isDemoTransaction,
shouldShowViolation,
isUnreportedAndHasInvalidDistanceRateTransaction,
Expand Down
70 changes: 43 additions & 27 deletions src/pages/AddUnreportedExpense.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import LottieAnimations from '@components/LottieAnimations';
import {useSession} from '@components/OnyxListItemProvider';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionListWithSections';
import type {ListItem, SectionListDataType, SelectionListHandle} from '@components/SelectionListWithSections/types';
import SelectionList from '@components/SelectionList';
import type {ListItem, SelectionListHandle} from '@components/SelectionList/types';
import UnreportedExpensesSkeleton from '@components/Skeletons/UnreportedExpensesSkeleton';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -27,7 +27,7 @@ import {canSubmitPerDiemExpenseFromWorkspace, getPerDiemCustomUnit} from '@libs/
import {getTransactionDetails, isIOUReport} from '@libs/ReportUtils';
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
import tokenizedSearch from '@libs/tokenizedSearch';
import {createUnreportedExpenseSections, getAmount, getCurrency, getDescription, getMerchant, isPerDiemRequest} from '@libs/TransactionUtils';
import {createUnreportedExpenses, getAmount, getCurrency, getDescription, getMerchant, isPerDiemRequest} from '@libs/TransactionUtils';
import Navigation from '@navigation/Navigation';
import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types';
import {convertBulkTrackedExpensesToIOU, startMoneyRequest} from '@userActions/IOU';
Expand Down Expand Up @@ -153,7 +153,12 @@ function AddUnreportedExpense({route}: AddUnreportedExpensePageType) {
});
}, [debouncedSearchValue, shouldShowTextInput, transactions]);

const sections: Array<SectionListDataType<Transaction & ListItem>> = useMemo(() => createUnreportedExpenseSections(filteredTransactions), [filteredTransactions]);
const unreportedExpenses = useMemo(() => {
return createUnreportedExpenses(filteredTransactions).map((item) => ({
...item,
isSelected: selectedIds.has(item.transactionID),
Comment thread
OlGierd03 marked this conversation as resolved.
}));
}, [filteredTransactions, selectedIds]);

const handleConfirm = useCallback(() => {
if (selectedIds.size === 0) {
Expand Down Expand Up @@ -212,11 +217,39 @@ function AddUnreportedExpense({route}: AddUnreportedExpensePageType) {
}, [errorMessage, styles, translate, handleConfirm]);

const headerMessage = useMemo(() => {
if (debouncedSearchValue.trim() && sections.at(0)?.data.length === 0) {
if (debouncedSearchValue.trim() && unreportedExpenses?.length === 0) {
return translate('common.noResultsFound');
}
return '';
}, [debouncedSearchValue, sections, translate]);
}, [debouncedSearchValue, unreportedExpenses?.length, translate]);

const textInputOptions = useMemo(
() => ({
value: searchValue,
label: shouldShowTextInput ? translate('iou.findExpense') : undefined,
onChangeText: setSearchValue,
headerMessage,
}),
[searchValue, shouldShowTextInput, translate, setSearchValue, headerMessage],
);

const onSelectRow = useCallback(
(item: {transactionID: string}) => {
setSelectedIds((prevIds) => {
const newIds = new Set(prevIds);
if (newIds.has(item.transactionID)) {
newIds.delete(item.transactionID);
} else {
newIds.add(item.transactionID);
if (errorMessage) {
setErrorMessage('');
}
}
return newIds;
});
},
[errorMessage],
);

const hasSearchTerm = debouncedSearchValue.trim().length > 0;
const isShowingEmptyState = !hasSearchTerm && transactions.length === 0;
Expand Down Expand Up @@ -298,36 +331,19 @@ function AddUnreportedExpense({route}: AddUnreportedExpensePageType) {
onBackButtonPress={Navigation.goBack}
/>
<SelectionList<Transaction & ListItem>
data={unreportedExpenses}
ref={selectionListRef}
onSelectRow={(item) => {
setSelectedIds((prevIds) => {
const newIds = new Set(prevIds);
if (newIds.has(item.transactionID)) {
newIds.delete(item.transactionID);
} else {
newIds.add(item.transactionID);
if (errorMessage) {
setErrorMessage('');
}
}

return newIds;
});
}}
isSelected={(item) => selectedIds.has(item.transactionID)}
onSelectRow={onSelectRow}
textInputOptions={textInputOptions}
shouldShowTextInput={shouldShowTextInput}
textInputValue={searchValue}
textInputLabel={shouldShowTextInput ? translate('iou.findExpense') : undefined}
onChangeText={setSearchValue}
headerMessage={headerMessage}
canSelectMultiple
sections={sections}
ListItem={UnreportedExpenseListItem}
onEndReached={fetchMoreUnreportedTransactions}
onEndReachedThreshold={0.75}
addBottomSafeAreaPadding
listFooterContent={shouldShowUnreportedTransactionsSkeletons ? <UnreportedExpensesSkeleton fixedNumberOfItems={3} /> : undefined}
footerContent={footerContent}
disableMaintainingScrollPosition
/>
</ScreenWrapper>
);
Expand Down
41 changes: 26 additions & 15 deletions tests/unit/AddUnreportedExpenseTest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {createUnreportedExpenseSections} from '@libs/TransactionUtils';
import {createUnreportedExpenses} from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import type Transaction from '@src/types/onyx/Transaction';

Expand All @@ -24,7 +24,7 @@ function generateTransaction(values: Partial<Transaction> = {}): Transaction {
}

describe('AddUnreportedExpense', () => {
describe('createUnreportedExpenseSections', () => {
describe('createUnreportedExpenses', () => {
it('should mark transactions with DELETE pendingAction as disabled', () => {
const normalTransaction = generateTransaction({
transactionID: '123',
Expand All @@ -41,18 +41,17 @@ describe('AddUnreportedExpense', () => {
});

const transactions = [normalTransaction, deletedTransaction];
const sections = createUnreportedExpenseSections(transactions);
const unreportedExpenses = createUnreportedExpenses(transactions);

// Should create one section
expect(sections).toHaveLength(1);
expect(sections.at(0)?.shouldShow).toBe(true);
expect(sections.at(0)?.data).toHaveLength(2);
expect(unreportedExpenses).toHaveLength(2);

const processedNormalTransaction = sections.at(0)?.data.find((t) => t.transactionID === '123');
const processedNormalTransaction = unreportedExpenses.find((t) => t.transactionID === '123');
expect(processedNormalTransaction?.isDisabled).toBe(false);
expect(processedNormalTransaction?.keyForList).toBe('123');

const processedDeletedTransaction = sections.at(0)?.data.find((t) => t.transactionID === '456');
const processedDeletedTransaction = unreportedExpenses.find((t) => t.transactionID === '456');
expect(processedDeletedTransaction?.isDisabled).toBe(true);
expect(processedDeletedTransaction?.keyForList).toBe('456');
});

it('should not mark transactions without DELETE pendingAction as disabled', () => {
Expand All @@ -78,10 +77,10 @@ describe('AddUnreportedExpense', () => {
});

const transactions = [normalTransaction, updateTransaction, addTransaction];
const sections = createUnreportedExpenseSections(transactions);
const unreportedExpenses = createUnreportedExpenses(transactions);

expect(sections.at(0)?.data).toHaveLength(3);
for (const transaction of sections.at(0)?.data ?? []) {
expect(unreportedExpenses).toHaveLength(3);
for (const transaction of unreportedExpenses ?? []) {
expect(transaction.isDisabled).toBe(false);
}
});
Expand All @@ -102,13 +101,25 @@ describe('AddUnreportedExpense', () => {
});

const transactions = [deletedTransaction1, deletedTransaction2];
const sections = createUnreportedExpenseSections(transactions);
const unreportedExpenses = createUnreportedExpenses(transactions);

expect(sections.at(0)?.data).toHaveLength(2);
for (const transaction of sections.at(0)?.data ?? []) {
expect(unreportedExpenses).toHaveLength(2);
for (const transaction of unreportedExpenses ?? []) {
expect(transaction.isDisabled).toBe(true);
expect(transaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
}
});

it('should filter out undefined transactions', () => {
const normalTransaction = generateTransaction({
transactionID: '123',
});

const transactions = [normalTransaction, undefined];
const unreportedExpenses = createUnreportedExpenses(transactions);

expect(unreportedExpenses).toHaveLength(1);
expect(unreportedExpenses.at(0)?.transactionID).toBe('123');
});
});
});
Loading