From 17247ce4cc6d9ccf863261c9209fc3c71004ce98 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 30 Jul 2025 16:42:38 +0530 Subject: [PATCH 1/3] Refactored localeCompare in PolicyUtils --- src/hooks/useWorkspaceList.ts | 8 +++++--- src/libs/PolicyUtils.ts | 12 ++++++++++-- src/pages/ReportChangeWorkspacePage.tsx | 3 ++- src/pages/Search/AdvancedSearchFilters.tsx | 1 + .../SearchFiltersWorkspacePage.tsx | 3 ++- .../request/step/IOURequestStepPerDiemWorkspace.tsx | 5 +++-- .../iou/request/step/IOURequestStepSendFrom.tsx | 5 +++-- .../netsuite/import/NetSuiteImportPage.tsx | 4 ++-- 8 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/hooks/useWorkspaceList.ts b/src/hooks/useWorkspaceList.ts index 6cff4900a53c..37ebea65220d 100644 --- a/src/hooks/useWorkspaceList.ts +++ b/src/hooks/useWorkspaceList.ts @@ -1,6 +1,7 @@ import {useMemo} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {ListItem, SectionListDataType} from '@components/SelectionList/types'; import {isPolicyAdmin, shouldShowPolicy, sortWorkspacesBySelected} from '@libs/PolicyUtils'; import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; @@ -23,10 +24,11 @@ type UseWorkspaceListParams = { shouldShowPendingDeletePolicy: boolean; selectedPolicyIDs: string[] | undefined; searchTerm: string; + localeCompare: LocaleContextProps['localeCompare']; additionalFilter?: (policy: OnyxEntry) => boolean; }; -function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, searchTerm, shouldShowPendingDeletePolicy, additionalFilter}: UseWorkspaceListParams) { +function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, searchTerm, shouldShowPendingDeletePolicy, localeCompare, additionalFilter}: UseWorkspaceListParams) { const usersWorkspaces = useMemo(() => { if (!policies || isEmptyObject(policies)) { return []; @@ -61,9 +63,9 @@ function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, search const filteredAndSortedUserWorkspaces = useMemo( () => tokenizedSearch(usersWorkspaces, searchTerm, (policy) => [policy.text]).sort((policy1, policy2) => - sortWorkspacesBySelected({policyID: policy1.policyID, name: policy1.text}, {policyID: policy2.policyID, name: policy2.text}, selectedPolicyIDs), + sortWorkspacesBySelected({policyID: policy1.policyID, name: policy1.text}, {policyID: policy2.policyID, name: policy2.text}, selectedPolicyIDs, localeCompare), ), - [searchTerm, usersWorkspaces, selectedPolicyIDs], + [searchTerm, usersWorkspaces, selectedPolicyIDs, localeCompare], ); const sections = useMemo(() => { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 42b0663bf5b2..10abb3fc8615 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -36,7 +36,6 @@ import {hasSynchronizationErrorMessage, isAuthenticationError} from './actions/c import {shouldShowQBOReimbursableExportDestinationAccountError} from './actions/connections/QuickbooksOnline'; import {getCurrentUserAccountID, getCurrentUserEmail} from './actions/Report'; import {getCategoryApproverRule} from './CategoryUtils'; -import localeCompare from './LocaleCompare'; import {translateLocal} from './Localize'; import Navigation from './Navigation/Navigation'; import {isOffline as isOfflineNetworkStore} from './Network/NetworkStore'; @@ -1084,6 +1083,7 @@ function getNetSuiteImportCustomFieldLabel( policy: Policy | undefined, importField: ValueOf, translate: LocaleContextProps['translate'], + localeCompare: LocaleContextProps['localeCompare'], ): string | undefined { const fieldData = policy?.connections?.netsuite?.options?.config.syncOptions?.[importField] ?? []; if (fieldData.length === 0) { @@ -1194,7 +1194,15 @@ function getSageIntacctCreditCards(policy?: Policy, selectedAccount?: string): S * @param workspace2 Details of the second workspace to be compared. * @param selectedWorkspaceID ID of the selected workspace which needs to be at the beginning. */ -const sortWorkspacesBySelected = (workspace1: WorkspaceDetails, workspace2: WorkspaceDetails, selectedWorkspaceIDs: string[] | undefined): number => { +const sortWorkspacesBySelected = ( + workspace1: WorkspaceDetails, + workspace2: WorkspaceDetails, + selectedWorkspaceIDs: string[] | undefined, + localeCompare: LocaleContextProps['localeCompare'], +): number => { + if (workspace1.policyID && selectedWorkspaceIDs?.includes(workspace1?.policyID) && workspace2.policyID && selectedWorkspaceIDs?.includes(workspace2.policyID)) { + return localeCompare(workspace1.name?.toLowerCase() ?? '', workspace2.name?.toLowerCase() ?? ''); + } if (workspace1.policyID && selectedWorkspaceIDs?.includes(workspace1?.policyID)) { return -1; } diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index 80bf2a5375d0..ebcff360fd61 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -31,7 +31,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro const {isOffline} = useNetwork(); const styles = useThemeStyles(); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); - const {translate} = useLocalize(); + const {translate, localeCompare} = useLocalize(); const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); const [reportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${reportID}`, {canBeMissing: true}); @@ -70,6 +70,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro shouldShowPendingDeletePolicy: false, selectedPolicyIDs: report.policyID ? [report.policyID] : undefined, searchTerm: debouncedSearchTerm, + localeCompare, additionalFilter: (newPolicy) => isWorkspaceEligibleForReportChange(newPolicy, report, policies), }); diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 76a1991be19b..1fd922df1162 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -587,6 +587,7 @@ function AdvancedSearchFilters() { shouldShowPendingDeletePolicy: false, selectedPolicyIDs: undefined, searchTerm: '', + localeCompare, }); // When looking if a user has any categories to display, we want to ignore the policies that are of type PERSONAL diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersWorkspacePage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersWorkspacePage.tsx index 4cc74cf5c20c..6aedf0bfa8de 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersWorkspacePage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersWorkspacePage.tsx @@ -27,7 +27,7 @@ const updateWorkspaceFilter = (policyID: string[] | null) => { function SearchFiltersWorkspacePage() { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {translate, localeCompare} = useLocalize(); const {isOffline} = useNetwork(); const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {canBeMissing: true}); @@ -45,6 +45,7 @@ function SearchFiltersWorkspacePage() { shouldShowPendingDeletePolicy: false, selectedPolicyIDs: selectedOptions, searchTerm: debouncedSearchTerm, + localeCompare, }); const selectWorkspace = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx index 1eed8a42349d..68438d0aae25 100644 --- a/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx @@ -31,7 +31,7 @@ function IOURequestStepPerDiemWorkspace({ }, transaction, }: IOURequestStepPerDiemWorkspaceProps) { - const {translate} = useLocalize(); + const {translate, localeCompare} = useLocalize(); const {login: currentUserLogin, accountID} = useCurrentUserPersonalDetails(); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); @@ -46,6 +46,7 @@ function IOURequestStepPerDiemWorkspace({ {policyID: policy1.id, name: policy1.name}, {policyID: policy2.id, name: policy2.name}, selectedWorkspace?.policyID ? [selectedWorkspace?.policyID] : [], + localeCompare, ), ) .map((policy) => ({ @@ -63,7 +64,7 @@ function IOURequestStepPerDiemWorkspace({ ], isSelected: selectedWorkspace?.policyID === policy.id, })); - }, [allPolicies, currentUserLogin, selectedWorkspace]); + }, [allPolicies, currentUserLogin, selectedWorkspace, localeCompare]); const selectWorkspace = (item: WorkspaceListItem) => { const policyExpenseReportID = getPolicyExpenseChat(accountID, item.value)?.reportID; diff --git a/src/pages/iou/request/step/IOURequestStepSendFrom.tsx b/src/pages/iou/request/step/IOURequestStepSendFrom.tsx index d965fb67ad90..a9bd0e73586c 100644 --- a/src/pages/iou/request/step/IOURequestStepSendFrom.tsx +++ b/src/pages/iou/request/step/IOURequestStepSendFrom.tsx @@ -26,7 +26,7 @@ type IOURequestStepSendFromProps = WithWritableReportOrNotFoundProps; function IOURequestStepSendFrom({route, transaction}: IOURequestStepSendFromProps) { - const {translate} = useLocalize(); + const {translate, localeCompare} = useLocalize(); const {transactionID, backTo} = route.params; const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email, canBeMissing: false}); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); @@ -42,6 +42,7 @@ function IOURequestStepSendFrom({route, transaction}: IOURequestStepSendFromProp {policyID: policy1.id, name: policy1.name}, {policyID: policy2.id, name: policy2.name}, selectedWorkspace?.policyID ? [selectedWorkspace?.policyID] : [], + localeCompare, ), ) .map((policy) => ({ @@ -59,7 +60,7 @@ function IOURequestStepSendFrom({route, transaction}: IOURequestStepSendFromProp ], isSelected: selectedWorkspace?.policyID === policy.id, })); - }, [allPolicies, currentUserLogin, selectedWorkspace]); + }, [allPolicies, currentUserLogin, selectedWorkspace, localeCompare]); const navigateBack = () => { Navigation.goBack(backTo); diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx index 8af256784811..3cdb1d9035f5 100644 --- a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx @@ -18,7 +18,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; function NetSuiteImportPage({policy}: WithPolicyConnectionsProps) { - const {translate} = useLocalize(); + const {translate, localeCompare} = useLocalize(); const styles = useThemeStyles(); const {isBetaEnabled} = usePermissions(); @@ -131,7 +131,7 @@ function NetSuiteImportPage({policy}: WithPolicyConnectionsProps) { shouldDisableStrikeThrough > { From 480b7deb992a5ce74aff25b740e35b70a0e31e4d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 1 Aug 2025 00:31:03 +0530 Subject: [PATCH 2/3] Add some tests --- tests/unit/PolicyUtilsTest.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 8660792749a0..e4214d018ca9 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -13,6 +13,7 @@ import { getUnitRateValue, isUserInvitedToWorkspace, shouldShowPolicy, + sortWorkspacesBySelected, } from '@libs/PolicyUtils'; import {isWorkspaceEligibleForReportChange} from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -854,4 +855,25 @@ describe('PolicyUtils', () => { expect(tagList.name).toEqual(expected); }); }); + describe('sortWorkspacesBySelected', () => { + it('should order workspaces with selected workspace first', () => { + const workspace1 = {policyID: '1', name: 'Workspace 1'}; + const workspace2 = {policyID: '2', name: 'Workspace 2'}; + const selectedWorkspace1 = {policyID: '3', name: 'Workspace 3'}; + const selectedWorkspace2 = {policyID: '4', name: 'Workspace 4'}; + expect(sortWorkspacesBySelected(workspace1, workspace2, ['3', '4'], TestHelper.localeCompare)).toBe(-1); + expect(sortWorkspacesBySelected(workspace1, selectedWorkspace1, ['3', '4'], TestHelper.localeCompare)).toBe(1); + expect(sortWorkspacesBySelected(selectedWorkspace1, selectedWorkspace2, ['3', '4'], TestHelper.localeCompare)).toBe(-1); + }); + + it('should order workspaces using name if no workspace is selected', () => { + const workspace1 = {policyID: '1', name: 'Workspace 1'}; + const workspace2 = {policyID: '2', name: 'Workspace 2'}; + const workspace3 = {policyID: '3', name: 'Workspace 3'}; + const workspace4 = {policyID: '4', name: 'Workspace 4'}; + expect(sortWorkspacesBySelected(workspace1, workspace2, undefined, TestHelper.localeCompare)).toBe(-1); + expect(sortWorkspacesBySelected(workspace1, workspace3, undefined, TestHelper.localeCompare)).toBe(-1); + expect(sortWorkspacesBySelected(workspace3, workspace4, undefined, TestHelper.localeCompare)).toBe(-1); + }); + }); }); From 9134584cef4048cbfaaa064a90315389be758d84 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 1 Aug 2025 11:07:11 +0530 Subject: [PATCH 3/3] Add some more tests --- tests/unit/PolicyUtilsTest.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index e4214d018ca9..4430a389079e 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -875,5 +875,22 @@ describe('PolicyUtils', () => { expect(sortWorkspacesBySelected(workspace1, workspace3, undefined, TestHelper.localeCompare)).toBe(-1); expect(sortWorkspacesBySelected(workspace3, workspace4, undefined, TestHelper.localeCompare)).toBe(-1); }); + + it('should sort workspaces when using this method correctly', () => { + const unsortedWorkspaces = [ + {policyID: '2', name: 'Workspace 2'}, + {policyID: '1', name: 'Workspace 1'}, + {policyID: '4', name: 'Workspace 4'}, + {policyID: '3', name: 'Workspace 3'}, + ]; + const selectedWorkspaceIDs = ['3', '4']; + const sortedWorkspaces = unsortedWorkspaces.sort((a, b) => sortWorkspacesBySelected(a, b, selectedWorkspaceIDs, TestHelper.localeCompare)); + expect(sortedWorkspaces).toEqual([ + {policyID: '3', name: 'Workspace 3'}, + {policyID: '4', name: 'Workspace 4'}, + {policyID: '1', name: 'Workspace 1'}, + {policyID: '2', name: 'Workspace 2'}, + ]); + }); }); });