Skip to content
Open
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
20 changes: 16 additions & 4 deletions src/pages/DynamicReportChangeWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import type {DismissedProductTraining} from '@src/types/onyx';
import type {OnyxEntry} from 'react-native-onyx';

import {isTrackIntentUserSelector} from '@selectors/Onboarding';
import React from 'react';
import React, {useState} from 'react';
import {View} from 'react-native';

import type {WithReportOrNotFoundProps} from './inbox/report/withReportOrNotFound';
Expand Down Expand Up @@ -117,6 +117,9 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace
const {currentSearchResults} = useSearchResultsContext();
const shouldCalculateTotals = useSearchShouldCalculateTotals(currentSearchKey, currentSearchQueryJSON?.hash, true);

const [draftPolicyID, setDraftPolicyID] = useState<string>();
const currentSelection = draftPolicyID ?? report.policyID;

// The snapshot keeps the report row after a workspace change, and only the server can tell whether it still matches the query.
const refreshSearch = () => {
refreshSearchAfterReportAction({
Expand Down Expand Up @@ -215,7 +218,7 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace
policies,
currentUserLogin: session?.email,
shouldShowPendingDeletePolicy: false,
selectedPolicyIDs: report.policyID ? [report.policyID] : undefined,
selectedPolicyIDs: currentSelection ? [currentSelection] : undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep draft workspace selections in place until Save

When a user selects a workspace that is not already first—particularly while searching—passing the changing currentSelection to useWorkspaceList triggers its default shouldSortSelectedToTop behavior, immediately moving that row to the top before Save. During an active search, SelectionList retains its numeric focused index, so focus can now point to a different workspace and the next Enter press selects that unintended row; the immediate reorder also preserves the input-driven context shift this change is meant to prevent. Set shouldSortSelectedToTop: false here and in the corresponding SetDefaultWorkspacePage call while retaining selectedPolicyIDs for highlighting.

Useful? React with 👍 / 👎.

searchTerm: debouncedSearchTerm,
localeCompare,
additionalFilter: (newPolicy) => {
Expand All @@ -235,14 +238,21 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace
headerMessage: shouldShowNoResultsFoundMessage ? translate('common.noResultsFound') : '',
};

const confirmButtonOptions = {
showButton: true,
text: translate('common.save'),
onConfirm: () => selectPolicy(currentSelection),
isDisabled: !currentSelection || currentSelection === report.policyID,
};

if (!isMoneyRequestReport(report) || isMoneyRequestReportPendingDeletion(report) || hasCommuterExclusionDistanceRequest) {
return <NotFoundPage />;
}

return (
<ScreenWrapper
testID="DynamicReportChangeWorkspacePage"
includeSafeAreaPaddingBottom
enableEdgeToEdgeBottomSafeAreaPadding
shouldEnableMaxHeight
>
{({didScreenTransitionEnd}) => (
Expand All @@ -261,11 +271,13 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace
<SelectionList<WorkspaceListItemType>
ListItem={UserListItem}
data={data}
onSelectRow={(option) => selectPolicy(option.policyID)}
onSelectRow={(option) => setDraftPolicyID(option.policyID)}
textInputOptions={textInputOptions}
confirmButtonOptions={confirmButtonOptions}
initiallyFocusedItemKey={report.policyID}
shouldShowLoadingPlaceholder={fetchStatus.status === 'loading' || !didScreenTransitionEnd}
disableMaintainingScrollPosition
addBottomSafeAreaPadding
/>
)}
</>
Expand Down
20 changes: 16 additions & 4 deletions src/pages/SetDefaultWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';

import React, {useMemo} from 'react';
import React, {useMemo, useState} from 'react';
import {View} from 'react-native';

type SetDefaultWorkspacePageProps = PlatformStackScreenProps<MoneyRequestNavigatorParamList, typeof SCREENS.SET_DEFAULT_WORKSPACE>;
Expand All @@ -46,6 +46,9 @@ function SetDefaultWorkspacePage({route}: SetDefaultWorkspacePageProps) {
const shouldShowLoadingIndicator = isAppLoadPending && !isOffline;
const session = useSession();

const [draftPolicyID, setDraftPolicyID] = useState<string>();
const currentSelection = draftPolicyID ?? activePolicyID;

const selectPolicy = (selectedPolicyID?: string) => {
if (!selectedPolicyID) {
return;
Expand All @@ -72,12 +75,19 @@ function SetDefaultWorkspacePage({route}: SetDefaultWorkspacePageProps) {
policies,
currentUserLogin: session?.email,
shouldShowPendingDeletePolicy: false,
selectedPolicyIDs: undefined,
selectedPolicyIDs: draftPolicyID ? [draftPolicyID] : undefined,
searchTerm: debouncedSearchTerm,
localeCompare,
additionalFilter: (newPolicy) => isGroupPolicy(newPolicy),
});

const confirmButtonOptions = {
showButton: true,
text: translate('common.save'),
onConfirm: () => selectPolicy(currentSelection),
isDisabled: currentSelection === activePolicyID,
};

const textInputOptions = useMemo(
() => ({
label: shouldShowSearchInput ? translate('common.search') : undefined,
Expand All @@ -91,7 +101,7 @@ function SetDefaultWorkspacePage({route}: SetDefaultWorkspacePageProps) {
return (
<ScreenWrapper
testID="SetDefaultWorkspacePage"
includeSafeAreaPaddingBottom
enableEdgeToEdgeBottomSafeAreaPadding
shouldEnableMaxHeight
>
{({didScreenTransitionEnd}) => (
Expand All @@ -109,9 +119,11 @@ function SetDefaultWorkspacePage({route}: SetDefaultWorkspacePageProps) {
data={data}
ListItem={UserListItem}
textInputOptions={textInputOptions}
onSelectRow={(option) => selectPolicy(option.policyID)}
onSelectRow={(option) => setDraftPolicyID(option.policyID)}
confirmButtonOptions={confirmButtonOptions}
shouldShowLoadingPlaceholder={fetchStatus.status === 'loading' || !didScreenTransitionEnd}
disableMaintainingScrollPosition
addBottomSafeAreaPadding
/>
)}
</>
Expand Down
34 changes: 24 additions & 10 deletions src/pages/domain/Groups/DomainGroupPreferredWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type SCREENS from '@src/SCREENS';

import {domainSecurityGroupSettingPendingActionSelector, selectGroupByID} from '@selectors/Domain';
import {createAdminPoliciesSelector} from '@selectors/Policy';
import React from 'react';
import React, {useState} from 'react';

type WorkspaceListItem = {
policyID: string;
Expand All @@ -48,6 +48,9 @@ function DomainGroupPreferredWorkspacePage({route}: DomainGroupPreferredWorkspac

const currentPolicyID = group?.restrictedPrimaryPolicyID;

const [selectedPolicyID, setSelectedPolicyID] = useState<string>();
const currentSelection = selectedPolicyID ?? currentPolicyID;

const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: createAdminPoliciesSelector(currentPolicyID)});

const workspaceOptions: WorkspaceListItem[] = [];
Expand All @@ -61,10 +64,25 @@ function DomainGroupPreferredWorkspacePage({route}: DomainGroupPreferredWorkspac
policyID: policy.id,
created: policy.created,
keyForList: policy.id,
isSelected: currentPolicyID === policy.id,
isSelected: currentSelection === policy.id,
});
}

const saveWorkspace = () => {
if (!group || !currentSelection) {
return;
}
updateDomainSecurityGroup(domainAccountID, groupID, group, {restrictedPrimaryPolicyID: currentSelection}, 'restrictedPrimaryPolicyID');
Navigation.goBack(ROUTES.DOMAIN_GROUP_DETAILS.getRoute(domainAccountID, groupID));
};

const confirmButtonOptions = {
showButton: true,
text: translate('common.save'),
onConfirm: saveWorkspace,
isDisabled: currentSelection === currentPolicyID,
};

return (
<DomainNotFoundPageWrapper
domainAccountID={domainAccountID}
Expand All @@ -76,7 +94,7 @@ function DomainGroupPreferredWorkspacePage({route}: DomainGroupPreferredWorkspac
<ScreenWrapper
shouldEnableMaxHeight
testID="DomainGroupPreferredWorkspacePage"
includeSafeAreaPaddingBottom
enableEdgeToEdgeBottomSafeAreaPadding
>
<HeaderWithBackButton
title={translate('domain.groups.preferredWorkspace')}
Expand All @@ -86,15 +104,11 @@ function DomainGroupPreferredWorkspacePage({route}: DomainGroupPreferredWorkspac
<SelectionList<WorkspaceListItem>
data={workspaceOptions.sort((a, b) => localeCompare(a.created ?? '', b.created ?? ''))}
ListItem={UserListItem}
onSelectRow={(item: WorkspaceListItem) => {
if (!group) {
return;
}
updateDomainSecurityGroup(domainAccountID, groupID, group, {restrictedPrimaryPolicyID: item.policyID}, 'restrictedPrimaryPolicyID');
Navigation.goBack(ROUTES.DOMAIN_GROUP_DETAILS.getRoute(domainAccountID, groupID));
}}
onSelectRow={(item: WorkspaceListItem) => setSelectedPolicyID(item.policyID)}
confirmButtonOptions={confirmButtonOptions}
initiallyFocusedItemKey={currentPolicyID}
shouldUpdateFocusedIndex
addBottomSafeAreaPadding
/>
</ScreenWrapper>
</DomainNotFoundPageWrapper>
Expand Down
Loading