Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions assets/images/receipt-search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,7 @@ const ONYXKEYS = {
TODOS: 'todos',
RAM_ONLY_SORTED_REPORT_ACTIONS: 'sortedReportActions',
OPEN_AND_SUBMITTED_REPORTS_BY_POLICY_ID: 'openAndSubmittedReportsByPolicyID',
FLAGGED_EXPENSES: 'flaggedExpenses',
},

/** Stores HybridApp specific state required to interoperate with OldDot */
Expand Down Expand Up @@ -1707,6 +1708,7 @@ type OnyxDerivedValuesMapping = {
[ONYXKEYS.DERIVED.TODOS]: OnyxTypes.TodosDerivedValue;
[ONYXKEYS.DERIVED.RAM_ONLY_SORTED_REPORT_ACTIONS]: OnyxTypes.SortedReportActionsDerivedValue;
[ONYXKEYS.DERIVED.OPEN_AND_SUBMITTED_REPORTS_BY_POLICY_ID]: OnyxTypes.OpenAndSubmittedReportsByPolicyIDDerivedValue;
[ONYXKEYS.DERIVED.FLAGGED_EXPENSES]: OnyxTypes.FlaggedExpensesDerivedValue;
};

type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping & OnyxDerivedValuesMapping;
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/chunks/expensify-icons.chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ import ReceiptMultiple from '@assets/images/receipt-multiple.svg';
import ReceiptPlaceholderPlus from '@assets/images/receipt-placeholder-plus.svg';
import ReceiptPlus from '@assets/images/receipt-plus.svg';
import ReceiptScan from '@assets/images/receipt-scan.svg';
import ReceiptSearch from '@assets/images/receipt-search.svg';
import ReceiptSlash from '@assets/images/receipt-slash.svg';
import Receipt from '@assets/images/receipt.svg';
import RemoveMembers from '@assets/images/remove-members.svg';
Expand Down Expand Up @@ -451,6 +452,7 @@ const Expensicons = {
ReceiptPlaceholderPlus,
ReceiptPlus,
ReceiptScan,
ReceiptSearch,
ReceiptSlash,
RemoveMembers,
ReportCopy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ import SelectionList from '@components/SelectionList';
import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem';
import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton';
import Text from '@components/Text';
import {useWideRHPActions} from '@components/WideRHPContextProvider';
import useCopySelectionHelper from '@hooks/useCopySelectionHelper';
import {useCurrencyListActions} from '@hooks/useCurrencyList';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useHandleSelectionMode from '@hooks/useHandleSelectionMode';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
import useNavigateToTransactionThread from '@hooks/useNavigateToTransactionThread';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
Expand All @@ -38,14 +38,13 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import {setOptimisticTransactionThread} from '@libs/actions/Report';
import {getReportLayoutGroupBy, getReportLayoutSelection, setReportLayout} from '@libs/actions/ReportLayout';
import {clearActiveTransactionIDs, setActiveTransactionIDs} from '@libs/actions/TransactionThreadNavigation';
import {resolveTransactionCardFields} from '@libs/CardUtils';
import {hasNonReimbursableTransactions, isBillableEnabledOnPolicy} from '@libs/MoneyRequestReportUtils';
import {navigationRef} from '@libs/Navigation/Navigation';
import {isPolicyTaxEnabled} from '@libs/PolicyUtils';
import {getIOUActionForTransactionID, getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {groupTransactionsByCategory, groupTransactionsByTag} from '@libs/ReportLayoutUtils';
import {
canAddTransaction,
Expand All @@ -68,9 +67,7 @@ import {getTransactionPendingAction, getVisibleTransactionViolations, isTransact
import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear';
import isReportOpenInSuperWideRHP from '@navigation/helpers/isReportOpenInSuperWideRHP';
import Navigation from '@navigation/Navigation';
import type {ReportsSplitNavigatorParamList} from '@navigation/types';
import variables from '@styles/variables';
import {createTransactionThreadReport} from '@userActions/Report';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import NAVIGATORS from '@src/NAVIGATORS';
Expand Down Expand Up @@ -151,8 +148,6 @@ type TransactionWithOptionalHighlight = OnyxTypes.Transaction & {
shouldBeHighlighted?: boolean;
};

type ReportScreenNavigationProps = ReportsSplitNavigatorParamList[typeof SCREENS.REPORT];

type SortedTransactions = {
sortBy: SortableColumnName;
sortOrder: SortOrder;
Expand Down Expand Up @@ -180,7 +175,7 @@ function MoneyRequestReportTransactionList({
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth, isMediumScreenWidth, isInLandscapeMode} = useResponsiveLayout();
const {shouldUseNarrowLayout} = useResponsiveLayoutOnWideRHP();
const {markReportIDAsExpense} = useWideRHPActions();
const navigateToTransactionThread = useNavigateToTransactionThread();
const [isModalVisible, setIsModalVisible] = useState(false);
const [selectedTransactionID, setSelectedTransactionID] = useState<string>('');
const {reportPendingAction} = getReportOfflinePendingActionAndErrors(report);
Expand All @@ -206,8 +201,6 @@ function MoneyRequestReportTransactionList({
const [reportLayoutOption] = useOnyx(ONYXKEYS.NVP_REPORT_LAYOUT_OPTION);
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
const [reportDetailsColumns] = useOnyx(ONYXKEYS.NVP_REPORT_DETAILS_COLUMNS);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [nonPersonalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST);
const [cardList] = useOnyx(ONYXKEYS.CARD_LIST);
const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
Expand Down Expand Up @@ -554,44 +547,15 @@ function MoneyRequestReportTransactionList({
*/
const navigateToTransaction = useCallback(
(activeTransactionID: string) => {
const iouAction = getIOUActionForTransactionID(reportActions, activeTransactionID);
const backTo = Navigation.getActiveRoute();
let reportIDToNavigate = iouAction?.childReportID;

const routeParams = {
reportID: reportIDToNavigate,
backTo,
} as ReportScreenNavigationProps;

if (!reportIDToNavigate) {
const transaction = sortedTransactions.find((t) => t.transactionID === activeTransactionID);
const transactionThreadReport = createTransactionThreadReport({
introSelected,
currentUserLogin: currentUserDetails.email ?? '',
currentUserAccountID: currentUserDetails.accountID,
betas,
iouReport: report,
iouReportAction: iouAction,
transaction,
});
if (transactionThreadReport) {
reportIDToNavigate = transactionThreadReport.reportID;
routeParams.reportID = reportIDToNavigate;
}
} else {
setOptimisticTransactionThread(reportIDToNavigate, report?.reportID, iouAction?.reportActionID, report?.policyID);
}

// Single transaction report will open in RHP, and we need to find every other report ID for the rest of transactions
// to display prev/next arrows in RHP for navigation - use visual order from grouped transactions
setActiveTransactionIDs(visualOrderTransactionIDs).then(() => {
if (reportIDToNavigate) {
markReportIDAsExpense(reportIDToNavigate);
}
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(routeParams));
navigateToTransactionThread({
transactionID: activeTransactionID,
reportActions,
report,
transaction: sortedTransactions.find((t) => t.transactionID === activeTransactionID),
siblingTransactionIDs: visualOrderTransactionIDs,
});
},
[reportActions, visualOrderTransactionIDs, sortedTransactions, report, markReportIDAsExpense, introSelected, betas, currentUserDetails.email, currentUserDetails.accountID],
[navigateToTransactionThread, reportActions, sortedTransactions, report, visualOrderTransactionIDs],
);

const {amountColumnSize, dateColumnSize, taxAmountColumnSize} = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR
};
}, [nextTransactionID, parentReportActions, prevTransactionID, transactionIDsList]);

const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${currentTransaction?.reportID}`);
const [prevParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${prevTransaction?.reportID}`);
const [nextParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${nextTransaction?.reportID}`);
const [prevThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${prevParentReportAction?.childReportID}`);
const [nextThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${nextParentReportAction?.childReportID}`);

Expand Down Expand Up @@ -132,7 +133,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR
}
// We know that the next thread report exists, it just wasn't fetched to Onyx yet, so we set it optimistically.
if (!nextThreadReport && nextThreadReportID) {
setOptimisticTransactionThread(nextThreadReportID, parentReport?.reportID, nextParentReportAction?.reportActionID, parentReport?.policyID);
setOptimisticTransactionThread(nextThreadReportID, nextParentReport?.reportID, nextParentReportAction?.reportActionID, nextParentReport?.policyID);
}
// The transaction thread doesn't exist yet, so we should create it
if (!nextThreadReportID) {
Expand All @@ -141,7 +142,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR
currentUserLogin: currentUserEmail ?? '',
currentUserAccountID,
betas,
iouReport: parentReport,
iouReport: nextParentReport,
iouReportAction: nextParentReportAction,
transaction: nextTransaction,
});
Expand All @@ -168,7 +169,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR
}
// We know that the previous thread report exists, it just wasn't fetched to Onyx yet, so we set it optimistically.
if (!prevThreadReport && prevThreadReportID) {
setOptimisticTransactionThread(prevThreadReportID, parentReport?.reportID, prevParentReportAction?.reportActionID, parentReport?.policyID);
setOptimisticTransactionThread(prevThreadReportID, prevParentReport?.reportID, prevParentReportAction?.reportActionID, prevParentReport?.policyID);
}
// The transaction thread doesn't exist yet, so we should create it
if (!prevThreadReportID) {
Expand All @@ -177,7 +178,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR
currentUserLogin: currentUserEmail ?? '',
currentUserAccountID,
betas,
iouReport: parentReport,
iouReport: prevParentReport,
iouReportAction: prevParentReportAction,
transaction: prevTransaction,
});
Expand Down
87 changes: 87 additions & 0 deletions src/hooks/useNavigateToTransactionThread.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type {OnyxEntry} from 'react-native-onyx';
import {useWideRHPActions} from '@components/WideRHPContextProvider';
import {createTransactionThreadReport, setOptimisticTransactionThread} from '@libs/actions/Report';
import {setActiveTransactionIDs} from '@libs/actions/TransactionThreadNavigation';
import Navigation from '@libs/Navigation/Navigation';
import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Report, ReportAction, Transaction} from '@src/types/onyx';
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useOnyx from './useOnyx';

type NavigateToTransactionThreadParams = {
/** The transaction whose thread should be opened */
transactionID: string;

/** Report actions of the parent (IOU/expense) report, used to resolve the IOU action and its thread */
reportActions: ReportAction[];

/** The parent (IOU/expense) report that owns the transaction */
report: OnyxEntry<Report>;

/** The transaction being opened, used to build the thread optimistically when it doesn't exist yet */
transaction: OnyxEntry<Transaction>;

/** Ordered list of sibling transaction IDs used to drive the prev/next carousel in the thread RHP */
siblingTransactionIDs: string[];

/** Route to return to when navigating back; defaults to the current active route */
backTo?: string;
};

/**
* Shared navigation algorithm for opening a transaction thread (single-expense RHP view with the
* prev/next carousel). It resolves the IOU action's `childReportID`, creates the thread optimistically
* when it doesn't exist yet, seeds the sibling transaction IDs for the carousel, and navigates to the
* SEARCH_REPORT route.
*
* Callers are responsible for gathering their own `reportActions`, `report`, `transaction`, and
* `siblingTransactionIDs` because the data sources differ per screen.
*/
function useNavigateToTransactionThread() {
const {markReportIDAsExpense} = useWideRHPActions();
const currentUserDetails = useCurrentUserPersonalDetails();
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);

return ({transactionID, reportActions, report, transaction, siblingTransactionIDs, backTo}: NavigateToTransactionThreadParams) => {
const iouAction = getIOUActionForTransactionID(reportActions, transactionID);
const resolvedBackTo = backTo ?? Navigation.getActiveRoute();
let reportIDToNavigate = iouAction?.childReportID;

const routeParams: {reportID: string | undefined; reportActionID?: string; backTo?: string} = {
reportID: reportIDToNavigate,
backTo: resolvedBackTo,
};

if (!reportIDToNavigate) {
const transactionThreadReport = createTransactionThreadReport({
introSelected,
currentUserLogin: currentUserDetails.email ?? '',
currentUserAccountID: currentUserDetails.accountID,
betas,
iouReport: report,
iouReportAction: iouAction,
transaction,
});
if (transactionThreadReport) {
reportIDToNavigate = transactionThreadReport.reportID;
routeParams.reportID = reportIDToNavigate;
}
} else {
setOptimisticTransactionThread(reportIDToNavigate, report?.reportID, iouAction?.reportActionID, report?.policyID);
}

// Single transaction report opens in RHP. We seed every sibling transaction ID so the RHP can
// display prev/next arrows for navigation between expenses.
setActiveTransactionIDs(siblingTransactionIDs).then(() => {
if (reportIDToNavigate) {
markReportIDAsExpense(reportIDToNavigate);
}
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(routeParams));
});
};
}

export default useNavigateToTransactionThread;
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,7 @@ const translations: TranslationDeepObject<typeof en> = {
f1FlagsTitle: 'Alles erledigt',
f1FlagsDescription: 'Sie haben alle offenen Aufgaben abgeschlossen.',
},
reviewExpenses: ({count}: {count: number}) => `Überprüfen Sie ${count} ${count === 1 ? 'Ausgabe' : 'Spesen'}`,
},
upcomingTravel: 'Bevorstehende Reisen',
upcomingTravelSection: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@ const translations = {
menuItemDescription: 'See what Expensify can do in 2 min',
},
forYouSection: {
reviewExpenses: ({count}: {count: number}) => `Review ${count} ${count === 1 ? 'expense' : 'expenses'}`,
submit: ({count}: {count: number}) => `Submit ${count} ${count === 1 ? 'report' : 'reports'}`,
approve: ({count}: {count: number}) => `Approve ${count} ${count === 1 ? 'report' : 'reports'}`,
pay: ({count}: {count: number}) => `Pay ${count} ${count === 1 ? 'report' : 'reports'}`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ const translations: TranslationDeepObject<typeof en> = {
f1FlagsTitle: 'Todo al día',
f1FlagsDescription: 'Has completado todas las tareas pendientes.',
},
reviewExpenses: ({count}: {count: number}) => `Revisa ${count} ${count === 1 ? 'gasto' : 'gastos'}`,
},
gettingStartedSection: {
title: 'Primeros pasos',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,7 @@ const translations: TranslationDeepObject<typeof en> = {
f1FlagsTitle: 'Tout est à jour',
f1FlagsDescription: 'Vous avez terminé toutes les tâches en cours.',
},
reviewExpenses: ({count}: {count: number}) => `Examiner ${count} ${count === 1 ? 'dépense' : 'dépenses'}`,
},
upcomingTravel: 'Voyages à venir',
upcomingTravelSection: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,7 @@ const translations: TranslationDeepObject<typeof en> = {
f1FlagsTitle: 'Tutto a posto',
f1FlagsDescription: 'Hai completato tutte le attività in sospeso.',
},
reviewExpenses: ({count}: {count: number}) => `Esamina ${count} ${count === 1 ? 'spesa' : 'spese'}`,
},
upcomingTravel: 'Prossimi viaggi',
upcomingTravelSection: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,7 @@ const translations: TranslationDeepObject<typeof en> = {
f1FlagsTitle: 'すべて確認済みです',
f1FlagsDescription: 'すべての未処理の To-do が完了しました。',
},
reviewExpenses: ({count}: {count: number}) => `${count} 件の${count === 1 ? '経費' : '経費'}を確認`,
},
upcomingTravel: '今後の出張',
upcomingTravelSection: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@ const translations: TranslationDeepObject<typeof en> = {
f1FlagsTitle: 'Helemaal bij',
f1FlagsDescription: "Je hebt alle openstaande to-do's afgerond.",
},
reviewExpenses: ({count}: {count: number}) => `Beoordeel ${count} ${count === 1 ? 'uitgave' : 'uitgaven'}`,
},
upcomingTravel: 'Aankomende reizen',
upcomingTravelSection: {
Expand Down
Loading
Loading