diff --git a/src/hooks/useFilteredSelection.ts b/src/hooks/useFilteredSelection.ts new file mode 100644 index 000000000000..e803bdd61e7c --- /dev/null +++ b/src/hooks/useFilteredSelection.ts @@ -0,0 +1,19 @@ +import {useEffect, useState} from 'react'; + +/** + * Custom hook to manage a selection of keys from a given set of options. + * It filters the selected keys based on a provided filter function whenever the options or the filter change. + * + * @param options - Option data + * @param filter - Filter function + * @returns A tuple containing the array of selected keys and a function to update the selected keys. + */ +function useFilteredSelection(options: Record | undefined, filter: (option: TValue | undefined) => boolean) { + const [selectedOptions, setSelectedOptions] = useState([]); + + useEffect(() => setSelectedOptions((prevOptions) => prevOptions.filter((key) => filter(options?.[key]))), [options, filter]); + + return [selectedOptions, setSelectedOptions] as const; +} + +export default useFilteredSelection; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 066909dfcc3d..ea369d6d06f6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2760,11 +2760,11 @@ function getParticipantsAccountIDsForDisplay( return participantsIds.filter((accountID) => isNumber(accountID)); } -function getParticipantsList(report: Report, personalDetails: OnyxEntry, isRoomMembersList = false): number[] { +function getParticipantsList(report: Report, personalDetails: OnyxEntry, isRoomMembersList = false, reportMetadata: OnyxEntry = undefined): number[] { const isReportGroupChat = isGroupChat(report); const isReportIOU = isIOUReport(report); const shouldExcludeHiddenParticipants = !isReportGroupChat && !isReportIOU; - const chatParticipants = getParticipantsAccountIDsForDisplay(report, isRoomMembersList || shouldExcludeHiddenParticipants); + const chatParticipants = getParticipantsAccountIDsForDisplay(report, isRoomMembersList || shouldExcludeHiddenParticipants, false, false, reportMetadata); return chatParticipants.filter((accountID) => { const details = personalDetails?.[accountID]; @@ -10441,6 +10441,20 @@ function getChatListItemReportName(action: ReportAction & {reportName?: string}, return action?.reportName ?? ''; } +function getReportPersonalDetailsParticipants(report: Report, personalDetailsParam: OnyxEntry, reportMetadata: OnyxEntry, isRoomMembersList = false) { + const chatParticipants = getParticipantsList(report, personalDetailsParam, isRoomMembersList, reportMetadata); + return { + chatParticipants, + personalDetailsParticipants: chatParticipants.reduce>((acc, accountID) => { + const details = personalDetailsParam?.[accountID]; + if (details) { + acc[accountID] = details; + } + return acc; + }, {}), + }; +} + export { addDomainToShortMention, completeShortMention, @@ -10810,6 +10824,7 @@ export { populateOptimisticReportFormula, getOutstandingReportsForUser, isReportOutstanding, + getReportPersonalDetailsParticipants, isAllowedToSubmitDraftExpenseReport, }; diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index ef8b0c317a19..a62d5089fdd1 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -17,6 +17,7 @@ import TableListItem from '@components/SelectionList/TableListItem'; import type {ListItem, SelectionListHandle} from '@components/SelectionList/types'; import SelectionListWithModal from '@components/SelectionListWithModal'; import Text from '@components/Text'; +import useFilteredSelection from '@hooks/useFilteredSelection'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -33,8 +34,8 @@ import type {ParticipantsNavigatorParamList} from '@libs/Navigation/types'; import {isSearchStringMatchUserDetails} from '@libs/OptionsListUtils'; import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; import { - getParticipantsList, getReportName, + getReportPersonalDetailsParticipants, isArchivedNonExpenseReport, isChatRoom, isChatThread, @@ -50,6 +51,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type {PersonalDetails} from '@src/types/onyx'; import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; import withReportOrNotFound from './home/report/withReportOrNotFound'; @@ -58,7 +60,6 @@ type MemberOption = Omit & {accountID: number}; type ReportParticipantsPageProps = WithReportOrNotFoundProps & PlatformStackScreenProps; function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { const backTo = route.params.backTo; - const [selectedMembers, setSelectedMembers] = useState([]); const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false); const {translate, formatPhoneNumber} = useLocalize(); const styles = useThemeStyles(); @@ -84,14 +85,26 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { const canSelectMultiple = isGroupChat && isCurrentUserAdmin && (isSmallScreenWidth ? selectionMode?.isEnabled : true); const [searchValue, setSearchValue] = useState(''); - useEffect(() => { - if (isFocused) { - return; - } - setSelectedMembers([]); - }, [isFocused]); + const {chatParticipants, personalDetailsParticipants} = useMemo( + () => getReportPersonalDetailsParticipants(report, personalDetails, reportMetadata), + [report, personalDetails, reportMetadata], + ); + + const filterParticipants = useCallback( + (participant?: PersonalDetails) => { + if (!participant) { + return false; + } + const isInParticipants = chatParticipants.includes(participant.accountID); + const pendingChatMember = reportMetadata?.pendingChatMembers?.find((member) => member.accountID === participant.accountID.toString()); + + const isPendingDelete = pendingChatMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + return isInParticipants && !isPendingDelete; + }, + [chatParticipants, reportMetadata?.pendingChatMembers], + ); - const chatParticipants = getParticipantsList(report, personalDetails); + const [selectedMembers, setSelectedMembers] = useFilteredSelection(personalDetailsParticipants, filterParticipants); const pendingChatMembers = reportMetadata?.pendingChatMembers; const reportParticipants = report?.participants; @@ -240,7 +253,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { updateGroupChatMemberRoles(report.reportID, accountIDsToUpdate, role); setSelectedMembers([]); }, - [report, selectedMembers], + [report, selectedMembers, setSelectedMembers], ); /** diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 2344434f5833..9e7c0e3362e3 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -17,6 +17,7 @@ import SelectionListWithModal from '@components/SelectionListWithModal'; import Text from '@components/Text'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useFilteredSelection from '@hooks/useFilteredSelection'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -32,13 +33,14 @@ import type {RoomMembersNavigatorParamList} from '@libs/Navigation/types'; import {isPersonalDetailsReady, isSearchStringMatchUserDetails} from '@libs/OptionsListUtils'; import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; import {isPolicyEmployee as isPolicyEmployeeUtils, isUserPolicyAdmin} from '@libs/PolicyUtils'; -import {getParticipantsList, getReportName, isChatThread, isDefaultRoom, isPolicyExpenseChat as isPolicyExpenseChatUtils, isUserCreatedPolicyRoom} from '@libs/ReportUtils'; +import {getReportName, getReportPersonalDetailsParticipants, isChatThread, isDefaultRoom, isPolicyExpenseChat as isPolicyExpenseChatUtils, isUserCreatedPolicyRoom} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import {clearAddRoomMemberError, openRoomMembersPage, removeFromRoom} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type {PersonalDetails} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; import withReportOrNotFound from './home/report/withReportOrNotFound'; @@ -52,7 +54,6 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, {canBeMissing: false}); const currentUserAccountID = Number(session?.accountID); const {formatPhoneNumber, translate} = useLocalize(); - const [selectedMembers, setSelectedMembers] = useState([]); const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false); const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE, {canBeMissing: true}); const [searchValue, setSearchValue] = useState(''); @@ -62,6 +63,29 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { const isPolicyExpenseChat = useMemo(() => isPolicyExpenseChatUtils(report), [report]); const backTo = route.params.backTo; + const {chatParticipants: participants, personalDetailsParticipants} = useMemo( + () => getReportPersonalDetailsParticipants(report, personalDetails, reportMetadata, true), + [report, personalDetails, reportMetadata], + ); + + const shouldIncludeMember = useCallback( + (participant?: PersonalDetails) => { + if (!participant) { + return false; + } + const isInParticipants = participants.includes(participant.accountID); + const pendingChatMember = reportMetadata?.pendingChatMembers?.find((member) => member.accountID === participant.accountID.toString()); + + const isPendingDelete = pendingChatMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + + // Keep the member only if they're still in the room and not pending removal + return isInParticipants && !isPendingDelete; + }, + [participants, reportMetadata?.pendingChatMembers], + ); + + const [selectedMembers, setSelectedMembers] = useFilteredSelection(personalDetailsParticipants, shouldIncludeMember); + const isFocusedScreen = useIsFocused(); const {isOffline} = useNetwork(); @@ -71,13 +95,6 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); const canSelectMultiple = isSmallScreenWidth ? selectionMode?.isEnabled : true; - useEffect(() => { - if (isFocusedScreen) { - return; - } - setSelectedMembers([]); - }, [isFocusedScreen]); - /** * Get members for the current room */ @@ -125,16 +142,22 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { /** * Add user from the selectedMembers list */ - const addUser = useCallback((accountID: number) => { - setSelectedMembers((prevSelected) => [...prevSelected, accountID]); - }, []); + const addUser = useCallback( + (accountID: number) => { + setSelectedMembers((prevSelected) => [...prevSelected, accountID]); + }, + [setSelectedMembers], + ); /** * Remove user from the selectedEmployees list */ - const removeUser = useCallback((accountID: number) => { - setSelectedMembers((prevSelected) => prevSelected.filter((selected) => selected !== accountID)); - }, []); + const removeUser = useCallback( + (accountID: number) => { + setSelectedMembers((prevSelected) => prevSelected.filter((id) => id !== accountID)); + }, + [setSelectedMembers], + ); /** Toggle user from the selectedMembers list */ const toggleUser = useCallback( @@ -171,8 +194,6 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { } }; - const participants = useMemo(() => getParticipantsList(report, personalDetails, true), [report, personalDetails]); - /** Include the search bar when there are 8 or more active members in the selection list */ const shouldShowTextInput = useMemo(() => { // Get the active chat members by filtering out the pending members with delete action diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index fbfc13556748..f0b6f4a0a16b 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -22,6 +22,7 @@ import CustomListHeader from '@components/SelectionListWithModal/CustomListHeade import Text from '@components/Text'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useFilteredSelection from '@hooks/useFilteredSelection'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -51,7 +52,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; import {isPersonalDetailsReady, sortAlphabetically} from '@libs/OptionsListUtils'; -import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; +import {getAccountIDsByLogins, getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; import {getMemberAccountIDsForWorkspace, isDeletedPolicyEmployee, isExpensifyTeam, isPaidGroupPolicy, isPolicyAdmin as isPolicyAdminUtils} from '@libs/PolicyUtils'; import {getDisplayNameForParticipant} from '@libs/ReportUtils'; import {convertPolicyEmployeesToApprovalWorkflows, updateWorkflowDataOnApproverRemoval} from '@libs/WorkflowUtils'; @@ -61,7 +62,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetails, PersonalDetailsList, PolicyEmployeeList} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, PolicyEmployee, PolicyEmployeeList} from '@src/types/onyx'; import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; @@ -83,9 +84,20 @@ function invertObject(object: Record): Record { type MemberOption = Omit & {accountID: number}; function WorkspaceMembersPage({personalDetails, route, policy, currentUserPersonalDetails}: WorkspaceMembersPageProps) { - const policyMemberEmailsToAccountIDs = useMemo(() => getMemberAccountIDsForWorkspace(policy?.employeeList, true), [policy?.employeeList]); + const {policyMemberEmailsToAccountIDs, employeeListDetails} = useMemo(() => { + const emailsToAccountIDs = getMemberAccountIDsForWorkspace(policy?.employeeList, true); + const details = Object.keys(policy?.employeeList ?? {}).reduce>((acc, email) => { + const employee = policy?.employeeList?.[email]; + const accountID = emailsToAccountIDs[email]; + if (!employee) { + return acc; + } + acc[accountID] = employee; + return acc; + }, {}); + return {policyMemberEmailsToAccountIDs: emailsToAccountIDs, employeeListDetails: details}; + }, [policy?.employeeList]); const styles = useThemeStyles(); - const [selectedEmployees, setSelectedEmployees] = useState([]); const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false); const [errors, setErrors] = useState({}); const {isOffline} = useNetwork(); @@ -100,6 +112,23 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson const prevPersonalDetails = usePrevious(personalDetails); const {translate, formatPhoneNumber, preferredLocale} = useLocalize(); + const filterEmployees = useCallback( + (employee?: PolicyEmployee) => { + if (!employee?.email) { + return false; + } + const employeeAccountID = getAccountIDsByLogins([employee.email]).at(0); + if (!employeeAccountID) { + return false; + } + const isPendingDelete = employeeListDetails?.[employeeAccountID]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + return accountIDs.includes(employeeAccountID) && !isPendingDelete; + }, + [accountIDs, employeeListDetails], + ); + + const [selectedEmployees, setSelectedEmployees] = useFilteredSelection(employeeListDetails, filterEmployees); + // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply the correct modal type for the decision modal // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); @@ -180,7 +209,6 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson // useFocus would make getWorkspaceMembers get called twice on fresh login because policyEmployee is a dependency of getWorkspaceMembers. useEffect(() => { if (!isFocused) { - setSelectedEmployees([]); return; } getWorkspaceMembers(); @@ -195,11 +223,11 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson if (removeMembersConfirmModalVisible && !lodashIsEqual(accountIDs, prevAccountIDs)) { setRemoveMembersConfirmModalVisible(false); } - setSelectedEmployees((prevSelected) => { + setSelectedEmployees((prevSelectedEmployees) => { // Filter all personal details in order to use the elements needed for the current workspace const currentPersonalDetails = filterPersonalDetails(policy?.employeeList ?? {}, personalDetails); // We need to filter the previous selected employees by the new personal details, since unknown/new user id's change when transitioning from offline to online - const prevSelectedElements = prevSelected.map((id) => { + const prevSelectedElements = prevSelectedEmployees.map((id) => { const prevItem = prevPersonalDetails?.[id]; const res = Object.values(currentPersonalDetails).find((item) => prevItem?.login === item?.login); return res?.accountID ?? id; @@ -311,7 +339,7 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson setSelectedEmployees((prevSelected) => [...prevSelected, accountID]); validateSelection(); }, - [validateSelection], + [validateSelection, setSelectedEmployees], ); /** @@ -322,7 +350,7 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson setSelectedEmployees((prevSelected) => prevSelected.filter((id) => id !== accountID)); validateSelection(); }, - [validateSelection], + [validateSelection, setSelectedEmployees], ); /** @@ -532,8 +560,8 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson return policy?.employeeList?.[email]?.role !== role; }); - updateWorkspaceMembersRole(route.params.policyID, accountIDsToUpdate, role); setSelectedEmployees([]); + updateWorkspaceMembersRole(route.params.policyID, accountIDsToUpdate, role); }; const getBulkActionsButtonOptions = () => { diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 19a97cd73767..8b27b1268287 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -25,6 +25,7 @@ import TextLink from '@components/TextLink'; import useAutoTurnSelectionModeOffWhenHasNoActiveOption from '@hooks/useAutoTurnSelectionModeOffWhenHasNoActiveOption'; import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; import useEnvironment from '@hooks/useEnvironment'; +import useFilteredSelection from '@hooks/useFilteredSelection'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -53,7 +54,6 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {PolicyCategory} from '@src/types/onyx'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; type PolicyOption = ListItem & { /** Category name is used as a key for the selectedCategories state */ @@ -71,7 +71,6 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const threeDotsAnchorPosition = useThreeDotsAnchorPosition(styles.threeDotsPopoverOffsetNoCloseButton); const {translate} = useLocalize(); const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); - const [selectedCategories, setSelectedCategories] = useState>({}); const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false); const [deleteCategoriesConfirmModalVisible, setDeleteCategoriesConfirmModalVisible] = useState(false); const {environmentURL} = useEnvironment(); @@ -88,6 +87,9 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const currentConnectionName = getCurrentConnectionName(policy); const isQuickSettingsFlow = !!backTo; const {canUseLeftHandBar} = usePermissions(); + const filterCategories = useCallback((category: PolicyCategory | undefined) => !!category && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, []); + + const [selectedCategories, setSelectedCategories] = useFilteredSelection(policyCategories, filterCategories); const canSelectMultiple = isSmallScreenWidth ? selectionMode?.isEnabled : true; @@ -103,31 +105,11 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const cleanupSelectedOption = useCallback(() => setSelectedCategories({}), []); + const cleanupSelectedOption = useCallback(() => setSelectedCategories([]), [setSelectedCategories]); useCleanupSelectedOptions(cleanupSelectedOption); - useEffect(() => { - if (isEmptyObject(selectedCategories) || !canSelectMultiple) { - return; - } - - setSelectedCategories((prevSelectedCategories) => { - const keys = Object.keys(prevSelectedCategories); - const newSelectedCategories: Record = {}; - - for (const key of keys) { - if (policyCategories?.[key] && policyCategories[key].pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { - newSelectedCategories[key] = prevSelectedCategories[key]; - } - } - - return newSelectedCategories; - }); - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [policyCategories]); - useSearchBackPress({ - onClearSelection: () => setSelectedCategories({}), + onClearSelection: () => setSelectedCategories([]), onNavigationCallBack: () => Navigation.goBack(backTo), }); @@ -150,7 +132,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { acc.push({ text: value.name, keyForList: value.name, - isSelected: !!selectedCategories[value.name] && canSelectMultiple, + isSelected: !!selectedCategories.includes(value.name) && canSelectMultiple, isDisabled, pendingAction: value.pendingAction, errors: value.errors ?? undefined, @@ -170,20 +152,22 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { useAutoTurnSelectionModeOffWhenHasNoActiveOption(categoryList); - const toggleCategory = useCallback((category: PolicyOption) => { - setSelectedCategories((prev) => { - if (prev[category.keyForList]) { - const {[category.keyForList]: omittedCategory, ...newCategories} = prev; - return newCategories; - } - return {...prev, [category.keyForList]: true}; - }); - }, []); + const toggleCategory = useCallback( + (category: PolicyOption) => { + setSelectedCategories((prev) => { + if (prev.includes(category.keyForList)) { + return prev.filter((key) => key !== category.keyForList); + } + return [...prev, category.keyForList]; + }); + }, + [setSelectedCategories], + ); const toggleAllCategories = () => { const availableCategories = categoryList.filter((category) => category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - const someSelected = availableCategories.some((category) => selectedCategories[category.keyForList]); - setSelectedCategories(someSelected ? {} : Object.fromEntries(availableCategories.map((item) => [item.keyForList, true]))); + const someSelected = availableCategories.some((category) => selectedCategories.includes(category.keyForList)); + setSelectedCategories(someSelected ? [] : availableCategories.map((item) => item.keyForList)); }; const getCustomListHeader = () => { @@ -220,11 +204,9 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { clearCategoryErrors(policyId, item.keyForList); }; - const selectedCategoriesArray = Object.keys(selectedCategories).filter((key) => selectedCategories[key]); - const handleDeleteCategories = () => { - setSelectedCategories({}); - deleteWorkspaceCategories(policyId, selectedCategoriesArray); + setSelectedCategories([]); + deleteWorkspaceCategories(policyId, selectedCategories); setDeleteCategoriesConfirmModalVisible(false); }; @@ -232,19 +214,19 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const options: Array>> = []; const isThereAnyAccountingConnection = Object.keys(policy?.connections ?? {}).length !== 0; - if (isSmallScreenWidth ? canSelectMultiple : selectedCategoriesArray.length > 0) { + if (isSmallScreenWidth ? canSelectMultiple : selectedCategories.length > 0) { if (!isThereAnyAccountingConnection) { options.push({ icon: Expensicons.Trashcan, - text: translate(selectedCategoriesArray.length === 1 ? 'workspace.categories.deleteCategory' : 'workspace.categories.deleteCategories'), + text: translate(selectedCategories.length === 1 ? 'workspace.categories.deleteCategory' : 'workspace.categories.deleteCategories'), value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, onSelected: () => setDeleteCategoriesConfirmModalVisible(true), }); } - const enabledCategories = selectedCategoriesArray.filter((categoryName) => policyCategories?.[categoryName]?.enabled); + const enabledCategories = selectedCategories.filter((categoryName) => policyCategories?.[categoryName]?.enabled); if (enabledCategories.length > 0) { - const categoriesToDisable = selectedCategoriesArray + const categoriesToDisable = selectedCategories .filter((categoryName) => policyCategories?.[categoryName]?.enabled) .reduce>((acc, categoryName) => { acc[categoryName] = { @@ -259,15 +241,15 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { text: translate(enabledCategories.length === 1 ? 'workspace.categories.disableCategory' : 'workspace.categories.disableCategories'), value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, onSelected: () => { - setSelectedCategories({}); + setSelectedCategories([]); setWorkspaceCategoryEnabled(policyId, categoriesToDisable); }, }); } - const disabledCategories = selectedCategoriesArray.filter((categoryName) => !policyCategories?.[categoryName]?.enabled); + const disabledCategories = selectedCategories.filter((categoryName) => !policyCategories?.[categoryName]?.enabled); if (disabledCategories.length > 0) { - const categoriesToEnable = selectedCategoriesArray + const categoriesToEnable = selectedCategories .filter((categoryName) => !policyCategories?.[categoryName]?.enabled) .reduce>((acc, categoryName) => { acc[categoryName] = { @@ -281,7 +263,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { text: translate(disabledCategories.length === 1 ? 'workspace.categories.enableCategory' : 'workspace.categories.enableCategories'), value: CONST.POLICY.BULK_ACTION_TYPES.ENABLE, onSelected: () => { - setSelectedCategories({}); + setSelectedCategories([]); setWorkspaceCategoryEnabled(policyId, categoriesToEnable); }, }); @@ -293,11 +275,11 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { shouldAlwaysShowDropdownMenu pressOnEnter buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {count: selectedCategoriesArray.length})} + customText={translate('workspace.common.selected', {count: selectedCategories.length})} options={options} isSplitButton={false} style={[shouldUseNarrowLayout && styles.flexGrow1, shouldUseNarrowLayout && styles.mb3]} - isDisabled={!selectedCategoriesArray.length} + isDisabled={!selectedCategories.length} testID={`${WorkspaceCategoriesPage.displayName}-header-dropdown-menu-button`} /> ); @@ -331,7 +313,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { return; } - setSelectedCategories({}); + setSelectedCategories([]); }, [setSelectedCategories, selectionMode?.isEnabled]); const hasVisibleCategories = categoryList.some((category) => category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline); @@ -417,7 +399,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { shouldUseHeadlineHeader={!selectionModeHeader} onBackButtonPress={() => { if (selectionMode?.isEnabled) { - setSelectedCategories({}); + setSelectedCategories([]); turnOffMobileSelectionMode(); return; } @@ -440,8 +422,8 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { isVisible={deleteCategoriesConfirmModalVisible} onConfirm={handleDeleteCategories} onCancel={() => setDeleteCategoriesConfirmModalVisible(false)} - title={translate(selectedCategoriesArray.length === 1 ? 'workspace.categories.deleteCategory' : 'workspace.categories.deleteCategories')} - prompt={translate(selectedCategoriesArray.length === 1 ? 'workspace.categories.deleteCategoryPrompt' : 'workspace.categories.deleteCategoriesPrompt')} + title={translate(selectedCategories.length === 1 ? 'workspace.categories.deleteCategory' : 'workspace.categories.deleteCategories')} + prompt={translate(selectedCategories.length === 1 ? 'workspace.categories.deleteCategoryPrompt' : 'workspace.categories.deleteCategoriesPrompt')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 5adbb3a092ea..1e5710ed9404 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -1,4 +1,3 @@ -import {useIsFocused} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import Button from '@components/Button'; @@ -14,6 +13,7 @@ import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; import Switch from '@components/Switch'; import Text from '@components/Text'; +import useFilteredSelection from '@hooks/useFilteredSelection'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -57,10 +57,8 @@ function PolicyDistanceRatesPage({ const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const [selectedDistanceRates, setSelectedDistanceRates] = useState([]); const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); - const isFocused = useIsFocused(); const policy = usePolicy(policyID); const {selectionMode} = useMobileSelectionMode(); @@ -68,11 +66,29 @@ function PolicyDistanceRatesPage({ const customUnit = getDistanceRateCustomUnit(policy); const customUnitRates: Record = useMemo(() => customUnit?.rates ?? {}, [customUnit]); - // Filter out rates that will be deleted - const allSelectableRates = useMemo(() => Object.values(customUnitRates).filter((rate) => rate.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE), [customUnitRates]); + + const selectableRates = useMemo( + () => + Object.values(customUnitRates).reduce>((acc, rate) => { + acc[rate.customUnitRateID] = rate; + return acc; + }, {}), + [customUnitRates], + ); + + const filterRate = useCallback( + (rate?: Rate) => !!rate && !!customUnitRates?.[rate.customUnitRateID] && customUnitRates?.[rate.customUnitRateID]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + [customUnitRates], + ); + + const [selectedDistanceRates, setSelectedDistanceRates] = useFilteredSelection(selectableRates, filterRate); + const canDisableOrDeleteSelectedRates = useMemo( - () => allSelectableRates.filter((rate: Rate) => !selectedDistanceRates.some((selectedRate) => selectedRate.customUnitRateID === rate.customUnitRateID)).some((rate) => rate.enabled), - [allSelectableRates, selectedDistanceRates], + () => + Object.keys(selectableRates) + .filter((rateID) => !selectedDistanceRates.includes(rateID)) + .some((rateID) => selectableRates[rateID].enabled), + [selectableRates, selectedDistanceRates], ); const fetchDistanceRates = useCallback(() => { @@ -103,13 +119,6 @@ function PolicyDistanceRatesPage({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - if (isFocused) { - return; - } - setSelectedDistanceRates([]); - }, [isFocused]); - useSearchBackPress({ onClearSelection: () => setSelectedDistanceRates([]), onNavigationCallBack: () => Navigation.goBack(), @@ -127,15 +136,6 @@ function PolicyDistanceRatesPage({ ); if (!rate?.enabled || canDisableOrDeleteRate) { - setSelectedDistanceRates((prevSelectedRates) => - prevSelectedRates.map((selectedRate) => { - if (selectedRate.customUnitRateID === rateID) { - return {...selectedRate, enabled: value}; - } - return selectedRate; - }), - ); - setPolicyDistanceRatesEnabled(policyID, customUnit, [{...rate, enabled: value}]); } else { setIsWarningModalVisible(true); @@ -154,7 +154,7 @@ function PolicyDistanceRatesPage({ `common.${customUnit?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}`, )}`, keyForList: value.customUnitRateID, - isSelected: selectedDistanceRates.find((rate) => rate.customUnitRateID === value.customUnitRateID) !== undefined && canSelectMultiple, + isSelected: selectedDistanceRates.includes(value.customUnitRateID) && canSelectMultiple, isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, pendingAction: value.pendingAction ?? @@ -197,7 +197,10 @@ function PolicyDistanceRatesPage({ setPolicyDistanceRatesEnabled( policyID, customUnit, - selectedDistanceRates.filter((rate) => rate.enabled).map((rate) => ({...rate, enabled: false})), + selectedDistanceRates + .map((rateID) => selectableRates[rateID]) + .filter((rate) => rate.enabled) + .map((rate) => ({...rate, enabled: false})), ); setSelectedDistanceRates([]); }; @@ -210,7 +213,10 @@ function PolicyDistanceRatesPage({ setPolicyDistanceRatesEnabled( policyID, customUnit, - selectedDistanceRates.filter((rate) => !rate.enabled).map((rate) => ({...rate, enabled: true})), + selectedDistanceRates + .map((rateID) => selectableRates[rateID]) + .filter((rate) => !rate.enabled) + .map((rate) => ({...rate, enabled: true})), ); setSelectedDistanceRates([]); }; @@ -220,28 +226,29 @@ function PolicyDistanceRatesPage({ return; } - deletePolicyDistanceRates( - policyID, - customUnit, - selectedDistanceRates.map((rate) => rate.customUnitRateID), - ); + deletePolicyDistanceRates(policyID, customUnit, selectedDistanceRates); setSelectedDistanceRates([]); setIsDeleteModalVisible(false); }; const toggleRate = (rate: RateForList) => { - if (selectedDistanceRates.find((selectedRate) => selectedRate.customUnitRateID === rate.value) !== undefined) { - setSelectedDistanceRates((prev) => prev.filter((selectedRate) => selectedRate.customUnitRateID !== rate.value)); - } else { - setSelectedDistanceRates((prev) => [...prev, customUnitRates[rate.value]]); - } + setSelectedDistanceRates((prevSelectedRates) => { + if (prevSelectedRates.includes(rate.value)) { + return prevSelectedRates.filter((selectedRate) => selectedRate !== rate.value); + } + return [...prevSelectedRates, rate.value]; + }); }; const toggleAllRates = () => { if (selectedDistanceRates.length > 0) { setSelectedDistanceRates([]); } else { - setSelectedDistanceRates([...allSelectableRates]); + setSelectedDistanceRates( + Object.entries(selectableRates) + .filter(([, rate]) => rate.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) + .map(([key]) => key), + ); } }; @@ -265,7 +272,7 @@ function PolicyDistanceRatesPage({ }, ]; - const enabledRates = selectedDistanceRates.filter((rate) => rate.enabled); + const enabledRates = selectedDistanceRates.filter((rateID) => selectableRates[rateID].enabled); if (enabledRates.length > 0) { options.push({ text: translate('workspace.distanceRates.disableRates', {count: enabledRates.length}), @@ -275,7 +282,7 @@ function PolicyDistanceRatesPage({ }); } - const disabledRates = selectedDistanceRates.filter((rate) => !rate.enabled); + const disabledRates = selectedDistanceRates.filter((rateID) => !selectableRates[rateID].enabled); if (disabledRates.length > 0) { options.push({ text: translate('workspace.distanceRates.enableRates', {count: disabledRates.length}), diff --git a/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx b/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx index 9c84adbafcc7..c6e18d27a55b 100644 --- a/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx +++ b/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx @@ -71,7 +71,7 @@ function ReportFieldsListValuesPage({ // See https://github.com/Expensify/App/issues/48724 for more details // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); - const [formDraft] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT); + const [formDraft] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT, {canBeMissing: true}); const {selectionMode} = useMobileSelectionMode(); const [selectedValues, setSelectedValues] = useState>({}); @@ -138,7 +138,7 @@ function ReportFieldsListValuesPage({ }, [canSelectMultiple, disabledListValues, listValues, selectedValues, translate, updateReportFieldListValueEnabled]); const shouldShowEmptyState = Object.values(listValues ?? {}).length <= 0; - const selectedValuesArray = Object.keys(selectedValues).filter((key) => selectedValues[key]); + const selectedValuesArray = Object.keys(selectedValues).filter((key) => selectedValues[key] && listValues.includes(key)); const toggleValue = (valueItem: ValueListItem) => { setSelectedValues((prev) => ({ @@ -179,8 +179,6 @@ function ReportFieldsListValuesPage({ } Navigation.navigate(ROUTES.WORKSPACE_REPORT_FIELDS_VALUE_SETTINGS.getRoute(policyID, valueItem.index, reportFieldID)); - - setSelectedValues({}); }; const getCustomListHeader = () => { diff --git a/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx b/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx index e385646a1e98..294ada570e37 100644 --- a/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx +++ b/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx @@ -1,6 +1,6 @@ -import {useFocusEffect, useIsFocused} from '@react-navigation/native'; +import {useFocusEffect} from '@react-navigation/native'; import {Str} from 'expensify-common'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -24,6 +24,7 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useAutoTurnSelectionModeOffWhenHasNoActiveOption from '@hooks/useAutoTurnSelectionModeOffWhenHasNoActiveOption'; import useEnvironment from '@hooks/useEnvironment'; +import useFilteredSelection from '@hooks/useFilteredSelection'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -48,7 +49,8 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {PolicyReportField} from '@src/types/onyx/Policy'; +import type {PolicyReportField} from '@src/types/onyx'; +import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; type ReportFieldForList = ListItem & { @@ -70,18 +72,9 @@ function WorkspaceReportFieldsPage({ const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const isFocused = useIsFocused(); const {environmentURL} = useEnvironment(); const policy = usePolicy(policyID); const {selectionMode} = useMobileSelectionMode(); - const filteredPolicyFieldList = useMemo(() => { - if (!policy?.fieldList) { - return {}; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - return Object.fromEntries(Object.entries(policy.fieldList).filter(([_, value]) => value.fieldID !== 'text_title')); - }, [policy]); - const [selectedReportFields, setSelectedReportFields] = useState([]); const [deleteReportFieldsConfirmModalVisible, setDeleteReportFieldsConfirmModalVisible] = useState(false); const hasReportAccountingConnections = hasAccountingConnections(policy); const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`, {canBeMissing: true}); @@ -93,23 +86,36 @@ function WorkspaceReportFieldsPage({ const canSelectMultiple = !hasReportAccountingConnections && (isSmallScreenWidth ? selectionMode?.isEnabled : true); + const selectionFieldList = useMemo(() => { + if (!policy?.fieldList) { + return {}; + } + return Object.values(policy.fieldList).reduce>>((acc, reportField) => { + if (reportField.fieldID === CONST.POLICY.FIELDS.FIELD_LIST_TITLE) { + return acc; + } + const reportFieldKey = getReportFieldKey(reportField.fieldID); + acc[reportFieldKey] = reportField; + return acc; + }, {}); + }, [policy]); + + const filterReportFields = useCallback((reportField: OnyxValueWithOfflineFeedback | undefined) => { + return !!reportField && reportField.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + }, []); + + const [selectedReportFields, setSelectedReportFields] = useFilteredSelection(selectionFieldList, filterReportFields); + const fetchReportFields = useCallback(() => { openPolicyReportFieldsPage(policyID); }, [policyID]); const {isOffline} = useNetwork({onReconnect: fetchReportFields}); - const hasVisibleReportField = Object.values(filteredPolicyFieldList).some((reportField) => reportField.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline); + const hasVisibleReportField = Object.values(selectionFieldList).some((reportField) => reportField.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline); useFocusEffect(fetchReportFields); - useEffect(() => { - if (isFocused) { - return; - } - setSelectedReportFields([]); - }, [isFocused]); - const reportFieldsSections = useMemo(() => { if (!policy) { return [{data: [], isDisabled: true}]; @@ -117,7 +123,7 @@ function WorkspaceReportFieldsPage({ return [ { - data: Object.values(filteredPolicyFieldList) + data: Object.values(selectionFieldList) .sort((a, b) => localeCompare(a.name, b.name)) .map((reportField) => ({ value: reportField.name, @@ -125,7 +131,7 @@ function WorkspaceReportFieldsPage({ keyForList: String(reportField.fieldID), orderWeight: reportField.orderWeight, pendingAction: reportField.pendingAction, - isSelected: selectedReportFields.find((selectedReportField) => selectedReportField.name === reportField.name) !== undefined && canSelectMultiple, + isSelected: selectedReportFields.includes(getReportFieldKey(reportField.fieldID)) && canSelectMultiple, isDisabled: reportField.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, text: reportField.name, rightElement: , @@ -133,21 +139,23 @@ function WorkspaceReportFieldsPage({ isDisabled: false, }, ]; - }, [filteredPolicyFieldList, policy, selectedReportFields, canSelectMultiple, translate]); + }, [selectionFieldList, policy, canSelectMultiple, translate, selectedReportFields]); useAutoTurnSelectionModeOffWhenHasNoActiveOption(reportFieldsSections.at(0)?.data ?? ([] as ListItem[])); const updateSelectedReportFields = (item: ReportFieldForList) => { const fieldKey = getReportFieldKey(item.fieldID); - const updatedReportFields = selectedReportFields.find((selectedReportField) => selectedReportField.name === item.value) - ? selectedReportFields.filter((selectedReportField) => selectedReportField.name !== item.value) - : [...selectedReportFields, filteredPolicyFieldList[fieldKey]]; - setSelectedReportFields(updatedReportFields); + setSelectedReportFields((prevSelectedReportFields) => { + if (prevSelectedReportFields.includes(fieldKey)) { + return prevSelectedReportFields.filter((selectedField) => selectedField !== fieldKey); + } + return [...prevSelectedReportFields, fieldKey]; + }); }; const toggleAllReportFields = () => { - const availableReportFields = Object.values(filteredPolicyFieldList).filter((reportField) => reportField.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - setSelectedReportFields(selectedReportFields.length > 0 ? [] : availableReportFields); + const availableReportFields = Object.values(selectionFieldList).filter((reportField) => reportField.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + setSelectedReportFields(selectedReportFields.length > 0 ? [] : Object.keys(availableReportFields)); }; const navigateToReportFieldsSettings = (reportField: ReportFieldForList) => { @@ -155,15 +163,14 @@ function WorkspaceReportFieldsPage({ }; const handleDeleteReportFields = () => { - const reportFieldKeys = selectedReportFields.map((selectedReportField) => getReportFieldKey(selectedReportField.fieldID)); setSelectedReportFields([]); - deleteReportFields(policyID, reportFieldKeys); + deleteReportFields(policyID, selectedReportFields); setDeleteReportFieldsConfirmModalVisible(false); }; const isLoading = !isOffline && policy === undefined; const shouldShowEmptyState = - !Object.values(filteredPolicyFieldList).some((reportField) => reportField.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline) && !isLoading; + !Object.values(selectionFieldList).some((reportField) => reportField.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline) && !isLoading; const getHeaderButtons = () => { const options: Array>> = []; diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index 831260db3ace..dcb8446d8436 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -64,7 +64,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { const policyID = route.params.policyID; const backTo = route.params.backTo; const policy = usePolicy(policyID); - const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`); + const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}); const {selectionMode} = useMobileSelectionMode(); const currentTagListName = useMemo(() => getTagListName(policyTags, route.params.orderWeight), [policyTags, route.params.orderWeight]); const currentPolicyTag = policyTags?.[currentTagListName]; @@ -81,10 +81,8 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { if (isFocused) { return; } - setSelectedTags({}); return () => { - setSelectedTags({}); turnOffMobileSelectionMode(); }; }, [isFocused]);