diff --git a/src/components/DatePicker/CalendarPicker/YearPickerModal.tsx b/src/components/DatePicker/CalendarPicker/YearPickerModal.tsx index de3a1e3b725b..b2de19045a51 100644 --- a/src/components/DatePicker/CalendarPicker/YearPickerModal.tsx +++ b/src/components/DatePicker/CalendarPicker/YearPickerModal.tsx @@ -35,7 +35,7 @@ function YearPickerModal({isVisible, years, currentYear = new Date().getFullYear const yearsList = searchText === '' ? years : years.filter((year) => year.text?.includes(searchText)); return { headerMessage: !yearsList.length ? translate('common.noResultsFound') : '', - sections: [{data: yearsList.sort((a, b) => b.value - a.value), indexOffset: 0}], + sections: [{data: yearsList, indexOffset: 0}], }; }, [years, searchText, translate]); diff --git a/src/components/DatePicker/CalendarPicker/index.tsx b/src/components/DatePicker/CalendarPicker/index.tsx index 04da196cc0aa..c7923a0bafcd 100644 --- a/src/components/DatePicker/CalendarPicker/index.tsx +++ b/src/components/DatePicker/CalendarPicker/index.tsx @@ -77,7 +77,7 @@ function CalendarPicker({ const maxYear = getYear(new Date(maxDate)); const [years, setYears] = useState(() => - Array.from({length: maxYear - minYear + 1}, (v, i) => i + minYear).map((year) => ({ + Array.from({length: maxYear - minYear + 1}, (v, i) => maxYear - i).map((year) => ({ text: year.toString(), value: year, keyForList: year.toString(), diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index f778dde4d02e..89fbc49d8f16 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -20,6 +20,7 @@ import type {Form} from '@src/types/form'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; +import arrayLastElement from '@src/utils/arrayLastElement'; import KeyboardUtils from '@src/utils/keyboard'; import type {RegisterInput} from './FormContext'; import FormContext from './FormContext'; @@ -332,11 +333,8 @@ function FormProvider( } const errorFields = formState?.errorFields?.[inputID] ?? {}; - const fieldErrorMessage = - Object.keys(errorFields) - .sort() - .map((key) => errorFields[key]) - .at(-1) ?? ''; + const fieldErrorMessageKey = arrayLastElement(Object.keys(errorFields)); + const fieldErrorMessage = fieldErrorMessageKey ? (errorFields[fieldErrorMessageKey] ?? '') : ''; const inputRef = inputProps.ref; diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 4ae38762bf1f..0cf22ed1b170 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -23,7 +23,7 @@ import {isValidDraftComment} from '@libs/DraftCommentUtils'; import getPlatform from '@libs/getPlatform'; import Log from '@libs/Log'; import {getIOUReportIDOfLastAction, getLastMessageTextForReport} from '@libs/OptionsListUtils'; -import {getOneTransactionThreadReportID, getOriginalMessage, getSortedReportActionsForDisplay, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {getFirstSortedReportActionForDisplay, getOneTransactionThreadReportID, getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {canUserPerformWriteAction} from '@libs/ReportUtils'; import isProductTrainingElementDismissed from '@libs/TooltipUtils'; import variables from '@styles/variables'; @@ -192,8 +192,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const hasDraftComment = isValidDraftComment(draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]); const canUserPerformWrite = canUserPerformWriteAction(item); - const sortedReportActions = getSortedReportActionsForDisplay(itemReportActions, canUserPerformWrite); - const lastReportAction = sortedReportActions.at(0); + const lastReportAction = getFirstSortedReportActionForDisplay(itemReportActions, canUserPerformWrite); // Get the transaction for the last report action const lastReportActionTransactionID = isMoneyRequestAction(lastReportAction) diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index de3450435c20..7df9d1bb9926 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -20,6 +20,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; +import arrayFirstElement from '@src/utils/arrayFirstElement'; import createPressHandler from './createPressHandler'; import type {ProductTrainingTooltipName} from './TOOLTIPS'; import TOOLTIPS from './TOOLTIPS'; @@ -101,14 +102,13 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return null; } - const sortedTooltips = Array.from(activeTooltips) - .map((name) => ({ + const highestPriorityTooltip = arrayFirstElement( + Array.from(activeTooltips).map((name) => ({ name, priority: TOOLTIPS[name]?.priority ?? 0, - })) - .sort((a, b) => b.priority - a.priority); - - const highestPriorityTooltip = sortedTooltips.at(0); + })), + (a, b) => b.priority - a.priority, + ); if (!highestPriorityTooltip) { return null; diff --git a/src/hooks/useFastSearchFromOptions.ts b/src/hooks/useFastSearchFromOptions.ts index 670ac8f14e4c..bd207b4614b2 100644 --- a/src/hooks/useFastSearchFromOptions.ts +++ b/src/hooks/useFastSearchFromOptions.ts @@ -7,8 +7,8 @@ import type {Options as OptionsListType, ReportAndPersonalDetailOptions} from '@ import {filterUserToInvite, isSearchStringMatch} from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; import type {OptionData} from '@libs/ReportUtils'; -import StringUtils from '@libs/StringUtils'; import CONST from '@src/CONST'; +import arrayLastElement from '@src/utils/arrayLastElement'; type Options = { includeUserToInvite: boolean; @@ -122,8 +122,7 @@ function useFastSearchFromOptions( } const deburredInput = deburr(searchInput); const searchWords = deburredInput.split(/\s+/); - const searchWordsSorted = StringUtils.sortStringArrayByLength(searchWords); - const longestSearchWord = searchWordsSorted.at(searchWordsSorted.length - 1); // longest word is the last element + const longestSearchWord = arrayLastElement(searchWords, (a, b) => a.length - b.length); // longest word is the last element if (!longestSearchWord) { return emptyResult; } diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index b880239b8abf..8fc7b1d7c03b 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -5,8 +5,10 @@ import type {TranslationPaths} from '@src/languages/types'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; import type {ReceiptError} from '@src/types/onyx/Transaction'; +import arrayFirstElement from '@src/utils/arrayFirstElement'; +import arrayLastElement from '@src/utils/arrayLastElement'; import DateUtils from './DateUtils'; -import * as Localize from './Localize'; +import {translateLocal} from './Localize'; function getAuthenticateErrorMessage(response: Response): TranslationPaths { switch (response.jsonCode) { @@ -42,7 +44,7 @@ function getAuthenticateErrorMessage(response: Response): TranslationPaths { * @param error - The translation key for the error message. */ function getMicroSecondOnyxErrorWithTranslationKey(error: TranslationPaths, errorKey?: number): Errors { - return {[errorKey ?? DateUtils.getMicroseconds()]: Localize.translateLocal(error)}; + return {[errorKey ?? DateUtils.getMicroseconds()]: translateLocal(error)}; } /** @@ -77,7 +79,7 @@ function getLatestErrorMessage(onyxData: O return ''; } - const key = Object.keys(errors).sort().reverse().at(0) ?? ''; + const key = arrayLastElement(Object.keys(errors)) ?? ''; return getErrorMessageWithTranslationData(errors[key] ?? ''); } @@ -88,7 +90,7 @@ function getLatestErrorMessageField(onyxDa return {}; } - const key = Object.keys(errors).sort().reverse().at(0) ?? ''; + const key = arrayLastElement(Object.keys(errors)) ?? ''; return {key: errors[key]}; } @@ -104,7 +106,7 @@ function getLatestErrorField(onyxData return {}; } - const key = Object.keys(errorsForField).sort().reverse().at(0) ?? ''; + const key = arrayLastElement(Object.keys(errorsForField)) ?? ''; return {[key]: getErrorMessageWithTranslationData(errorsForField[key])}; } @@ -115,7 +117,7 @@ function getEarliestErrorField(onyxDa return {}; } - const key = Object.keys(errorsForField).sort().at(0) ?? ''; + const key = arrayFirstElement(Object.keys(errorsForField)) ?? ''; return {[key]: getErrorMessageWithTranslationData(errorsForField[key])}; } @@ -139,7 +141,7 @@ function getLatestError(errors?: Errors): Errors { return {}; } - const key = Object.keys(errors).sort().reverse().at(0) ?? ''; + const key = arrayLastElement(Object.keys(errors)) ?? ''; return {[key]: getErrorMessageWithTranslationData(errors[key])}; } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 60f0861bb33a..7af6e9732bce 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1,7 +1,6 @@ import {format} from 'date-fns'; import {fastMerge, Str} from 'expensify-common'; import clone from 'lodash/clone'; -import lodashFindLast from 'lodash/findLast'; import isEmpty from 'lodash/isEmpty'; import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; @@ -20,6 +19,8 @@ import type ReportAction from '@src/types/onyx/ReportAction'; import type {Message, OldDotReportAction, OriginalMessage, ReportActions} from '@src/types/onyx/ReportAction'; import type ReportActionName from '@src/types/onyx/ReportActionName'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import arrayFirstElement from '@src/utils/arrayFirstElement'; +import arrayLastElement from '@src/utils/arrayLastElement'; import {convertAmountToDisplayString, convertToDisplayString, convertToShortDisplayString} from './CurrencyUtils'; import DateUtils from './DateUtils'; import {getEnvironmentURL} from './Environment/Environment'; @@ -423,43 +424,49 @@ function isTransactionThread(parentReportAction: OnyxInputOrEntry) } /** - * Sort an array of reportActions by their created timestamp first, and reportActionID second + * Gives the comparator for sorting an array of reportActions by their created timestamp first, and reportActionID second * This gives us a stable order even in the case of multiple reportActions created on the same millisecond - * */ -function getSortedReportActions(reportActions: ReportAction[] | null, shouldSortInDescendingOrder = false): ReportAction[] { - if (!Array.isArray(reportActions)) { - throw new Error(`ReportActionsUtils.getSortedReportActions requires an array, received ${typeof reportActions}`); +function getSortedReportActionsComparator(first: ReportAction, second: ReportAction, shouldSortInDescendingOrder = false): number { + const invertedMultiplier = shouldSortInDescendingOrder ? -1 : 1; + + // First sort by action type, ensuring that `CREATED` actions always come first if they have the same or even a later timestamp as another action type + if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED || second.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) && first.actionName !== second.actionName) { + return (first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED ? -1 : 1) * invertedMultiplier; } - const invertedMultiplier = shouldSortInDescendingOrder ? -1 : 1; + // Ensure that neither first's nor second's created property is undefined + if (first.created === undefined || second.created === undefined) { + return (first.created === undefined ? -1 : 1) * invertedMultiplier; + } - const sortedActions = reportActions?.filter(Boolean).sort((first, second) => { - // First sort by action type, ensuring that `CREATED` actions always come first if they have the same or even a later timestamp as another action type - if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED || second.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) && first.actionName !== second.actionName) { - return (first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED ? -1 : 1) * invertedMultiplier; - } + // Then sort by timestamp + if (first.created !== second.created) { + return (first.created < second.created ? -1 : 1) * invertedMultiplier; + } - // Ensure that neither first's nor second's created property is undefined - if (first.created === undefined || second.created === undefined) { - return (first.created === undefined ? -1 : 1) * invertedMultiplier; - } + // Ensure that `REPORT_PREVIEW` actions always come after if they have the same timestamp as another action type + if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW || second.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) && first.actionName !== second.actionName) { + return (first.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW ? 1 : -1) * invertedMultiplier; + } - // Then sort by timestamp - if (first.created !== second.created) { - return (first.created < second.created ? -1 : 1) * invertedMultiplier; - } + // Then fallback on reportActionID as the final sorting criteria. It is a random number, + // but using this will ensure that the order of reportActions with the same created time and action type + // will be consistent across all users and devices + return (first.reportActionID < second.reportActionID ? -1 : 1) * invertedMultiplier; +} - // Ensure that `REPORT_PREVIEW` actions always come after if they have the same timestamp as another action type - if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW || second.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) && first.actionName !== second.actionName) { - return (first.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW ? 1 : -1) * invertedMultiplier; - } +/** + * Sort an array of reportActions by their created timestamp first, and reportActionID second + * This gives us a stable order even in the case of multiple reportActions created on the same millisecond + * + */ +function getSortedReportActions(reportActions: ReportAction[] | null, shouldSortInDescendingOrder = false): ReportAction[] { + if (!Array.isArray(reportActions)) { + throw new Error(`ReportActionsUtils.getSortedReportActions requires an array, received ${typeof reportActions}`); + } - // Then fallback on reportActionID as the final sorting criteria. It is a random number, - // but using this will ensure that the order of reportActions with the same created time and action type - // will be consistent across all users and devices - return (first.reportActionID < second.reportActionID ? -1 : 1) * invertedMultiplier; - }); + const sortedActions = reportActions?.filter(Boolean).sort((first, second) => getSortedReportActionsComparator(first, second, shouldSortInDescendingOrder)); return sortedActions; } @@ -541,8 +548,8 @@ function getMostRecentIOURequestActionID(reportActions: ReportAction[] | null): return null; } - const sortedReportActions = getSortedReportActions(iouRequestActions); - return sortedReportActions.at(-1)?.reportActionID ?? null; + const lastSortedReportAction = arrayLastElement(iouRequestActions, getSortedReportActionsComparator); + return lastSortedReportAction?.reportActionID ?? null; } /** @@ -921,11 +928,8 @@ function getLastVisibleAction( reportActions = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}); } const visibleReportActions = reportActions.filter((action): action is ReportAction => shouldReportActionBeVisibleAsLastAction(action, canUserPerformWriteAction)); - const sortedReportActions = getSortedReportActions(visibleReportActions, true); - if (sortedReportActions.length === 0) { - return undefined; - } - return sortedReportActions.at(0); + const lastVisibleAction = arrayFirstElement(visibleReportActions, (el1, el2) => getSortedReportActionsComparator(el1, el2, true)); + return lastVisibleAction; } function formatLastMessageText(lastMessageText: string | undefined) { @@ -984,6 +988,37 @@ function filterOutDeprecatedReportActions(reportActions: OnyxEntry | ReportAction[], + canUserPerformWriteAction?: boolean, + shouldIncludeInvisibleActions = false, +): ReportAction | null { + if (!reportActions) { + return null; + } + + let filteredReportActions: ReportAction[] = []; + + if (shouldIncludeInvisibleActions) { + filteredReportActions = Object.values(reportActions).filter(Boolean); + } else { + filteredReportActions = Object.entries(reportActions) + .filter(([key, reportAction]) => shouldReportActionBeVisible(reportAction, key, canUserPerformWriteAction)) + .map(([, reportAction]) => reportAction); + } + + const comparator = (first: ReportAction, second: ReportAction) => { + return getSortedReportActionsComparator(first, second, true); + }; + + const firstReportAction = arrayFirstElement(filteredReportActions, comparator); + return firstReportAction ? replaceBaseURLInPolicyChangeLogAction(firstReportAction) : null; +} + +/** + * This method returns the last report action that are ready for display in the ReportActionsView. * Helper for filtering out Report Actions that are either: * - ReportPreview with shouldShow set to false and without a pending action * - Money request with parent action deleted @@ -1039,8 +1074,7 @@ function getLastClosedReportAction(reportActions: OnyxEntry): Ony } const filteredReportActions = filterOutDeprecatedReportActions(reportActions); - const sortedReportActions = getSortedReportActions(filteredReportActions); - return lodashFindLast(sortedReportActions, (action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED); + return arrayLastElement(filteredReportActions, getSortedReportActionsComparator, (action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED); } /** @@ -1070,8 +1104,7 @@ function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): NonNu } const reportActions = Object.values((reportActionUpdate.value as ReportActions) ?? {}); - const sortedReportActions = getSortedReportActions(reportActions); - return sortedReportActions.at(-1) ?? null; + return arrayLastElement(reportActions, getSortedReportActionsComparator) ?? null; } /** @@ -2974,7 +3007,9 @@ export { getReportActionMessageText, getReportActionText, getReportPreviewAction, + getSortedReportActionsComparator, getSortedReportActions, + getFirstSortedReportActionForDisplay, getSortedReportActionsForDisplay, getTextFromHtml, getTrackExpenseActionableWhisper, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index b985c6c4d918..6adfd1b765e8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -13,6 +13,7 @@ import type Policy from '@src/types/onyx/Policy'; import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; +import arrayLastElement from '@src/utils/arrayLastElement'; import {getExpensifyCardFromReportAction} from './CardMessageUtils'; import {extractCollectionItemID} from './CollectionUtils'; import {hasValidDraftComment} from './DraftCommentUtils'; @@ -46,7 +47,7 @@ import { getReportAction, getReportActionMessageText, getRetractedMessage, - getSortedReportActions, + getSortedReportActionsComparator, getTagListNameUpdatedMessage, getTravelUpdateMessage, getUpdatedApprovalRuleMessage, @@ -157,15 +158,15 @@ Onyx.connect({ const reportID = extractCollectionItemID(key); const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const canUserPerformWriteAction = canUserPerformWriteActionUtil(report); - const actionsArray: ReportAction[] = getSortedReportActions(Object.values(actions)); // The report is only visible if it is the last action not deleted that // does not match a closed or created state. - const reportActionsForDisplay = actionsArray.filter( - (reportAction) => shouldReportActionBeVisibleAsLastAction(reportAction, canUserPerformWriteAction) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED, + const reportAction = arrayLastElement( + Object.values(actions), + getSortedReportActionsComparator, + (action) => shouldReportActionBeVisibleAsLastAction(action, canUserPerformWriteAction) && action.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED, ); - const reportAction = reportActionsForDisplay.at(-1); if (!reportAction) { delete visibleReportActionItems[reportID]; return; diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 193a4b66e83c..cbdfc540f2d5 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -24,6 +24,7 @@ import type {Policy, TaxRate, TaxRates} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {Rate} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; +import arrayFirstElement from '@src/utils/arrayFirstElement'; let allPolicies: OnyxCollection; Onyx.connect({ @@ -298,9 +299,7 @@ type TaxRateDeleteMap = Record< function deletePolicyTaxes(policy: OnyxEntry, taxesToDelete: string[]) { const policyTaxRates = policy?.taxRates?.taxes; const foreignTaxDefault = policy?.taxRates?.foreignTaxDefault; - const firstTaxID = Object.keys(policyTaxRates ?? {}) - .sort((a, b) => a.localeCompare(b)) - .at(0); + const firstTaxID = arrayFirstElement(Object.keys(policyTaxRates ?? {}), (a, b) => a.localeCompare(b)); const distanceRateCustomUnit = PolicyUtils.getDistanceRateCustomUnit(policy); const customUnitID = distanceRateCustomUnit?.customUnitID; const ratesToUpdate = Object.values(distanceRateCustomUnit?.rates ?? {}).filter( diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 4e6d9abd963d..302f47c1c668 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -30,6 +30,7 @@ import type {OriginalMessageModifiedExpense} from '@src/types/onyx/OriginalMessa import type {OnyxData} from '@src/types/onyx/Request'; import type {WaypointCollection} from '@src/types/onyx/Transaction'; import type TransactionState from '@src/types/utils/TransactionStateType'; +import arrayLastElement from '@src/utils/arrayLastElement'; import {getPolicyCategoriesData} from './Policy/Category'; import {getPolicyTagsData} from './Policy/Tag'; @@ -498,8 +499,8 @@ function clearError(transactionID: string) { function getLastModifiedExpense(reportID?: string): OriginalMessageModifiedExpense | undefined { const modifiedExpenseActions = Object.values(getAllReportActions(reportID)).filter(isModifiedExpenseAction); - modifiedExpenseActions.sort((a, b) => Number(a.reportActionID) - Number(b.reportActionID)); - return getOriginalMessage(modifiedExpenseActions.at(-1)); + const lastModifiedExpenseActions = arrayLastElement(modifiedExpenseActions, (a, b) => Number(a.reportActionID) - Number(b.reportActionID)); + return getOriginalMessage(lastModifiedExpenseActions); } function revert(transactionID?: string, originalMessage?: OriginalMessageModifiedExpense | undefined) { diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 564d79dd2759..1c25cfe5fe1d 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -1,4 +1,3 @@ -import lodashSortBy from 'lodash/sortBy'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {FormOnyxValues} from '@components/Form/types'; @@ -11,6 +10,7 @@ import type {InternationalBankAccountForm} from '@src/types/form'; import type {BankAccount, BankAccountList, CorpayFields, PrivatePersonalDetails} from '@src/types/onyx'; import type {CorpayFieldsMap} from '@src/types/onyx/CorpayFields'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import arrayLastElement from '@src/utils/arrayLastElement'; function getFieldsMap(corpayFields: OnyxEntry): Record, CorpayFieldsMap> { return (corpayFields?.formFields ?? []).reduce( @@ -37,7 +37,15 @@ function getFieldsMap(corpayFields: OnyxEntry): Record): BankAccount | undefined { - return lodashSortBy(Object.values(bankAccountList ?? {}), 'accountData.created').pop(); + const comparator = (bankAccount1: BankAccount, bankAccount2: BankAccount) => { + const created1 = bankAccount1.accountData?.created ?? ''; + const created2 = bankAccount2.accountData?.created ?? ''; + if (created1 === created2) { + return 0; + } + return created1 > created2 ? 1 : -1; + }; + return arrayLastElement(Object.values(bankAccountList ?? {}), comparator); } function getSubstepValues( diff --git a/src/types/onyx/AccountData.ts b/src/types/onyx/AccountData.ts index 6bb69cd78dc4..b258a1ebc4e1 100644 --- a/src/types/onyx/AccountData.ts +++ b/src/types/onyx/AccountData.ts @@ -50,6 +50,9 @@ type AccountData = { /** The debit card ID */ fundID?: number; + + /** When was the bank account added */ + created?: string; }; export default AccountData; diff --git a/src/utils/arrayFirstElement.ts b/src/utils/arrayFirstElement.ts new file mode 100644 index 000000000000..df1a5b737cb9 --- /dev/null +++ b/src/utils/arrayFirstElement.ts @@ -0,0 +1,28 @@ +function arrayFirstElement(array: T[] | undefined | null, comparator?: (el1: T, el2: T) => number, filter: (el: T) => boolean = () => true): T | undefined { + if (!array?.length) { + return undefined; + } + if (array.length === 1) { + return array.at(0); + } + let i = array.findIndex((el) => filter(el)); + if (i === -1) { + return undefined; + } + let candidateElement = array.at(i) as T; + while (i < array.length) { + const element = array.at(i) as T; + if (filter(element)) { + if (!comparator && element < candidateElement) { + candidateElement = element; + } + if (comparator && comparator(element, candidateElement) < 0) { + candidateElement = element; + } + } + i++; + } + return candidateElement; +} + +export default arrayFirstElement; diff --git a/src/utils/arrayLastElement.ts b/src/utils/arrayLastElement.ts new file mode 100644 index 000000000000..480658840209 --- /dev/null +++ b/src/utils/arrayLastElement.ts @@ -0,0 +1,28 @@ +function arrayLastElement(array: T[] | undefined | null, comparator?: (el1: T, el2: T) => number, filter: (el: T) => boolean = () => true): T | undefined { + if (!array?.length) { + return undefined; + } + if (array.length === 1) { + return array.at(0); + } + let i = array.findIndex((el) => filter(el)); + if (i === -1) { + return undefined; + } + let candidateElement = array.at(i) as T; + while (i < array.length) { + const element = array.at(i) as T; + if (filter(element)) { + if (!comparator && element >= candidateElement) { + candidateElement = element; + } + if (comparator && comparator(element, candidateElement) >= 0) { + candidateElement = element; + } + } + i++; + } + return candidateElement; +} + +export default arrayLastElement;