From d3d480545fdea6d51ded5866dd407881857bb04a Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 3 Apr 2024 14:53:31 +0200 Subject: [PATCH 01/22] Remove policyMembersDraft and policyMembers part-1 --- src/ONYXKEYS.ts | 2 - src/components/Indicator.tsx | 13 +- src/libs/PolicyUtils.ts | 18 +- src/libs/WorkspacesSettingsUtils.ts | 23 +-- src/libs/actions/Policy.ts | 188 +++++++----------- src/libs/actions/TeachersUnite.ts | 24 +-- src/pages/home/sidebar/AllSettingsScreen.tsx | 12 +- src/pages/settings/InitialSettingsPage.tsx | 12 +- src/pages/workspace/WorkspaceInitialPage.tsx | 4 +- src/pages/workspace/WorkspacesListPage.tsx | 14 +- src/pages/workspace/withPolicy.tsx | 5 - .../withPolicyAndFullscreenLoading.tsx | 2 - 12 files changed, 114 insertions(+), 203 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index c134d2a65db2..3ff71362af35 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -309,7 +309,6 @@ const ONYXKEYS = { POLICY: 'policy_', POLICY_MEMBERS: 'policyMembers_', POLICY_DRAFTS: 'policyDrafts_', - POLICY_MEMBERS_DRAFTS: 'policyMembersDrafts_', POLICY_JOIN_MEMBER: 'policyJoinMember_', POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', @@ -513,7 +512,6 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs; diff --git a/src/components/Indicator.tsx b/src/components/Indicator.tsx index e3d226a17999..8830681bc55f 100644 --- a/src/components/Indicator.tsx +++ b/src/components/Indicator.tsx @@ -8,14 +8,11 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import * as UserUtils from '@libs/UserUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {BankAccountList, FundList, LoginList, Policy, PolicyMembers, ReimbursementAccount, UserWallet, WalletTerms} from '@src/types/onyx'; +import type {BankAccountList, FundList, LoginList, Policy, ReimbursementAccount, UserWallet, WalletTerms} from '@src/types/onyx'; type CheckingMethod = () => boolean; type IndicatorOnyxProps = { - /** The employee list of all policies (coming from Onyx) */ - allPolicyMembers: OnyxCollection; - /** All the user's policies (from Onyx via withFullPolicy) */ policies: OnyxCollection; @@ -40,14 +37,13 @@ type IndicatorOnyxProps = { type IndicatorProps = IndicatorOnyxProps; -function Indicator({reimbursementAccount, allPolicyMembers, policies, bankAccountList, fundList, userWallet, walletTerms, loginList}: IndicatorOnyxProps) { +function Indicator({reimbursementAccount, policies, bankAccountList, fundList, userWallet, walletTerms, loginList}: IndicatorOnyxProps) { const theme = useTheme(); const styles = useThemeStyles(); // If a policy was just deleted from Onyx, then Onyx will pass a null value to the props, and // those should be cleaned out before doing any error checking const cleanPolicies = Object.fromEntries(Object.entries(policies ?? {}).filter(([, policy]) => policy?.id)); - const cleanAllPolicyMembers = Object.fromEntries(Object.entries(allPolicyMembers ?? {}).filter(([, policyMembers]) => !!policyMembers)); // All of the error & info-checking methods are put into an array. This is so that using _.some() will return // early as soon as the first error / info condition is returned. This makes the checks very efficient since @@ -57,7 +53,7 @@ function Indicator({reimbursementAccount, allPolicyMembers, policies, bankAccoun () => PaymentMethods.hasPaymentMethodError(bankAccountList, fundList), () => Object.values(cleanPolicies).some(PolicyUtils.hasPolicyError), () => Object.values(cleanPolicies).some(PolicyUtils.hasCustomUnitsError), - () => Object.values(cleanAllPolicyMembers).some(PolicyUtils.hasPolicyMemberError), + () => Object.values(cleanPolicies).some(PolicyUtils.hasEmployeeListError), () => Object.keys(reimbursementAccount?.errors ?? {}).length > 0, () => !!loginList && UserUtils.hasLoginListError(loginList), @@ -77,9 +73,6 @@ function Indicator({reimbursementAccount, allPolicyMembers, policies, bankAccoun Indicator.displayName = 'Indicator'; export default withOnyx({ - allPolicyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, policies: { key: ONYXKEYS.COLLECTION.POLICY, }, diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 0a8437a5afaf..7ff74f8af98e 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -26,10 +26,9 @@ function getActivePolicies(policies: OnyxCollection): Policy[] | undefin /** * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. - * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...} */ -function hasPolicyMemberError(policyMembers: OnyxEntry): boolean { - return Object.values(policyMembers ?? {}).some((member) => Object.keys(member?.errors ?? {}).length > 0); +function hasEmployeeListError(policy: OnyxEntry): boolean { + return Object.values(policy?.employeeList ?? {}).some((employee) => Object.keys(employee?.errors ?? {}).length > 0); } /** @@ -90,12 +89,11 @@ function getUnitRateValue(toLocaleDigit: (arg: string) => string, customUnitRate /** * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. */ -function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, policyMembersCollection: OnyxCollection): ValueOf | undefined { - const policyMembers = policyMembersCollection?.[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy?.id}`] ?? {}; - if (hasPolicyMemberError(policyMembers) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { - return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; - } - return undefined; +function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf | undefined { + if (hasEmployeeListError(policy) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { + return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; + } + return undefined; } /** @@ -316,7 +314,7 @@ function getPolicyIDFromNavigationState() { export { getActivePolicies, hasAccountingConnections, - hasPolicyMemberError, + hasEmployeeListError, hasPolicyError, hasPolicyErrorFields, hasCustomUnitsError, diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index 30820fc0c48b..ef35421efb90 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -4,12 +4,12 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Policy, PolicyMembers, ReimbursementAccount, Report} from '@src/types/onyx'; +import type {Policy, ReimbursementAccount, Report} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import * as CurrencyUtils from './CurrencyUtils'; import type {Phrase, PhraseParameters} from './Localize'; import * as OptionsListUtils from './OptionsListUtils'; -import {hasCustomUnitsError, hasPolicyError, hasPolicyMemberError, hasTaxRateError} from './PolicyUtils'; +import {hasCustomUnitsError, hasEmployeeListError, hasPolicyError, hasTaxRateError} from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; @@ -33,16 +33,6 @@ Onyx.connect({ callback: (value) => (allPolicies = value), }); -let allPolicyMembers: OnyxCollection; - -Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - waitForCollectionCallback: true, - callback: (val) => { - allPolicyMembers = val; - }, -}); - let reimbursementAccount: OnyxEntry; Onyx.connect({ @@ -75,17 +65,16 @@ const getBrickRoadForPolicy = (report: Report): BrickRoad => { return shouldShowGreenDotIndicator ? CONST.BRICK_ROAD_INDICATOR_STATUS.INFO : undefined; }; -function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection, policyMembers: OnyxCollection) { +function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection) { // When attempting to open a policy with an invalid policyID, the policy collection is updated to include policy objects with error information. // Only policies displayed on the policy list page should be verified. Otherwise, the user will encounter an RBR unrelated to any policies on the list. const cleanPolicies = Object.fromEntries(Object.entries(policies ?? {}).filter(([, policy]) => policy?.id)); - const cleanAllPolicyMembers = Object.fromEntries(Object.entries(policyMembers ?? {}).filter(([, policyMemberValues]) => !!policyMemberValues)); const errorCheckingMethods: CheckingMethod[] = [ () => Object.values(cleanPolicies).some(hasPolicyError), () => Object.values(cleanPolicies).some(hasCustomUnitsError), () => Object.values(cleanPolicies).some(hasTaxRateError), - () => Object.values(cleanAllPolicyMembers).some(hasPolicyMemberError), + () => Object.values(cleanPolicies).some(hasEmployeeListError), () => Object.keys(reimbursementAccount?.errors ?? {}).length > 0, ]; @@ -93,7 +82,7 @@ function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection, policyM } function hasWorkspaceSettingsRBR(policy: Policy) { - const policyMemberError = allPolicyMembers ? hasPolicyMemberError(allPolicyMembers[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy.id}`]) : false; + const policyMemberError = hasEmployeeListError(policy); const taxRateError = hasTaxRateError(policy); return Object.keys(reimbursementAccount?.errors ?? {}).length > 0 || hasPolicyError(policy) || hasCustomUnitsError(policy) || policyMemberError || taxRateError; @@ -130,7 +119,7 @@ function getChatTabBrickRoad(policyID?: string): BrickRoad | undefined { function checkIfWorkspaceSettingsTabHasRBR(policyID?: string) { if (!policyID) { - return hasGlobalWorkspaceSettingsRBR(allPolicies, allPolicyMembers); + return hasGlobalWorkspaceSettingsRBR(allPolicies); } const policy = allPolicies ? allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] : null; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 3c34e823ac9a..356eb440e3ca 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -180,15 +180,6 @@ Onyx.connect({ callback: (value) => (allReports = value), }); -let allPolicyMembers: OnyxCollection; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - waitForCollectionCallback: true, - callback: (val) => { - allPolicyMembers = val; - }, -}); - let lastAccessedWorkspacePolicyID: OnyxEntry = null; Onyx.connect({ key: ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, @@ -820,9 +811,10 @@ function removeMembers(accountIDs: number[], policyID: string) { return; } - const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const; + const policyKey = `${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const; const policy = ReportUtils.getPolicy(policyID); const workspaceChats = ReportUtils.getWorkspaceChats(policyID, accountIDs); + const emailList = accountIDs.map((accountID) => allPersonalDetails?.[accountID]?.login).filter((login) => !!login) as string[]; const optimisticClosedReportActions = workspaceChats.map(() => ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy.name, CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY)); const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policyID, accountIDs); @@ -830,17 +822,17 @@ function removeMembers(accountIDs: number[], policyID: string) { const optimisticMembersState: OnyxCollection = {}; const successMembersState: OnyxCollection = {}; const failureMembersState: OnyxCollection = {}; - accountIDs.forEach((accountID) => { - optimisticMembersState[accountID] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}; - successMembersState[accountID] = null; - failureMembersState[accountID] = {errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericRemove')}; + emailList.forEach((email) => { + optimisticMembersState[email] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}; + successMembersState[email] = null; + failureMembersState[email] = {errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericRemove')}; }); const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, - value: optimisticMembersState, + key: policyKey, + value: {employeeList: optimisticMembersState}, }, ...announceRoomMembers.onyxOptimisticData, ]; @@ -848,8 +840,8 @@ function removeMembers(accountIDs: number[], policyID: string) { const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, - value: successMembersState, + key: policyKey, + value: {employeeList: successMembersState}, }, ...announceRoomMembers.onyxSuccessData, ]; @@ -857,8 +849,8 @@ function removeMembers(accountIDs: number[], policyID: string) { const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, - value: failureMembersState, + key: policyKey, + value: {employeeList: failureMembersState}, }, ...announceRoomMembers.onyxFailureData, ]; @@ -905,9 +897,8 @@ function removeMembers(accountIDs: number[], policyID: string) { // If we delete all these logins then we should clear the informative messages since they are no longer relevant. if (!isEmptyObject(policy?.primaryLoginsInvited ?? {})) { // Take the current policy members and remove them optimistically - const policyMemberAccountIDs = Object.keys(allPolicyMembers?.[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`] ?? {}).map((accountID) => Number(accountID)); - const remainingMemberAccountIDs = policyMemberAccountIDs.filter((accountID) => !accountIDs.includes(accountID)); - const remainingLogins: string[] = PersonalDetailsUtils.getLoginsByAccountIDs(remainingMemberAccountIDs); + const employeeListEmails = Object.keys(allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.employeeList ?? {}); + const remainingLogins = employeeListEmails.filter((email) => !emailList.includes(email)); const invitedPrimaryToSecondaryLogins: Record = {}; if (policy.primaryLoginsInvited) { @@ -918,7 +909,7 @@ function removeMembers(accountIDs: number[], policyID: string) { if (!remainingLogins.some((remainingLogin) => !!invitedPrimaryToSecondaryLogins[remainingLogin])) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, + key: policyKey, value: { primaryLoginsInvited: null, }, @@ -949,7 +940,7 @@ function removeMembers(accountIDs: number[], policyID: string) { }); const params: DeleteMembersFromWorkspaceParams = { - emailList: accountIDs.map((accountID) => allPersonalDetails?.[accountID]?.login).join(','), + emailList: emailList.join(','), policyID, }; @@ -957,7 +948,7 @@ function removeMembers(accountIDs: number[], policyID: string) { } function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newRole: typeof CONST.POLICY.ROLE.ADMIN | typeof CONST.POLICY.ROLE.USER) { - const previousPolicyMembers = {...allPolicyMembers}; + const previousEmployeeList = {...allPolicies?.[policyID]?.employeeList}; const memberRoles: WorkspaceMembersRoleData[] = accountIDs.reduce((result: WorkspaceMembersRoleData[], accountID: number) => { if (!allPersonalDetails?.[accountID]?.login) { return result; @@ -975,13 +966,15 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - ...memberRoles.reduce((member: Record, current) => { - // eslint-disable-next-line no-param-reassign - member[current.accountID] = {role: current?.role, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}; - return member; - }, {}), + employeeList: { + ...memberRoles.reduce((member: Record, current) => { + // eslint-disable-next-line no-param-reassign + member[current.email] = {role: current?.role, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}; + return member; + }, {}), + }, errors: null, }, }, @@ -990,13 +983,15 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - ...memberRoles.reduce((member: Record, current) => { - // eslint-disable-next-line no-param-reassign - member[current.accountID] = {role: current?.role, pendingAction: null}; - return member; - }, {}), + employeeList: { + ...memberRoles.reduce((member: Record, current) => { + // eslint-disable-next-line no-param-reassign + member[current.email] = {role: current?.role, pendingAction: null}; + return member; + }, {}), + }, errors: null, }, }, @@ -1005,9 +1000,9 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - ...(previousPolicyMembers[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`] as Record), + employeeList: previousEmployeeList, errors: ErrorUtils.getMicroSecondOnyxError('workspace.editor.genericFailureMessage'), }, }, @@ -1277,7 +1272,7 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I * Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details */ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, welcomeNote: string, policyID: string) { - const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const; + const onyxKey = `${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const; const logins = Object.keys(invitedEmailsToAccountIDs).map((memberLogin) => PhoneNumber.addSMSDomainIfPhoneNumber(memberLogin)); const accountIDs = Object.values(invitedEmailsToAccountIDs); @@ -1289,10 +1284,12 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const membersChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs); const optimisticMembersState: OnyxCollection = {}; + const successMembersState: OnyxCollection = {}; const failureMembersState: OnyxCollection = {}; - accountIDs.forEach((accountID) => { - optimisticMembersState[accountID] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}; - failureMembersState[accountID] = { + Object.keys(invitedEmailsToAccountIDs).forEach((email) => { + optimisticMembersState[email] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}; + successMembersState[email] = {pendingAction: null}; + failureMembersState[email] = { errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericAdd'), }; }); @@ -1300,10 +1297,12 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, + key: onyxKey, // Convert to object with each key containing {pendingAction: ‘add’} - value: optimisticMembersState, + value: { + employeeList: optimisticMembersState, + }, }, ...newPersonalDetailsOnyxData.optimisticData, ...membersChats.onyxOptimisticData, @@ -1313,20 +1312,10 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, - - // Convert to object with each key clearing pendingAction, when it is an existing account. - // Remove the object, when it is a newly created account. - value: accountIDs.reduce((accountIDsWithClearedPendingAction, accountID) => { - let value = null; - const accountAlreadyExists = !isEmptyObject(allPersonalDetails?.[accountID]); - - if (accountAlreadyExists) { - value = {pendingAction: null, errors: null}; - } - - return {...accountIDsWithClearedPendingAction, [accountID]: value}; - }, {}), + key: onyxKey, + value: { + employeeList: successMembersState, + }, }, ...newPersonalDetailsOnyxData.finallyData, ...membersChats.onyxSuccessData, @@ -1336,7 +1325,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, + key: onyxKey, // Convert to object with each key containing the error. We don’t // need to remove the members since that is handled by onClose of OfflineWithFeedback. @@ -1938,15 +1927,11 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, customUnits, makeMeAdmin, - }, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS}${policyID}`, - value: { - [sessionAccountID]: { - role: CONST.POLICY.ROLE.ADMIN, - errors: {}, + employeeList: { + [sessionEmail]: { + role: CONST.POLICY.ROLE.ADMIN, + errors: {}, + }, }, }, }, @@ -2003,15 +1988,11 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName areWorkflowsEnabled: false, areReportFieldsEnabled: false, areConnectionsEnabled: false, - }, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, - value: { - [sessionAccountID]: { - role: CONST.POLICY.ROLE.ADMIN, - errors: {}, + employeeList: { + [sessionEmail]: { + role: CONST.POLICY.ROLE.ADMIN, + errors: {}, + }, }, }, }, @@ -2065,11 +2046,6 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName key: `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`, value: null, }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS}${policyID}`, - value: null, - }, ]; const successData: OnyxUpdate[] = [ @@ -2140,9 +2116,9 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName const failureData: OnyxUpdate[] = [ { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, - value: null, + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: {employeeList: null}, }, { onyxMethod: Onyx.METHOD.SET, @@ -2491,6 +2467,16 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string areWorkflowsEnabled: false, areReportFieldsEnabled: false, areConnectionsEnabled: false, + employeeList: { + [sessionEmail]: { + role: CONST.POLICY.ROLE.ADMIN, + errors: {}, + }, + [employeeEmail]: { + role: CONST.POLICY.ROLE.USER, + errors: {}, + }, + }, }; const optimisticData: OnyxUpdate[] = [ @@ -2499,20 +2485,6 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: newWorkspace, }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, - value: { - [sessionAccountID]: { - role: CONST.POLICY.ROLE.ADMIN, - errors: {}, - }, - [employeeAccountID]: { - role: CONST.POLICY.ROLE.USER, - errors: {}, - }, - }, - }, { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, @@ -2568,13 +2540,6 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string pendingAction: null, }, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS}${policyID}`, - value: { - pendingAction: null, - }, - }, ...employeeWorkspaceChat.onyxOptimisticData, ]; @@ -2645,13 +2610,6 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string ]; const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, - value: { - pendingAction: null, - }, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, diff --git a/src/libs/actions/TeachersUnite.ts b/src/libs/actions/TeachersUnite.ts index 1f8b32724bd4..36fd9d340aeb 100644 --- a/src/libs/actions/TeachersUnite.ts +++ b/src/libs/actions/TeachersUnite.ts @@ -96,17 +96,13 @@ function addSchoolPrincipal(firstName: string, partnerUserID: string, lastName: role: CONST.POLICY.ROLE.USER, owner: sessionEmail, outputCurrency: allPersonalDetails?.[sessionAccountID]?.localCurrencyCode ?? CONST.CURRENCY.USD, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, - value: { - [sessionAccountID]: { - role: CONST.POLICY.ROLE.USER, - errors: {}, + employeeList: { + [sessionEmail]: { + role: CONST.POLICY.ROLE.USER, + errors: {}, + }, }, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, }, { @@ -155,9 +151,11 @@ function addSchoolPrincipal(firstName: string, partnerUserID: string, lastName: const failureData: OnyxUpdate[] = [ { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, - value: null, + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + [sessionEmail]: null, + }, }, { onyxMethod: Onyx.METHOD.SET, diff --git a/src/pages/home/sidebar/AllSettingsScreen.tsx b/src/pages/home/sidebar/AllSettingsScreen.tsx index 7151cc84e735..8056d9bcc413 100644 --- a/src/pages/home/sidebar/AllSettingsScreen.tsx +++ b/src/pages/home/sidebar/AllSettingsScreen.tsx @@ -18,16 +18,15 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Policy, PolicyMembers} from '@src/types/onyx'; +import type {Policy} from '@src/types/onyx'; type AllSettingsScreenOnyxProps = { policies: OnyxCollection; - policyMembers: OnyxCollection; }; type AllSettingsScreenProps = AllSettingsScreenOnyxProps; -function AllSettingsScreen({policies, policyMembers}: AllSettingsScreenProps) { +function AllSettingsScreen({policies}: AllSettingsScreenProps) { const styles = useThemeStyles(); const waitForNavigate = useWaitForNavigation(); const {translate} = useLocalize(); @@ -48,7 +47,7 @@ function AllSettingsScreen({policies, policyMembers}: AllSettingsScreenProps) { })(); }, focused: !isSmallScreenWidth, - brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies, policyMembers) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, }, ...(shouldShowSubscriptionsMenu ? [ @@ -90,7 +89,7 @@ function AllSettingsScreen({policies, policyMembers}: AllSettingsScreenProps) { hoverAndPressStyle: styles.hoveredComponentBG, brickRoadIndicator: item.brickRoadIndicator, })); - }, [isSmallScreenWidth, styles.hoveredComponentBG, styles.sectionMenuItem, translate, waitForNavigate, policies, policyMembers]); + }, [isSmallScreenWidth, styles.hoveredComponentBG, styles.sectionMenuItem, translate, waitForNavigate, policies]); return ( ({ policies: { key: ONYXKEYS.COLLECTION.POLICY, }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, })(AllSettingsScreen); diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 73bd031b5978..b535165e86bc 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -71,9 +71,6 @@ type InitialSettingsPageOnyxProps = { /** The policies which the user has access to */ policies: OnyxCollection; - - /** Members of all the workspaces the user is member of */ - policyMembers: OnyxCollection; }; type InitialSettingsPageProps = InitialSettingsPageOnyxProps & WithCurrentUserPersonalDetailsProps; @@ -98,7 +95,7 @@ type MenuData = { type Menu = {sectionStyle: StyleProp; sectionTranslationKey: TranslationPaths; items: MenuData[]}; -function InitialSettingsPage({session, userWallet, bankAccountList, fundList, walletTerms, loginList, currentUserPersonalDetails, policies, policyMembers}: InitialSettingsPageProps) { +function InitialSettingsPage({session, userWallet, bankAccountList, fundList, walletTerms, loginList, currentUserPersonalDetails, policies}: InitialSettingsPageProps) { const network = useNetwork(); const theme = useTheme(); const styles = useThemeStyles(); @@ -189,7 +186,7 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa translationKey: 'common.workspaces', icon: Expensicons.Building, routeName: ROUTES.SETTINGS_WORKSPACES, - brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies, policyMembers) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, }, { translationKey: 'allSettingsScreen.cardsAndDomains', @@ -221,7 +218,7 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa sectionTranslationKey: 'common.workspaces', items, }; - }, [policies, policyMembers, styles.workspaceSettingsSectionContainer]); + }, [policies, styles.workspaceSettingsSectionContainer]); /** * Retuns a list of menu items data for general section @@ -529,8 +526,5 @@ export default withCurrentUserPersonalDetails( policies: { key: ONYXKEYS.COLLECTION.POLICY, }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, })(InitialSettingsPage), ); diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 0d3bb027b8e6..1eef3ad214c5 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -60,7 +60,7 @@ function dismissError(policyID: string) { Policy.removeWorkspace(policyID); } -function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, reimbursementAccount, policyCategories}: WorkspaceInitialPageProps) { +function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAccount, policyCategories}: WorkspaceInitialPageProps) { const styles = useThemeStyles(); const policy = policyDraft?.id ? policyDraft : policyProp; const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false); @@ -99,7 +99,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r ReimbursementAccount.navigateToBankAccountRoute(policyID); }, [policyID, policyName]); - const hasMembersError = PolicyUtils.hasPolicyMemberError(policyMembers); + const hasMembersError = PolicyUtils.hasEmployeeListError(policy); const hasPolicyCategoryError = PolicyUtils.hasPolicyCategoriesError(policyCategories); const hasGeneralSettingsError = !isEmptyObject(policy?.errorFields?.generalSettings ?? {}) || !isEmptyObject(policy?.errorFields?.avatar ?? {}); const shouldShowProtectedItems = PolicyUtils.isPolicyAdmin(policy); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 549d307b2a2f..721aa41c7675 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -36,7 +36,7 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount, Report} from '@src/types/onyx'; +import type {Policy as PolicyType, ReimbursementAccount, Report} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -74,9 +74,6 @@ type WorkspaceListPageOnyxProps = { /** Bank account attached to free plan */ reimbursementAccount: OnyxEntry; - /** A collection of objects for all policies which key policy member objects by accountIDs */ - allPolicyMembers: OnyxCollection; - /** All reports shared with the user (coming from Onyx) */ reports: OnyxCollection; }; @@ -114,7 +111,7 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi throw new Error('Not implemented'); } -function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports}: WorkspaceListPageProps) { +function WorkspacesListPage({policies, reimbursementAccount, reports}: WorkspaceListPageProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -322,7 +319,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r title: policy.name, icon: policy.avatar ? policy.avatar : ReportUtils.getDefaultWorkspaceAvatar(policy.name), action: () => Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policy.id)), - brickRoadIndicator: reimbursementAccountBrickRoadIndicator ?? PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy, allPolicyMembers), + brickRoadIndicator: reimbursementAccountBrickRoadIndicator ?? PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy), pendingAction: policy.pendingAction, errors: policy.errors, dismissError: () => { @@ -344,7 +341,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r }; }) .sort((a, b) => localeCompare(a.title, b.title)); - }, [reimbursementAccount?.errors, policies, isOffline, theme.textLight, allPolicyMembers, policyRooms]); + }, [reimbursementAccount?.errors, policies, isOffline, theme.textLight, policyRooms]); if (isEmptyObject(workspaces)) { return ( @@ -435,9 +432,6 @@ export default withPolicyAndFullscreenLoading( policies: { key: ONYXKEYS.COLLECTION.POLICY, }, - allPolicyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 16af069fa0a2..0f276a9c53e3 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -94,7 +94,6 @@ type WithPolicyOnyxProps = { policy: OnyxEntry; policyMembers: OnyxEntry; policyDraft: OnyxEntry; - policyMembersDraft: OnyxEntry; }; type WithPolicyProps = WithPolicyOnyxProps & { @@ -105,7 +104,6 @@ const policyDefaultProps: WithPolicyOnyxProps = { policy: {} as OnyxTypes.Policy, policyMembers: {}, policyDraft: {} as OnyxTypes.Policy, - policyMembersDraft: {}, }; /* @@ -142,9 +140,6 @@ export default function (WrappedComponent: policyDraft: { key: (props) => `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${getPolicyIDFromRoute(props.route)}`, }, - policyMembersDraft: { - key: (props) => `${ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS}${getPolicyIDFromRoute(props.route)}`, - }, })(forwardRef(WithPolicy)); } diff --git a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx index 93e10cc1760c..959d6f9290e0 100644 --- a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx +++ b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx @@ -29,7 +29,6 @@ export default function withPolicyAndFullscreenLoading, @@ -46,7 +45,6 @@ export default function withPolicyAndFullscreenLoading ); From 5e84a328e13c1d1e00643cfc7990426231fc11e1 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 3 Apr 2024 17:57:01 +0200 Subject: [PATCH 02/22] Remove policyMembersDraft and policyMembers part-2 --- src/libs/actions/Policy.ts | 18 ++++++++++++------ src/types/onyx/Policy.ts | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 356eb440e3ca..9287d20a7bcc 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1781,10 +1781,13 @@ function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: C * Removes an error after trying to delete a member */ function clearDeleteMemberError(policyID: string, accountID: number) { - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, { - [accountID]: { - pendingAction: null, - errors: null, + const email = allPersonalDetails?.[accountID]?.login ?? ''; + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + employeeList: { + [email]: { + pendingAction: null, + errors: null, + }, }, }); } @@ -1793,8 +1796,11 @@ function clearDeleteMemberError(policyID: string, accountID: number) { * Removes an error after trying to add a member */ function clearAddMemberError(policyID: string, accountID: number) { - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, { - [accountID]: null, + const email = allPersonalDetails?.[accountID]?.login ?? ''; + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + employeeList: { + [email]: null, + }, }); Onyx.merge(`${ONYXKEYS.PERSONAL_DETAILS_LIST}`, { [accountID]: null, diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index ddb0c33c2f0c..4a814938c6ba 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -329,7 +329,7 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< submitsTo?: number; /** The employee list of the policy */ - employeeList?: OnyxTypes.PolicyMembers | []; + employeeList?: OnyxTypes.PolicyMembers; /** The reimbursement choice for policy */ reimbursementChoice?: ValueOf; From e154c4a6a634919ab2c0e82f8459040f1bcbc53e Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 3 Apr 2024 21:18:09 +0200 Subject: [PATCH 03/22] Remove policyMembersDraft and policyMembers part-3 --- src/libs/PolicyUtils.ts | 33 ++--- src/libs/actions/Policy.ts | 1 + src/pages/workspace/WorkspaceInvitePage.tsx | 17 +-- src/pages/workspace/WorkspaceMembersPage.tsx | 126 +++++++++--------- .../members/WorkspaceMemberDetailsPage.tsx | 17 +-- ...orkspaceMemberDetailsRoleSelectionPage.tsx | 5 +- .../withPolicyAndFullscreenLoading.tsx | 15 ++- .../WorkspaceWorkflowsApproverPage.tsx | 12 +- .../workflows/WorkspaceWorkflowsPayerPage.tsx | 22 +-- 9 files changed, 109 insertions(+), 139 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 7ff74f8af98e..d48fdd11527b 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -28,7 +28,7 @@ function getActivePolicies(policies: OnyxCollection): Policy[] | undefin * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. */ function hasEmployeeListError(policy: OnyxEntry): boolean { - return Object.values(policy?.employeeList ?? {}).some((employee) => Object.keys(employee?.errors ?? {}).length > 0); + return Object.values(policy?.employeeList ?? {}).some((employee) => Object.keys(employee?.errors ?? {}).length > 0); } /** @@ -90,10 +90,10 @@ function getUnitRateValue(toLocaleDigit: (arg: string) => string, customUnitRate * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. */ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf | undefined { - if (hasEmployeeListError(policy) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { - return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; - } - return undefined; + if (hasEmployeeListError(policy) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { + return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; + } + return undefined; } /** @@ -133,18 +133,19 @@ const isPolicyMember = (policyID: string, policies: OnyxCollection): boo * * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { +function getMemberAccountIDsForWorkspace(policy: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { + const members = policy?.employeeList ?? {}; const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; - Object.keys(policyMembers ?? {}).forEach((accountID) => { - const member = policyMembers?.[accountID]; + Object.keys(members).forEach((email) => { + const member = members?.[email]; if (Object.keys(member?.errors ?? {})?.length > 0) { return; } - const personalDetail = personalDetails?.[accountID]; + const personalDetail = Object.values(personalDetails ?? {})?.find((details) => details?.login === email); if (!personalDetail?.login) { return; } - memberEmailsToAccountIDs[personalDetail.login] = Number(accountID); + memberEmailsToAccountIDs[email] = Number(personalDetail.accountID); }); return memberEmailsToAccountIDs; } @@ -152,19 +153,19 @@ function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry /** * Get login list that we should not show in the workspace invite options */ -function getIneligibleInvitees(policyMembers: OnyxEntry, personalDetails: OnyxEntry): string[] { +function getIneligibleInvitees(policy: OnyxEntry): string[] { + const members = policy?.employeeList ?? {}; const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; - Object.keys(policyMembers ?? {}).forEach((accountID) => { - const policyMember = policyMembers?.[accountID]; + Object.keys(members).forEach((email) => { + const policyMember = members?.[email]; // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { return; } - const memberEmail = personalDetails?.[accountID]?.login; - if (!memberEmail) { + if (!email) { return; } - memberEmailsToExclude.push(memberEmail); + memberEmailsToExclude.push(email); }); return memberEmailsToExclude; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 9287d20a7bcc..a718e526bb07 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1973,6 +1973,7 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName expenseReportActionData, expenseCreatedReportActionID, } = ReportUtils.buildOptimisticWorkspaceChats(policyID, workspaceName); + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.SET, diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index a8354cfd3276..5c53527bf688 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -30,7 +30,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Beta, InvitedEmailsToAccountIDs, PersonalDetailsList} from '@src/types/onyx'; +import type {Beta, InvitedEmailsToAccountIDs} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import SearchInputManager from './SearchInputManager'; @@ -40,9 +40,6 @@ import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscree type MembersSection = SectionListData>; type WorkspaceInvitePageOnyxProps = { - /** All of the personal details for everyone */ - personalDetails: OnyxEntry; - /** Beta features list */ betas: OnyxEntry; @@ -57,7 +54,6 @@ type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & function WorkspaceInvitePage({ route, - policyMembers, personalDetails: personalDetailsProp, betas, invitedEmailsToAccountIDsDraft, @@ -72,7 +68,7 @@ function WorkspaceInvitePage({ const [personalDetails, setPersonalDetails] = useState([]); const [usersToInvite, setUsersToInvite] = useState([]); const openWorkspaceInvitePage = () => { - const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policyMembers, personalDetailsProp); + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy, personalDetailsProp); Policy.openWorkspaceInvitePage(route.params.policyID, Object.keys(policyMemberEmailsToAccountIDs)); }; @@ -91,7 +87,7 @@ function WorkspaceInvitePage({ useNetwork({onReconnect: openWorkspaceInvitePage}); - const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(policyMembers, personalDetailsProp), [policyMembers, personalDetailsProp]); + const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(policy), [policy]); useEffect(() => { const newUsersToInviteDict: Record = {}; @@ -100,7 +96,7 @@ function WorkspaceInvitePage({ const inviteOptions = OptionsListUtils.getMemberInviteOptions(personalDetailsProp, betas ?? [], searchTerm, excludedUsers, true); - // Update selectedOptions with the latest personalDetails and policyMembers information + // Update selectedOptions with the latest personalDetails and employeeList information const detailsMap: Record = {}; inviteOptions.personalDetails.forEach((detail) => { if (!detail.login) { @@ -150,7 +146,7 @@ function WorkspaceInvitePage({ setSelectedOptions(Object.values(newSelectedOptionsDict)); // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change - }, [personalDetailsProp, policyMembers, betas, searchTerm, excludedUsers]); + }, [personalDetailsProp, policy?.employeeList, betas, searchTerm, excludedUsers]); const sections: MembersSection[] = useMemo(() => { const sectionsArr: MembersSection[] = []; @@ -330,9 +326,6 @@ WorkspaceInvitePage.displayName = 'WorkspaceInvitePage'; export default withNavigationTransitionEnd( withPolicyAndFullscreenLoading( withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, betas: { key: ONYXKEYS.BETAS, }, diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 7b77f6b60ede..3fae61616f3f 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -49,9 +49,6 @@ import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscree import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; type WorkspaceMembersPageOnyxProps = { - /** Personal details of all users */ - personalDetails: OnyxEntry; - /** Session info for the currently logged in user. */ session: OnyxEntry; @@ -73,37 +70,28 @@ function invertObject(object: Record): Record { return inverted; } -type MemberOption = Omit & {accountID: number}; - -function WorkspaceMembersPage({ - policyMembers, - personalDetails, - invitedEmailsToAccountIDsDraft, - route, - policy, - session, - currentUserPersonalDetails, - isLoadingReportData = true, -}: WorkspaceMembersPageProps) { +type MemberOption = Omit & {email: string; accountID: number}; + +function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails, isLoadingReportData = true}: WorkspaceMembersPageProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const [selectedEmployees, setSelectedEmployees] = useState([]); + const [selectedEmployees, setSelectedEmployees] = useState([]); const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false); const [errors, setErrors] = useState({}); const {isOffline} = useNetwork(); const prevIsOffline = usePrevious(isOffline); - const accountIDs = useMemo(() => Object.keys(policyMembers ?? {}).map((accountID) => Number(accountID)), [policyMembers]); - const prevAccountIDs = usePrevious(accountIDs); + const accountEmails = useMemo(() => Object.keys(policy?.employeeList ?? {}).map((email) => email), [policy?.employeeList]); + const prevAccountEmails = usePrevious(accountEmails); const textInputRef = useRef(null); - const isOfflineAndNoMemberDataAvailable = isEmptyObject(policyMembers) && isOffline; + const isOfflineAndNoMemberDataAvailable = isEmptyObject(policy?.employeeList ?? {}) && isOffline; const prevPersonalDetails = usePrevious(personalDetails); const {translate, formatPhoneNumber, preferredLocale} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); const dropdownButtonRef = useRef(null); const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); const isLoading = useMemo( - () => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policyMembers)), - [isOfflineAndNoMemberDataAvailable, personalDetails, policyMembers], + () => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policy?.employeeList ?? {})), + [isOfflineAndNoMemberDataAvailable, personalDetails, policy?.employeeList], ); const selectionListRef = useRef(null); const isFocused = useIsFocused(); @@ -113,12 +101,13 @@ function WorkspaceMembersPage({ /** * Get filtered personalDetails list with current policyMembers */ - const filterPersonalDetails = (members: OnyxEntry, details: OnyxEntry): PersonalDetailsList => + const filterPersonalDetails = (members: OnyxEntry, personalDetailsList: OnyxEntry): PersonalDetailsList => Object.keys(members ?? {}).reduce((result, key) => { - if (details?.[key]) { + const details = Object.values(personalDetailsList ?? {}).find((currentDetails) => currentDetails?.login === key); + if (details) { return { ...result, - [key]: details[key], + [key]: details, }; } return result; @@ -128,8 +117,8 @@ function WorkspaceMembersPage({ * Get members for the current workspace */ const getWorkspaceMembers = useCallback(() => { - Policy.openWorkspaceMembersPage(route.params.policyID, Object.keys(PolicyUtils.getMemberAccountIDsForWorkspace(policyMembers, personalDetails))); - }, [route.params.policyID, policyMembers, personalDetails]); + Policy.openWorkspaceMembersPage(route.params.policyID, Object.keys(PolicyUtils.getMemberAccountIDsForWorkspace(policy, personalDetails))); + }, [route.params.policyID, policy, personalDetails]); /** * Check if the current selection includes members that cannot be removed @@ -137,7 +126,8 @@ function WorkspaceMembersPage({ const validateSelection = useCallback(() => { const newErrors: Errors = {}; selectedEmployees.forEach((member) => { - if (member !== policy?.ownerAccountID && member !== session?.accountID) { + const details = Object.values(personalDetails ?? {}).find((currentDetails) => currentDetails?.login === member); + if (details?.accountID !== policy?.ownerAccountID && details?.accountID !== session?.accountID) { return; } newErrors[member] = translate('workspace.people.error.cannotRemove'); @@ -156,25 +146,25 @@ function WorkspaceMembersPage({ }, [preferredLocale, validateSelection]); useEffect(() => { - if (removeMembersConfirmModalVisible && !lodashIsEqual(accountIDs, prevAccountIDs)) { + if (removeMembersConfirmModalVisible && !lodashIsEqual(accountEmails, prevAccountEmails)) { setRemoveMembersConfirmModalVisible(false); } setSelectedEmployees((prevSelected) => { // Filter all personal details in order to use the elements needed for the current workspace - const currentPersonalDetails = filterPersonalDetails(policyMembers, personalDetails); + 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 prevItem = prevPersonalDetails?.id; + const prevSelectedElements = prevSelected.map((email) => { + const prevItem = prevPersonalDetails?.find((item) => item?.login === email); const res = Object.values(currentPersonalDetails).find((item) => prevItem?.login === item?.login); - return res?.accountID ?? id; + return res?.login ?? email; }); // This is an equivalent of the lodash intersection function. The reduce method below is used to filter the items that exist in both arrays. - return [prevSelectedElements, Object.values(PolicyUtils.getMemberAccountIDsForWorkspace(policyMembers, personalDetails))].reduce((prev, members) => + return [prevSelectedElements, Object.keys(PolicyUtils.getMemberAccountIDsForWorkspace(policy, personalDetails))].reduce((prev, members) => prev.filter((item) => members.includes(item)), ); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [policyMembers]); + }, [policy]); useEffect(() => { const isReconnecting = prevIsOffline && !isOffline; @@ -201,7 +191,12 @@ function WorkspaceMembersPage({ } // Remove the admin from the list - const accountIDsToRemove = session?.accountID ? selectedEmployees.filter((id) => id !== session.accountID) : selectedEmployees; + const accountIDsToRemove = (session?.accountID ? selectedEmployees.filter((email) => email !== session.email) : selectedEmployees) + .map((email) => { + const details = Object.values(personalDetails ?? {}).find((currentDetails) => currentDetails?.login === email); + return details?.accountID; + }) + .filter(Boolean) as number[]; Policy.removeMembers(accountIDsToRemove, route.params.policyID); setSelectedEmployees([]); @@ -223,13 +218,13 @@ function WorkspaceMembersPage({ */ const toggleAllUsers = (memberList: MemberOption[]) => { const enabledAccounts = memberList.filter((member) => !member.isDisabled && !member.isDisabledCheckbox); - const everyoneSelected = enabledAccounts.every((member) => selectedEmployees.includes(member.accountID)); + const everyoneSelected = enabledAccounts.every((member) => selectedEmployees.includes(member.email)); if (everyoneSelected) { setSelectedEmployees([]); } else { - const everyAccountId = enabledAccounts.map((member) => member.accountID); - setSelectedEmployees(everyAccountId); + const everyEmail = enabledAccounts.map((member) => member.email); + setSelectedEmployees(everyEmail); } validateSelection(); @@ -239,8 +234,8 @@ function WorkspaceMembersPage({ * Add user from the selectedEmployees list */ const addUser = useCallback( - (accountID: number) => { - setSelectedEmployees((prevSelected) => [...prevSelected, accountID]); + (selectedEmail: string) => { + setSelectedEmployees((prevSelected) => [...prevSelected, selectedEmail]); validateSelection(); }, [validateSelection], @@ -250,8 +245,8 @@ function WorkspaceMembersPage({ * Remove user from the selectedEmployees list */ const removeUser = useCallback( - (accountID: number) => { - setSelectedEmployees((prevSelected) => prevSelected.filter((id) => id !== accountID)); + (selectedEmail: string) => { + setSelectedEmployees((prevSelected) => prevSelected.filter((email) => email !== selectedEmail)); validateSelection(); }, [validateSelection], @@ -261,16 +256,16 @@ function WorkspaceMembersPage({ * Toggle user from the selectedEmployees list */ const toggleUser = useCallback( - (accountID: number, pendingAction?: PendingAction) => { + (email: string, pendingAction?: PendingAction) => { if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { return; } // Add or remove the user if the checkbox is enabled - if (selectedEmployees.includes(accountID)) { - removeUser(accountID); + if (selectedEmployees.includes(email)) { + removeUser(email); } else { - addUser(accountID); + addUser(email); } }, [selectedEmployees, addUser, removeUser], @@ -319,16 +314,15 @@ function WorkspaceMembersPage({ const getUsers = useCallback((): MemberOption[] => { let result: MemberOption[] = []; - Object.entries(policyMembers ?? {}).forEach(([accountIDKey, policyMember]) => { - const accountID = Number(accountIDKey); + Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { if (isDeletedPolicyMember(policyMember)) { return; } - const details = personalDetails?.[accountID]; + const details = Object.values(personalDetails ?? {}).find((currentDetails) => currentDetails?.login === email); if (!details) { - Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with accountID: ${accountID}`); + Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with email: ${email}`); return; } @@ -341,7 +335,7 @@ function WorkspaceMembersPage({ } } - const isSelected = selectedEmployees.includes(accountID); + const isSelected = selectedEmployees.includes(email); const isOwner = policy?.owner === details.login; const isAdmin = policyMember.role === CONST.POLICY.ROLE.ADMIN; @@ -357,9 +351,12 @@ function WorkspaceMembersPage({ ); } + const accountID = details.accountID ?? 0; + result.push({ - keyForList: accountIDKey, + keyForList: email, accountID, + email, isSelected, isDisabledCheckbox: !(isPolicyAdmin && accountID !== policy?.ownerAccountID && accountID !== session?.accountID), isDisabled: isPolicyAdmin && (policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyMember.errors)), @@ -395,7 +392,7 @@ function WorkspaceMembersPage({ personalDetails, policy?.owner, policy?.ownerAccountID, - policyMembers, + policy?.employeeList, policyOwner, selectedEmployees, session?.accountID, @@ -412,20 +409,20 @@ function WorkspaceMembersPage({ if (!isFocused) { return; } - if (isEmptyObject(invitedEmailsToAccountIDsDraft) || accountIDs === prevAccountIDs) { + if (isEmptyObject(invitedEmailsToAccountIDsDraft) || accountEmails === prevAccountEmails) { return; } const invitedEmails = Object.values(invitedEmailsToAccountIDsDraft).map(String); selectionListRef.current?.scrollAndHighlightItem?.(invitedEmails, 1500); Policy.setWorkspaceInviteMembersDraft(route.params.policyID, {}); - }, [invitedEmailsToAccountIDsDraft, route.params.policyID, isFocused, accountIDs, prevAccountIDs]); + }, [invitedEmailsToAccountIDsDraft, route.params.policyID, isFocused, accountEmails, prevAccountEmails]); const getHeaderMessage = () => { if (isOfflineAndNoMemberDataAvailable) { return translate('workspace.common.mustBeOnlineToViewMembers'); } - return !isLoading && isEmptyObject(policyMembers) ? translate('workspace.common.memberNotFound') : ''; + return !isLoading && isEmptyObject(policy?.employeeList ?? {}) ? translate('workspace.common.memberNotFound') : ''; }; const getHeaderContent = () => ( @@ -467,7 +464,13 @@ function WorkspaceMembersPage({ return; } - const accountIDsToUpdate = selectedEmployees.filter((id) => policyMembers?.[id].role !== role); + const accountIDsToUpdate = selectedEmployees + .filter((email) => policy?.employeeList?.[email].role !== role) + .map((email) => { + const details = Object.values(personalDetails ?? {}).find((currentDetails) => currentDetails?.login === email); + return details?.accountID; + }) + .filter(Boolean) as number[]; Policy.updateWorkspaceMembersRole(route.params.policyID, accountIDsToUpdate, role); setSelectedEmployees([]); @@ -484,7 +487,7 @@ function WorkspaceMembersPage({ ]; if (PolicyUtils.isPaidGroupPolicy(policy)) { - if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.ADMIN)) { + if (selectedEmployees.find((employee) => policy?.employeeList?.[employee]?.role === CONST.POLICY.ROLE.ADMIN)) { options.push({ text: translate('workspace.people.makeMember'), value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_MEMBER, @@ -493,7 +496,7 @@ function WorkspaceMembersPage({ }); } - if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.USER)) { + if (selectedEmployees.find((employee) => policy?.employeeList?.[employee]?.role === CONST.POLICY.ROLE.USER)) { options.push({ text: translate('workspace.people.makeAdmin'), value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_ADMIN, @@ -592,7 +595,7 @@ function WorkspaceMembersPage({ headerMessage={getHeaderMessage()} headerContent={getHeaderContent()} onSelectRow={openMemberDetails} - onCheckboxPress={(item) => toggleUser(item.accountID)} + onCheckboxPress={(item) => toggleUser(item.email)} onSelectAll={() => toggleAllUsers(data)} onDismissError={dismissError} showLoadingPlaceholder={isLoading} @@ -613,9 +616,6 @@ WorkspaceMembersPage.displayName = 'WorkspaceMembersPage'; export default withCurrentUserPersonalDetails( withPolicyAndFullscreenLoading( withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, invitedEmailsToAccountIDsDraft: { key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, }, diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 4199bb5f432d..f73a799affba 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -2,7 +2,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import Avatar from '@components/Avatar'; import Button from '@components/Button'; @@ -28,7 +27,6 @@ import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPol import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -41,7 +39,7 @@ type WorkspacePolicyOnyxProps = { type WorkspaceMemberDetailsPageProps = WithPolicyAndFullscreenLoadingProps & WorkspacePolicyOnyxProps & StackScreenProps; -function WorkspaceMemberDetailsPage({personalDetails, policyMembers, policy, route}: WorkspaceMemberDetailsPageProps) { +function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceMemberDetailsPageProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); const {translate} = useLocalize(); @@ -54,14 +52,15 @@ function WorkspaceMemberDetailsPage({personalDetails, policyMembers, policy, rou const policyID = route.params.policyID; const backTo = route.params.backTo ?? ('' as Route); - const member = policyMembers?.[accountID]; + const memberLogin = personalDetails?.[accountID]?.login ?? ''; + const member = policy?.employeeList?.[memberLogin]; const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); const avatar = details.avatar ?? UserUtils.getDefaultAvatar(); const fallbackIcon = details.fallbackIcon ?? ''; const displayName = details.displayName ?? ''; const isSelectedMemberOwner = policy?.owner === details.login; const isSelectedMemberCurrentUser = accountID === currentUserPersonalDetails?.accountID; - const isCurrentUserAdmin = policyMembers?.[currentUserPersonalDetails?.accountID]?.role === CONST.POLICY.ROLE.ADMIN; + const isCurrentUserAdmin = policy?.employeeList?.[personalDetails?.[currentUserPersonalDetails?.accountID]?.login ?? '']?.role === CONST.POLICY.ROLE.ADMIN; const isCurrentUserOwner = policy?.owner === currentUserPersonalDetails?.login; useEffect(() => { @@ -184,10 +183,4 @@ function WorkspaceMemberDetailsPage({personalDetails, policyMembers, policy, rou WorkspaceMemberDetailsPage.displayName = 'WorkspaceMemberDetailsPage'; -export default withPolicyAndFullscreenLoading( - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - })(WorkspaceMemberDetailsPage), -); +export default withPolicyAndFullscreenLoading(WorkspaceMemberDetailsPage); diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx index 7958ae93b922..36f24b8922da 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx @@ -27,14 +27,15 @@ type ListItemType = { keyForList: typeof CONST.POLICY.ROLE.ADMIN | typeof CONST.POLICY.ROLE.USER; }; -function WorkspaceMemberDetailsRoleSelectionPage({policyMembers, route}: WorkspaceMemberDetailsPageProps) { +function WorkspaceMemberDetailsRoleSelectionPage({route, personalDetails, policy}: WorkspaceMemberDetailsPageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const accountID = Number(route.params.accountID) ?? 0; const policyID = route.params.policyID; const backTo = route.params.backTo ?? ('' as Route); - const member = policyMembers?.[accountID]; + const memberLogin = personalDetails?.[accountID]?.login ?? ''; + const member = policy?.employeeList?.[memberLogin]; const items: ListItemType[] = [ { diff --git a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx index 959d6f9290e0..e5d93dbe36d5 100644 --- a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx +++ b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx @@ -6,12 +6,16 @@ import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import compose from '@libs/compose'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {PersonalDetailsList} from '@src/types/onyx'; import type {WithPolicyOnyxProps, WithPolicyProps} from './withPolicy'; import withPolicy, {policyDefaultProps} from './withPolicy'; type WithPolicyAndFullscreenLoadingOnyxProps = { /** Indicated whether the report data is loading */ isLoadingReportData: OnyxEntry; + + /** Personal details of all users */ + personalDetails: OnyxEntry; }; type WithPolicyAndFullscreenLoadingProps = WithPolicyProps & WithPolicyAndFullscreenLoadingOnyxProps; @@ -24,13 +28,7 @@ export default function withPolicyAndFullscreenLoading>, ): ComponentWithPolicyAndFullscreenLoading { function WithPolicyAndFullscreenLoading( - { - isLoadingReportData = true, - policy = policyDefaultProps.policy, - policyDraft = policyDefaultProps.policyDraft, - policyMembers = policyDefaultProps.policyMembers, - ...rest - }: TProps, + {isLoadingReportData = true, policy = policyDefaultProps.policy, policyDraft = policyDefaultProps.policyDraft, policyMembers = policyDefaultProps.policyMembers, ...rest}: TProps, ref: ForwardedRef, ) { if (isLoadingReportData && isEmpty(policy) && isEmpty(policyDraft)) { @@ -57,6 +55,9 @@ export default function withPolicyAndFullscreenLoading({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - }), - withPolicyAndFullscreenLoading, -)(WorkspaceWorkflowsApproverPage); +export default withPolicyAndFullscreenLoading(WorkspaceWorkflowsApproverPage); diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx index e96b19ce4442..c9dc4e7e41ca 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx @@ -2,7 +2,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useMemo, useState} from 'react'; import type {SectionListData} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -14,7 +13,6 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; @@ -29,7 +27,6 @@ import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullsc import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList, PolicyMember} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -45,7 +42,7 @@ type WorkspaceWorkflowsPayerPageProps = WorkspaceWorkflowsPayerPageOnyxProps & type MemberOption = Omit & {accountID: number}; type MembersSection = SectionListData>; -function WorkspaceWorkflowsPayerPage({route, policy, policyMembers, personalDetails, isLoadingReportData = true}: WorkspaceWorkflowsPayerPageProps) { +function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingReportData = true}: WorkspaceWorkflowsPayerPageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -63,8 +60,8 @@ function WorkspaceWorkflowsPayerPage({route, policy, policyMembers, personalDeta const policyAdminDetails: MemberOption[] = []; const authorizedPayerDetails: MemberOption[] = []; - Object.entries(policyMembers ?? {}).forEach(([accountIDKey, policyMember]) => { - const accountID = Number(accountIDKey); + Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { + const accountID = Object.values(personalDetails ?? {}).find((details) => details?.login === email)?.accountID ?? 0; const details = personalDetails?.[accountID]; if (!details) { Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with accountID: ${accountID}`); @@ -90,7 +87,7 @@ function WorkspaceWorkflowsPayerPage({route, policy, policyMembers, personalDeta const isAuthorizedPayer = policy?.reimburserEmail === details?.login ?? policy?.reimburserAccountID === accountID; const formattedMember = { - keyForList: accountIDKey, + keyForList: String(accountID), accountID, isSelected: isAuthorizedPayer, isDisabled: policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyMember.errors), @@ -118,7 +115,7 @@ function WorkspaceWorkflowsPayerPage({route, policy, policyMembers, personalDeta return [policyAdminDetails, authorizedPayerDetails]; }, [ personalDetails, - policyMembers, + policy?.employeeList, translate, policy?.reimburserEmail, isDeletedPolicyMember, @@ -225,11 +222,4 @@ function WorkspaceWorkflowsPayerPage({route, policy, policyMembers, personalDeta WorkspaceWorkflowsPayerPage.displayName = 'WorkspaceWorkflowsPayerPage'; -export default compose( - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - }), - withPolicyAndFullscreenLoading, -)(WorkspaceWorkflowsPayerPage); +export default withPolicyAndFullscreenLoading(WorkspaceWorkflowsPayerPage); From 72a467fe0f0eefd2e2239606c0edadb16c8d1abb Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 6 Apr 2024 20:41:34 +0200 Subject: [PATCH 04/22] Remove policyMembers from WorkspaceWorkflowsApproverPage and revert changes from WorkspaceMembersPage --- src/libs/PolicyUtils.ts | 4 +- src/pages/workspace/WorkspaceInvitePage.tsx | 2 +- src/pages/workspace/WorkspaceMembersPage.tsx | 157 +++++++----------- .../WorkspaceWorkflowsApproverPage.tsx | 13 +- 4 files changed, 68 insertions(+), 108 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index d48fdd11527b..f54d45987c0c 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -133,8 +133,8 @@ const isPolicyMember = (policyID: string, policies: OnyxCollection): boo * * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(policy: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { - const members = policy?.employeeList ?? {}; +function getMemberAccountIDsForWorkspace(policyMembers: PolicyMembers | undefined, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { + const members = policyMembers ?? {}; const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; Object.keys(members).forEach((email) => { const member = members?.[email]; diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 5c53527bf688..90f44d8dd1c1 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -68,7 +68,7 @@ function WorkspaceInvitePage({ const [personalDetails, setPersonalDetails] = useState([]); const [usersToInvite, setUsersToInvite] = useState([]); const openWorkspaceInvitePage = () => { - const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy, personalDetailsProp); + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetailsProp); Policy.openWorkspaceInvitePage(route.params.policyID, Object.keys(policyMemberEmailsToAccountIDs)); }; diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 3fae61616f3f..c4828409545c 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -55,12 +55,10 @@ type WorkspaceMembersPageOnyxProps = { /** An object containing the accountID for every invited user email */ invitedEmailsToAccountIDsDraft: OnyxEntry; }; - type WorkspaceMembersPageProps = WithPolicyAndFullscreenLoadingProps & WithCurrentUserPersonalDetailsProps & WorkspaceMembersPageOnyxProps & StackScreenProps; - /** * Inverts an object, equivalent of _.invert */ @@ -70,55 +68,60 @@ function invertObject(object: Record): Record { return inverted; } -type MemberOption = Omit & {email: string; accountID: number}; - -function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails, isLoadingReportData = true}: WorkspaceMembersPageProps) { +type MemberOption = Omit & {accountID: number}; + +function WorkspaceMembersPage({ + policyMembers, + personalDetails, + invitedEmailsToAccountIDsDraft, + route, + policy, + session, + currentUserPersonalDetails, + isLoadingReportData = true, +}: WorkspaceMembersPageProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const [selectedEmployees, setSelectedEmployees] = useState([]); + const [selectedEmployees, setSelectedEmployees] = useState([]); const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false); const [errors, setErrors] = useState({}); const {isOffline} = useNetwork(); const prevIsOffline = usePrevious(isOffline); - const accountEmails = useMemo(() => Object.keys(policy?.employeeList ?? {}).map((email) => email), [policy?.employeeList]); - const prevAccountEmails = usePrevious(accountEmails); + const accountIDs = useMemo(() => Object.keys(policyMembers ?? {}).map((accountID) => Number(accountID)), [policyMembers]); + const prevAccountIDs = usePrevious(accountIDs); const textInputRef = useRef(null); - const isOfflineAndNoMemberDataAvailable = isEmptyObject(policy?.employeeList ?? {}) && isOffline; + const isOfflineAndNoMemberDataAvailable = isEmptyObject(policyMembers) && isOffline; const prevPersonalDetails = usePrevious(personalDetails); const {translate, formatPhoneNumber, preferredLocale} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); const dropdownButtonRef = useRef(null); const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); const isLoading = useMemo( - () => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policy?.employeeList ?? {})), - [isOfflineAndNoMemberDataAvailable, personalDetails, policy?.employeeList], + () => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policyMembers)), + [isOfflineAndNoMemberDataAvailable, personalDetails, policyMembers], ); const selectionListRef = useRef(null); const isFocused = useIsFocused(); - const policyID = route.params.policyID; - /** * Get filtered personalDetails list with current policyMembers */ - const filterPersonalDetails = (members: OnyxEntry, personalDetailsList: OnyxEntry): PersonalDetailsList => + const filterPersonalDetails = (members: OnyxEntry, details: OnyxEntry): PersonalDetailsList => Object.keys(members ?? {}).reduce((result, key) => { - const details = Object.values(personalDetailsList ?? {}).find((currentDetails) => currentDetails?.login === key); - if (details) { + if (details?.[key]) { return { ...result, - [key]: details, + [key]: details[key], }; } return result; }, {}); - /** * Get members for the current workspace */ const getWorkspaceMembers = useCallback(() => { - Policy.openWorkspaceMembersPage(route.params.policyID, Object.keys(PolicyUtils.getMemberAccountIDsForWorkspace(policy, personalDetails))); - }, [route.params.policyID, policy, personalDetails]); + Policy.openWorkspaceMembersPage(route.params.policyID, Object.keys(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails))); + }, [route.params.policyID, policy?.employeeList, personalDetails]); /** * Check if the current selection includes members that cannot be removed @@ -126,8 +129,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, const validateSelection = useCallback(() => { const newErrors: Errors = {}; selectedEmployees.forEach((member) => { - const details = Object.values(personalDetails ?? {}).find((currentDetails) => currentDetails?.login === member); - if (details?.accountID !== policy?.ownerAccountID && details?.accountID !== session?.accountID) { + if (member !== policy?.ownerAccountID && member !== session?.accountID) { return; } newErrors[member] = translate('workspace.people.error.cannotRemove'); @@ -135,36 +137,34 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, setErrors(newErrors); // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedEmployees, policy?.owner, session?.accountID]); - useEffect(() => { getWorkspaceMembers(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { validateSelection(); }, [preferredLocale, validateSelection]); useEffect(() => { - if (removeMembersConfirmModalVisible && !lodashIsEqual(accountEmails, prevAccountEmails)) { + if (removeMembersConfirmModalVisible && !lodashIsEqual(accountIDs, prevAccountIDs)) { setRemoveMembersConfirmModalVisible(false); } setSelectedEmployees((prevSelected) => { // Filter all personal details in order to use the elements needed for the current workspace - const currentPersonalDetails = filterPersonalDetails(policy?.employeeList ?? {}, personalDetails); + const currentPersonalDetails = filterPersonalDetails(policyMembers, 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((email) => { - const prevItem = prevPersonalDetails?.find((item) => item?.login === email); + const prevSelectedElements = prevSelected.map((id) => { + const prevItem = prevPersonalDetails?.id; const res = Object.values(currentPersonalDetails).find((item) => prevItem?.login === item?.login); - return res?.login ?? email; + return res?.accountID ?? id; }); // This is an equivalent of the lodash intersection function. The reduce method below is used to filter the items that exist in both arrays. - return [prevSelectedElements, Object.keys(PolicyUtils.getMemberAccountIDsForWorkspace(policy, personalDetails))].reduce((prev, members) => + return [prevSelectedElements, Object.values(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails))].reduce((prev, members) => prev.filter((item) => members.includes(item)), ); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [policy]); + }, [policyMembers, policy?.employeeList]); useEffect(() => { const isReconnecting = prevIsOffline && !isOffline; @@ -173,14 +173,12 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, } getWorkspaceMembers(); }, [isOffline, prevIsOffline, getWorkspaceMembers]); - /** * Open the modal to invite a user */ const inviteUser = () => { Navigation.navigate(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID)); }; - /** * Remove selected users from the workspace * Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details @@ -191,18 +189,12 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, } // Remove the admin from the list - const accountIDsToRemove = (session?.accountID ? selectedEmployees.filter((email) => email !== session.email) : selectedEmployees) - .map((email) => { - const details = Object.values(personalDetails ?? {}).find((currentDetails) => currentDetails?.login === email); - return details?.accountID; - }) - .filter(Boolean) as number[]; + const accountIDsToRemove = session?.accountID ? selectedEmployees.filter((id) => id !== session.accountID) : selectedEmployees; Policy.removeMembers(accountIDsToRemove, route.params.policyID); setSelectedEmployees([]); setRemoveMembersConfirmModalVisible(false); }; - /** * Show the modal to confirm removal of the selected members */ @@ -212,65 +204,60 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, } setRemoveMembersConfirmModalVisible(true); }; - /** * Add or remove all users passed from the selectedEmployees list */ const toggleAllUsers = (memberList: MemberOption[]) => { const enabledAccounts = memberList.filter((member) => !member.isDisabled && !member.isDisabledCheckbox); - const everyoneSelected = enabledAccounts.every((member) => selectedEmployees.includes(member.email)); + const everyoneSelected = enabledAccounts.every((member) => selectedEmployees.includes(member.accountID)); if (everyoneSelected) { setSelectedEmployees([]); } else { - const everyEmail = enabledAccounts.map((member) => member.email); - setSelectedEmployees(everyEmail); + const everyAccountId = enabledAccounts.map((member) => member.accountID); + setSelectedEmployees(everyAccountId); } validateSelection(); }; - /** * Add user from the selectedEmployees list */ const addUser = useCallback( - (selectedEmail: string) => { - setSelectedEmployees((prevSelected) => [...prevSelected, selectedEmail]); + (accountID: number) => { + setSelectedEmployees((prevSelected) => [...prevSelected, accountID]); validateSelection(); }, [validateSelection], ); - /** * Remove user from the selectedEmployees list */ const removeUser = useCallback( - (selectedEmail: string) => { - setSelectedEmployees((prevSelected) => prevSelected.filter((email) => email !== selectedEmail)); + (accountID: number) => { + setSelectedEmployees((prevSelected) => prevSelected.filter((id) => id !== accountID)); validateSelection(); }, [validateSelection], ); - /** * Toggle user from the selectedEmployees list */ const toggleUser = useCallback( - (email: string, pendingAction?: PendingAction) => { + (accountID: number, pendingAction?: PendingAction) => { if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { return; } // Add or remove the user if the checkbox is enabled - if (selectedEmployees.includes(email)) { - removeUser(email); + if (selectedEmployees.includes(accountID)) { + removeUser(accountID); } else { - addUser(email); + addUser(accountID); } }, [selectedEmployees, addUser, removeUser], ); - /** Opens the member details page */ const openMemberDetails = useCallback( (item: MemberOption) => { @@ -278,13 +265,11 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); return; } - Policy.clearWorkspaceOwnerChangeFlow(policyID); Navigation.navigate(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(route.params.policyID, item.accountID, Navigation.getActiveRoute())); }, [isPolicyAdmin, policy, policyID, route.params.policyID], ); - /** * Dismisses the errors on one item */ @@ -298,7 +283,6 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }, [route.params.policyID], ); - /** * Check if the policy member is deleted from the workspace */ @@ -308,21 +292,20 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ); const policyOwner = policy?.owner; const currentUserLogin = currentUserPersonalDetails.login; - const invitedPrimaryToSecondaryLogins = invertObject(policy?.primaryLoginsInvited ?? {}); - const getUsers = useCallback((): MemberOption[] => { let result: MemberOption[] = []; - Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { + Object.entries(policyMembers ?? {}).forEach(([accountIDKey, policyMember]) => { + const accountID = Number(accountIDKey); if (isDeletedPolicyMember(policyMember)) { return; } - const details = Object.values(personalDetails ?? {}).find((currentDetails) => currentDetails?.login === email); + const details = personalDetails?.[accountID]; if (!details) { - Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with email: ${email}`); + Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with accountID: ${accountID}`); return; } @@ -335,11 +318,10 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, } } - const isSelected = selectedEmployees.includes(email); + const isSelected = selectedEmployees.includes(accountID); const isOwner = policy?.owner === details.login; const isAdmin = policyMember.role === CONST.POLICY.ROLE.ADMIN; - let roleBadge = null; if (isOwner || isAdmin) { roleBadge = ( @@ -351,12 +333,9 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ); } - const accountID = details.accountID ?? 0; - result.push({ - keyForList: email, + keyForList: accountIDKey, accountID, - email, isSelected, isDisabledCheckbox: !(isPolicyAdmin && accountID !== policy?.ownerAccountID && accountID !== session?.accountID), isDisabled: isPolicyAdmin && (policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyMember.errors)), @@ -373,14 +352,11 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ], errors: policyMember.errors, pendingAction: policyMember.pendingAction, - // Note which secondary login was used to invite this primary login invitedSecondaryLogin: details?.login ? invitedPrimaryToSecondaryLogins[details.login] ?? '' : '', }); }); - result = result.sort((a, b) => (a.text ?? '').toLowerCase().localeCompare((b.text ?? '').toLowerCase())); - return result; }, [ StyleUtils, @@ -392,7 +368,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, personalDetails, policy?.owner, policy?.ownerAccountID, - policy?.employeeList, + policyMembers, policyOwner, selectedEmployees, session?.accountID, @@ -402,27 +378,25 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, styles.textStrong, translate, ]); - const data = useMemo(() => getUsers(), [getUsers]); - useEffect(() => { if (!isFocused) { return; } - if (isEmptyObject(invitedEmailsToAccountIDsDraft) || accountEmails === prevAccountEmails) { + if (isEmptyObject(invitedEmailsToAccountIDsDraft) || accountIDs === prevAccountIDs) { return; } const invitedEmails = Object.values(invitedEmailsToAccountIDsDraft).map(String); selectionListRef.current?.scrollAndHighlightItem?.(invitedEmails, 1500); Policy.setWorkspaceInviteMembersDraft(route.params.policyID, {}); - }, [invitedEmailsToAccountIDsDraft, route.params.policyID, isFocused, accountEmails, prevAccountEmails]); + }, [invitedEmailsToAccountIDsDraft, route.params.policyID, isFocused, accountIDs, prevAccountIDs]); const getHeaderMessage = () => { if (isOfflineAndNoMemberDataAvailable) { return translate('workspace.common.mustBeOnlineToViewMembers'); } - return !isLoading && isEmptyObject(policy?.employeeList ?? {}) ? translate('workspace.common.memberNotFound') : ''; + return !isLoading && isEmptyObject(policyMembers) ? translate('workspace.common.memberNotFound') : ''; }; const getHeaderContent = () => ( @@ -439,7 +413,6 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, )} ); - const getCustomListHeader = () => { const header = ( @@ -451,31 +424,21 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ); - if (isPolicyAdmin) { return header; } - return {header}; }; - const changeUserRole = (role: typeof CONST.POLICY.ROLE.ADMIN | typeof CONST.POLICY.ROLE.USER) => { if (!isEmptyObject(errors)) { return; } - const accountIDsToUpdate = selectedEmployees - .filter((email) => policy?.employeeList?.[email].role !== role) - .map((email) => { - const details = Object.values(personalDetails ?? {}).find((currentDetails) => currentDetails?.login === email); - return details?.accountID; - }) - .filter(Boolean) as number[]; + const accountIDsToUpdate = selectedEmployees.filter((id) => policyMembers?.[id].role !== role); Policy.updateWorkspaceMembersRole(route.params.policyID, accountIDsToUpdate, role); setSelectedEmployees([]); }; - const getBulkActionsButtonOptions = () => { const options: Array> = [ { @@ -487,7 +450,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ]; if (PolicyUtils.isPaidGroupPolicy(policy)) { - if (selectedEmployees.find((employee) => policy?.employeeList?.[employee]?.role === CONST.POLICY.ROLE.ADMIN)) { + if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.ADMIN)) { options.push({ text: translate('workspace.people.makeMember'), value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_MEMBER, @@ -496,7 +459,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }); } - if (selectedEmployees.find((employee) => policy?.employeeList?.[employee]?.role === CONST.POLICY.ROLE.USER)) { + if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.USER)) { options.push({ text: translate('workspace.people.makeAdmin'), value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_ADMIN, @@ -505,15 +468,12 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }); } } - return options; }; - const getHeaderButtons = () => { if (!isPolicyAdmin) { return null; } - return ( {selectedEmployees.length > 0 ? ( @@ -541,7 +501,6 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ); }; - return ( toggleUser(item.email)} + onCheckboxPress={(item) => toggleUser(item.accountID)} onSelectAll={() => toggleAllUsers(data)} onDismissError={dismissError} showLoadingPlaceholder={isLoading} @@ -610,9 +569,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ); } - WorkspaceMembersPage.displayName = 'WorkspaceMembersPage'; - export default withCurrentUserPersonalDetails( withPolicyAndFullscreenLoading( withOnyx({ diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 51baaca166b5..d642960255c5 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -41,7 +41,7 @@ type WorkspaceWorkflowsApproverPageProps = WorkspaceWorkflowsApproverPageOnyxPro type MemberOption = Omit & {accountID: number}; type MembersSection = SectionListData>; -function WorkspaceWorkflowsApproverPage({policy, policyMembers, personalDetails, isLoadingReportData = true, route}: WorkspaceWorkflowsApproverPageProps) { +function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingReportData = true, route}: WorkspaceWorkflowsApproverPageProps) { const {translate} = useLocalize(); const policyName = policy?.name ?? ''; const [searchTerm, setSearchTerm] = useState(''); @@ -58,12 +58,15 @@ function WorkspaceWorkflowsApproverPage({policy, policyMembers, personalDetails, const policyMemberDetails: MemberOption[] = []; const approverDetails: MemberOption[] = []; - Object.entries(policyMembers ?? {}).forEach(([accountIDKey, policyMember]) => { - const accountID = Number(accountIDKey); + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails); + + Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { if (isDeletedPolicyMember(policyMember)) { return; } + const accountID = Number(policyMemberEmailsToAccountIDs[email] || ''); + const details = personalDetails?.[accountID]; if (!details) { Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with accountID: ${accountID}`); @@ -85,7 +88,7 @@ function WorkspaceWorkflowsApproverPage({policy, policyMembers, personalDetails, } const formattedMember = { - keyForList: accountIDKey, + keyForList: String(accountID), accountID, isSelected: policy?.approver === details.login, isDisabled: policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyMember.errors), @@ -111,7 +114,7 @@ function WorkspaceWorkflowsApproverPage({policy, policyMembers, personalDetails, } }); return [policyMemberDetails, approverDetails]; - }, [personalDetails, policyMembers, translate, policy?.approver, StyleUtils, isDeletedPolicyMember, policy?.owner, styles]); + }, [personalDetails, translate, policy?.approver, StyleUtils, isDeletedPolicyMember, policy?.owner, styles, policy?.employeeList]); const sections: MembersSection[] = useMemo(() => { const sectionsArray: MembersSection[] = []; From 224f2b7c9ed472e8f3c6594f73203c764abe96d4 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 6 Apr 2024 21:21:42 +0200 Subject: [PATCH 05/22] Remove policyMembers from ReportScreenIDSetter and refactor PolicyMembersUtils --- .../AppNavigator/ReportScreenIDSetter.ts | 19 +++++++++--------- src/libs/PolicyMembersUtils.ts | 20 +++++++++++++------ src/libs/PolicyUtils.ts | 12 +++++------ 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index 529f0f3d31a7..a9fbf19b4477 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -6,7 +6,7 @@ import usePermissions from '@hooks/usePermissions'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, Report, ReportMetadata} from '@src/types/onyx'; import type {ReportScreenWrapperProps} from './ReportScreenWrapper'; type ReportScreenIDSetterComponentProps = { @@ -16,8 +16,8 @@ type ReportScreenIDSetterComponentProps = { /** The policies which the user has access to */ policies: OnyxCollection; - /** Members of all the workspaces the user is member of */ - policyMembers: OnyxCollection; + /** The personal details of the person who is logged in */ + personalDetails: OnyxEntry; /** Whether user is a new user */ isFirstTimeNewExpensifyUser: OnyxEntry; @@ -58,7 +58,7 @@ const getLastAccessedReportID = ( }; // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params -function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { +function ReportScreenIDSetter({route, reports, policies, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID, personalDetails}: ReportScreenIDSetterProps) { const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); @@ -73,7 +73,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav return; } - const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID); + const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policies, personalDetails, activeWorkspaceID, accountID); // If there is no reportID in route, try to find last accessed and use it for setParams const reportID = getLastAccessedReportID( @@ -92,7 +92,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav if (reportID) { navigation.setParams({reportID: String(reportID)}); } - }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reportMetadata, activeWorkspaceID, policyMembers, accountID]); + }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reportMetadata, activeWorkspaceID, personalDetails, accountID]); // The ReportScreen without the reportID set will display a skeleton // until the reportID is loaded and set in the route param @@ -110,10 +110,6 @@ export default withOnyx session?.accountID, }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, })(ReportScreenIDSetter); diff --git a/src/libs/PolicyMembersUtils.ts b/src/libs/PolicyMembersUtils.ts index 4376de150f17..59d1bff22597 100644 --- a/src/libs/PolicyMembersUtils.ts +++ b/src/libs/PolicyMembersUtils.ts @@ -1,15 +1,23 @@ -import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyMembers} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy} from '@src/types/onyx'; import {getCurrentUserAccountID} from './actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from './PolicyUtils'; -let policyMembers: OnyxCollection; +let policies: OnyxCollection; Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, - callback: (value) => (policyMembers = value), + callback: (value) => (policies = value), +}); + +let allPersonalDetails: OnyxEntry = {}; +Onyx.connect({ + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + callback: (val) => { + allPersonalDetails = val; + }, }); function getPolicyMemberAccountIDs(policyID?: string) { @@ -19,7 +27,7 @@ function getPolicyMemberAccountIDs(policyID?: string) { const currentUserAccountID = getCurrentUserAccountID(); - return getPolicyMembersByIdWithoutCurrentUser(policyMembers, policyID, currentUserAccountID); + return getPolicyMembersByIdWithoutCurrentUser(policies, allPersonalDetails, policyID, currentUserAccountID); } export default getPolicyMemberAccountIDs; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index f54d45987c0c..43445db90fdf 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -273,12 +273,12 @@ function getPathWithoutPolicyID(path: string) { return path.replace(CONST.REGEX.PATH_WITHOUT_POLICY_ID, '/'); } -function getPolicyMembersByIdWithoutCurrentUser(policyMembers: OnyxCollection, currentPolicyID?: string, currentUserAccountID?: number) { - return policyMembers - ? Object.keys(policyMembers[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${currentPolicyID}`] ?? {}) - .map((policyMemberAccountID) => Number(policyMemberAccountID)) - .filter((policyMemberAccountID) => policyMemberAccountID !== currentUserAccountID) - : []; +function getPolicyMembersByIdWithoutCurrentUser(policies: OnyxCollection, personalDetails: OnyxEntry, currentPolicyID?: string, currentUserAccountID?: number) { + const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${currentPolicyID}`] ?? null; + const policyMemberEmailsToAccountIDs = getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails); + return Object.values(policyMemberEmailsToAccountIDs) + .map((policyMemberAccountID) => Number(policyMemberAccountID)) + .filter((policyMemberAccountID) => policyMemberAccountID !== currentUserAccountID); } function goBackFromInvalidPolicy() { From 99c6a2809649f0dfcd340f325a3de3dbca2c15f9 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 6 Apr 2024 21:27:04 +0200 Subject: [PATCH 06/22] Remove policyMembers from SidebarLinksData --- src/pages/home/sidebar/SidebarLinksData.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 0c1f8b8c5972..016435f42fa5 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -65,9 +65,6 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types policies: PropTypes.object, - // eslint-disable-next-line react/forbid-prop-types - policyMembers: PropTypes.object, - /** All of the transaction violations */ transactionViolations: PropTypes.shape({ violations: PropTypes.arrayOf( @@ -102,7 +99,6 @@ const defaultProps = { priorityMode: CONST.PRIORITY_MODE.DEFAULT, betas: [], policies: {}, - policyMembers: {}, transactionViolations: {}, allReportActions: {}, ...withCurrentUserPersonalDetailsDefaultProps, @@ -120,7 +116,6 @@ function SidebarLinksData({ policies, priorityMode, network, - policyMembers, transactionViolations, currentUserPersonalDetails, }) { @@ -129,7 +124,7 @@ function SidebarLinksData({ const {translate} = useLocalize(); const prevPriorityMode = usePrevious(priorityMode); - const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, currentUserPersonalDetails.accountID); + const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policies, activeWorkspaceID, currentUserPersonalDetails.accountID); // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); @@ -322,9 +317,6 @@ export default compose( selector: policySelector, initialValue: {}, }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, transactionViolations: { key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, initialValue: {}, @@ -348,7 +340,6 @@ export default compose( prevProps.network.isOffline === nextProps.network.isOffline && _.isEqual(prevProps.insets, nextProps.insets) && prevProps.onLinkClick === nextProps.onLinkClick && - _.isEqual(prevProps.policyMembers, nextProps.policyMembers) && _.isEqual(prevProps.transactionViolations, nextProps.transactionViolations) && _.isEqual(prevProps.currentUserPersonalDetails, nextProps.currentUserPersonalDetails) && prevProps.currentReportID === nextProps.currentReportID, From 23235b6b57b1d4505900121a25f81297c4b40616 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 6 Apr 2024 21:55:53 +0200 Subject: [PATCH 07/22] Remove policyMembers from WorkspaceMembersPage --- src/pages/workspace/WorkspaceMembersPage.tsx | 43 ++++++++----------- src/pages/workspace/withPolicy.tsx | 5 --- .../withPolicyAndFullscreenLoading.tsx | 3 +- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index c4828409545c..762e68f6befb 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -70,16 +70,8 @@ function invertObject(object: Record): Record { type MemberOption = Omit & {accountID: number}; -function WorkspaceMembersPage({ - policyMembers, - personalDetails, - invitedEmailsToAccountIDsDraft, - route, - policy, - session, - currentUserPersonalDetails, - isLoadingReportData = true, -}: WorkspaceMembersPageProps) { +function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails, isLoadingReportData = true}: WorkspaceMembersPageProps) { + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [selectedEmployees, setSelectedEmployees] = useState([]); @@ -87,18 +79,18 @@ function WorkspaceMembersPage({ const [errors, setErrors] = useState({}); const {isOffline} = useNetwork(); const prevIsOffline = usePrevious(isOffline); - const accountIDs = useMemo(() => Object.keys(policyMembers ?? {}).map((accountID) => Number(accountID)), [policyMembers]); + const accountIDs = useMemo(() => Object.values(policyMemberEmailsToAccountIDs ?? {}).map((accountID) => Number(accountID)), [policyMemberEmailsToAccountIDs]); const prevAccountIDs = usePrevious(accountIDs); const textInputRef = useRef(null); - const isOfflineAndNoMemberDataAvailable = isEmptyObject(policyMembers) && isOffline; + const isOfflineAndNoMemberDataAvailable = isEmptyObject(policy?.employeeList) && isOffline; const prevPersonalDetails = usePrevious(personalDetails); const {translate, formatPhoneNumber, preferredLocale} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); const dropdownButtonRef = useRef(null); const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); const isLoading = useMemo( - () => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policyMembers)), - [isOfflineAndNoMemberDataAvailable, personalDetails, policyMembers], + () => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policy?.employeeList)), + [isOfflineAndNoMemberDataAvailable, personalDetails, policy?.employeeList], ); const selectionListRef = useRef(null); const isFocused = useIsFocused(); @@ -111,7 +103,7 @@ function WorkspaceMembersPage({ if (details?.[key]) { return { ...result, - [key]: details[key], + [key]: details[policyMemberEmailsToAccountIDs[key] ?? ''], }; } return result; @@ -151,7 +143,7 @@ function WorkspaceMembersPage({ } setSelectedEmployees((prevSelected) => { // Filter all personal details in order to use the elements needed for the current workspace - const currentPersonalDetails = filterPersonalDetails(policyMembers, personalDetails); + 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 prevItem = prevPersonalDetails?.id; @@ -164,7 +156,7 @@ function WorkspaceMembersPage({ ); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [policyMembers, policy?.employeeList]); + }, [policy?.employeeList]); useEffect(() => { const isReconnecting = prevIsOffline && !isOffline; @@ -296,8 +288,8 @@ function WorkspaceMembersPage({ const getUsers = useCallback((): MemberOption[] => { let result: MemberOption[] = []; - Object.entries(policyMembers ?? {}).forEach(([accountIDKey, policyMember]) => { - const accountID = Number(accountIDKey); + Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { + const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? ''); if (isDeletedPolicyMember(policyMember)) { return; } @@ -334,7 +326,7 @@ function WorkspaceMembersPage({ } result.push({ - keyForList: accountIDKey, + keyForList: String(accountID), accountID, isSelected, isDisabledCheckbox: !(isPolicyAdmin && accountID !== policy?.ownerAccountID && accountID !== session?.accountID), @@ -368,7 +360,8 @@ function WorkspaceMembersPage({ personalDetails, policy?.owner, policy?.ownerAccountID, - policyMembers, + policy?.employeeList, + policyMemberEmailsToAccountIDs, policyOwner, selectedEmployees, session?.accountID, @@ -396,7 +389,7 @@ function WorkspaceMembersPage({ return translate('workspace.common.mustBeOnlineToViewMembers'); } - return !isLoading && isEmptyObject(policyMembers) ? translate('workspace.common.memberNotFound') : ''; + return !isLoading && isEmptyObject(policy?.employeeList) ? translate('workspace.common.memberNotFound') : ''; }; const getHeaderContent = () => ( @@ -434,7 +427,7 @@ function WorkspaceMembersPage({ return; } - const accountIDsToUpdate = selectedEmployees.filter((id) => policyMembers?.[id].role !== role); + const accountIDsToUpdate = selectedEmployees.filter((id) => policy?.employeeList?.[policyMemberEmailsToAccountIDs[id]]?.role !== role); Policy.updateWorkspaceMembersRole(route.params.policyID, accountIDsToUpdate, role); setSelectedEmployees([]); @@ -450,7 +443,7 @@ function WorkspaceMembersPage({ ]; if (PolicyUtils.isPaidGroupPolicy(policy)) { - if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.ADMIN)) { + if (selectedEmployees.find((employee) => policy?.employeeList?.[policyMemberEmailsToAccountIDs[employee]]?.role === CONST.POLICY.ROLE.ADMIN)) { options.push({ text: translate('workspace.people.makeMember'), value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_MEMBER, @@ -459,7 +452,7 @@ function WorkspaceMembersPage({ }); } - if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.USER)) { + if (selectedEmployees.find((employee) => policy?.employeeList?.[policyMemberEmailsToAccountIDs[employee]]?.role === CONST.POLICY.ROLE.USER)) { options.push({ text: translate('workspace.people.makeAdmin'), value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_ADMIN, diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 0f276a9c53e3..9262a1b0f727 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -92,7 +92,6 @@ const policyPropTypes = { type WithPolicyOnyxProps = { policy: OnyxEntry; - policyMembers: OnyxEntry; policyDraft: OnyxEntry; }; @@ -102,7 +101,6 @@ type WithPolicyProps = WithPolicyOnyxProps & { const policyDefaultProps: WithPolicyOnyxProps = { policy: {} as OnyxTypes.Policy, - policyMembers: {}, policyDraft: {} as OnyxTypes.Policy, }; @@ -134,9 +132,6 @@ export default function (WrappedComponent: policy: { key: (props) => `${ONYXKEYS.COLLECTION.POLICY}${getPolicyIDFromRoute(props.route)}`, }, - policyMembers: { - key: (props) => `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${getPolicyIDFromRoute(props.route)}`, - }, policyDraft: { key: (props) => `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${getPolicyIDFromRoute(props.route)}`, }, diff --git a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx index e5d93dbe36d5..161320441843 100644 --- a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx +++ b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx @@ -28,7 +28,7 @@ export default function withPolicyAndFullscreenLoading>, ): ComponentWithPolicyAndFullscreenLoading { function WithPolicyAndFullscreenLoading( - {isLoadingReportData = true, policy = policyDefaultProps.policy, policyDraft = policyDefaultProps.policyDraft, policyMembers = policyDefaultProps.policyMembers, ...rest}: TProps, + {isLoadingReportData = true, policy = policyDefaultProps.policy, policyDraft = policyDefaultProps.policyDraft, ...rest}: TProps, ref: ForwardedRef, ) { if (isLoadingReportData && isEmpty(policy) && isEmpty(policyDraft)) { @@ -42,7 +42,6 @@ export default function withPolicyAndFullscreenLoading ); From b3cc1d0882d38437a5fdb133893b0bb89c0b37d2 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 6 Apr 2024 22:01:37 +0200 Subject: [PATCH 08/22] Remove POLICY_MEMBERS from utils --- src/ONYXKEYS.ts | 2 -- src/libs/PolicyUtils.ts | 2 +- src/pages/workspace/withPolicy.tsx | 4 ---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 3ff71362af35..c4d83acf24c1 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -307,7 +307,6 @@ const ONYXKEYS = { COLLECTION: { DOWNLOAD: 'download_', POLICY: 'policy_', - POLICY_MEMBERS: 'policyMembers_', POLICY_DRAFTS: 'policyDrafts_', POLICY_JOIN_MEMBER: 'policyJoinMember_', POLICY_CATEGORIES: 'policyCategories_', @@ -511,7 +510,6 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 43445db90fdf..79bfd723eb76 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -25,7 +25,7 @@ function getActivePolicies(policies: OnyxCollection): Policy[] | undefin } /** - * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. + * Checks if we have any errors stored within the policy?.employeeList. Determines whether we should show a red brick road error or not. */ function hasEmployeeListError(policy: OnyxEntry): boolean { return Object.values(policy?.employeeList ?? {}).some((employee) => Object.keys(employee?.errors ?? {}).length > 0); diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 9262a1b0f727..3bdff65e2c66 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -9,7 +9,6 @@ import type {ValueOf} from 'type-fest'; import taxPropTypes from '@components/taxPropTypes'; import {translatableTextPropTypes} from '@libs/Localize'; import type {CentralPaneNavigatorParamList, FullScreenNavigatorParamList, SettingsNavigatorParamList, WorkspacesCentralPaneNavigatorParamList} from '@navigation/types'; -import policyMemberPropType from '@pages/policyMemberPropType'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -85,9 +84,6 @@ const policyPropTypes = { /** Collection of tax rates attached to a policy */ taxRates: taxPropTypes, }), - - /** The employee list of this policy */ - policyMembers: PropTypes.objectOf(policyMemberPropType), }; type WithPolicyOnyxProps = { From fbe42b21d8e4749d3ac7f9d0df337333d728ac6f Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 6 Apr 2024 22:10:24 +0200 Subject: [PATCH 09/22] Make a little code refactoring --- src/libs/PolicyUtils.ts | 4 ++-- src/pages/workspace/WorkspaceMembersPage.tsx | 2 +- tests/actions/PolicyTest.ts | 16 +--------------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 79bfd723eb76..8dc61755186b 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -133,8 +133,8 @@ const isPolicyMember = (policyID: string, policies: OnyxCollection): boo * * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(policyMembers: PolicyMembers | undefined, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { - const members = policyMembers ?? {}; +function getMemberAccountIDsForWorkspace(employeeList: PolicyMembers | undefined, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { + const members = employeeList ?? {}; const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; Object.keys(members).forEach((email) => { const member = members?.[email]; diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 762e68f6befb..a49267f37b41 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -96,7 +96,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, const isFocused = useIsFocused(); const policyID = route.params.policyID; /** - * Get filtered personalDetails list with current policyMembers + * Get filtered personalDetails list with current employeeList */ const filterPersonalDetails = (members: OnyxEntry, details: OnyxEntry): PersonalDetailsList => Object.keys(members ?? {}).reduce((result, key) => { diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index dfb8ae81d104..1ca2edec0de7 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -4,7 +4,7 @@ import CONST from '@src/CONST'; import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as Policy from '@src/libs/actions/Policy'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyMembers, Policy as PolicyType, Report, ReportAction, ReportActions} from '@src/types/onyx'; +import type {Policy as PolicyType, Report, ReportAction, ReportActions} from '@src/types/onyx'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -61,20 +61,6 @@ describe('actions/Policy', () => { expect(policy?.isPolicyExpenseChatEnabled).toBe(true); expect(policy?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - const policyMembers: OnyxCollection = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, - waitForCollectionCallback: true, - callback: (members) => { - Onyx.disconnect(connectionID); - resolve(members); - }, - }); - }); - - // check if the user was added as an admin to the policy - expect(policyMembers?.[ESH_ACCOUNT_ID]?.role).toBe(CONST.POLICY.ROLE.ADMIN); - let allReports: OnyxCollection = await new Promise((resolve) => { const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, From a9c77e9e1f6b10333683addf134376f88e433f3c Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 6 Apr 2024 22:27:41 +0200 Subject: [PATCH 10/22] Fix ts issue --- tests/utils/LHNTestUtils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 50a495f39d51..b7a8aa4f1616 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -248,7 +248,7 @@ function getFakePolicy(id = '1', name = 'Workspace-Test-001'): Policy { owner: 'myuser@gmail.com', outputCurrency: 'BRL', avatar: '', - employeeList: [], + employeeList: {}, isPolicyExpenseChatEnabled: true, lastModified: '1697323926777105', autoReporting: true, From 1bc67dca62985794b58c360abef45110c32d5792 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 6 Apr 2024 23:02:36 +0200 Subject: [PATCH 11/22] Refactor code and fix some issues --- src/libs/PolicyUtils.ts | 4 +-- src/pages/workspace/WorkspaceInvitePage.tsx | 2 +- src/pages/workspace/WorkspaceMembersPage.tsx | 28 ++++++++++++++++--- .../WorkspaceWorkflowsApproverPage.tsx | 2 +- .../workflows/WorkspaceWorkflowsPayerPage.tsx | 4 ++- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 0bd7cd65b176..135a0f96d684 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -153,8 +153,8 @@ function getMemberAccountIDsForWorkspace(employeeList: PolicyMembers | undefined /** * Get login list that we should not show in the workspace invite options */ -function getIneligibleInvitees(policy: OnyxEntry): string[] { - const members = policy?.employeeList ?? {}; +function getIneligibleInvitees(employeeList?: PolicyMembers): string[] { + const members = employeeList ?? {}; const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; Object.keys(members).forEach((email) => { const policyMember = members?.[email]; diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index a263db558632..108da0c4c3c8 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -91,7 +91,7 @@ function WorkspaceInvitePage({ useNetwork({onReconnect: openWorkspaceInvitePage}); - const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(policy), [policy]); + const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(policy?.employeeList), [policy?.employeeList]); useEffect(() => { const newUsersToInviteDict: Record = {}; diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 1ccb838bdf7c..5b7631b69682 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -71,7 +71,7 @@ function invertObject(object: Record): Record { type MemberOption = Omit & {accountID: number}; function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails, isLoadingReportData = true}: WorkspaceMembersPageProps) { - const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails); + const policyMemberEmailsToAccountIDs = useMemo(() => PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails), [policy?.employeeList, personalDetails]); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [selectedEmployees, setSelectedEmployees] = useState([]); @@ -100,10 +100,10 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, */ const filterPersonalDetails = (members: OnyxEntry, details: OnyxEntry): PersonalDetailsList => Object.keys(members ?? {}).reduce((result, key) => { - if (details?.[key]) { + if (details?.[policyMemberEmailsToAccountIDs[key] ?? '']) { return { ...result, - [key]: details[policyMemberEmailsToAccountIDs[key] ?? ''], + [policyMemberEmailsToAccountIDs[key] ?? '']: details[policyMemberEmailsToAccountIDs[key] ?? ''], }; } return result; @@ -162,7 +162,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [policy?.employeeList]); + }, [policy?.employeeList, policyMemberEmailsToAccountIDs]); useEffect(() => { const isReconnecting = prevIsOffline && !isOffline; @@ -171,12 +171,14 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, } getWorkspaceMembers(); }, [isOffline, prevIsOffline, getWorkspaceMembers]); + /** * Open the modal to invite a user */ const inviteUser = () => { Navigation.navigate(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID)); }; + /** * Remove selected users from the workspace * Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details @@ -193,6 +195,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, setSelectedEmployees([]); setRemoveMembersConfirmModalVisible(false); }; + /** * Show the modal to confirm removal of the selected members */ @@ -202,6 +205,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, } setRemoveMembersConfirmModalVisible(true); }; + /** * Add or remove all users passed from the selectedEmployees list */ @@ -218,6 +222,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, validateSelection(); }; + /** * Add user from the selectedEmployees list */ @@ -228,6 +233,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }, [validateSelection], ); + /** * Remove user from the selectedEmployees list */ @@ -238,6 +244,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }, [validateSelection], ); + /** * Toggle user from the selectedEmployees list */ @@ -256,6 +263,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }, [selectedEmployees, addUser, removeUser], ); + /** Opens the member details page */ const openMemberDetails = useCallback( (item: MemberOption) => { @@ -268,6 +276,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }, [isPolicyAdmin, policy, policyID, route.params.policyID], ); + /** * Dismisses the errors on one item */ @@ -281,6 +290,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }, [route.params.policyID], ); + /** * Check if the policy member is deleted from the workspace */ @@ -288,6 +298,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, (policyMember: PolicyMember): boolean => !isOffline && policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyMember.errors), [isOffline], ); + const policyOwner = policy?.owner; const currentUserLogin = currentUserPersonalDetails.login; const invitedPrimaryToSecondaryLogins = invertObject(policy?.primaryLoginsInvited ?? {}); @@ -377,7 +388,9 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, styles.textStrong, translate, ]); + const data = useMemo(() => getUsers(), [getUsers]); + useEffect(() => { if (!isFocused) { return; @@ -412,6 +425,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, )} ); + const getCustomListHeader = () => { const header = ( @@ -428,6 +442,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, } return {header}; }; + const changeUserRole = (role: typeof CONST.POLICY.ROLE.ADMIN | typeof CONST.POLICY.ROLE.USER) => { if (!isEmptyObject(errors)) { return; @@ -438,6 +453,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, Policy.updateWorkspaceMembersRole(route.params.policyID, accountIDsToUpdate, role); setSelectedEmployees([]); }; + const getBulkActionsButtonOptions = () => { const options: Array> = [ { @@ -469,6 +485,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, } return options; }; + const getHeaderButtons = () => { if (!isPolicyAdmin) { return null; @@ -500,6 +517,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ); }; + return ( ); } + WorkspaceMembersPage.displayName = 'WorkspaceMembersPage'; + export default withCurrentUserPersonalDetails( withPolicyAndFullscreenLoading( withOnyx({ diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index d642960255c5..230c3595bf4b 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -65,7 +65,7 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor return; } - const accountID = Number(policyMemberEmailsToAccountIDs[email] || ''); + const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? ''); const details = personalDetails?.[accountID]; if (!details) { diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx index d0d12dad3fdf..51d956a7614d 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx @@ -60,8 +60,10 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR const policyAdminDetails: MemberOption[] = []; const authorizedPayerDetails: MemberOption[] = []; + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails); + Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { - const accountID = Object.values(personalDetails ?? {}).find((details) => details?.login === email)?.accountID ?? 0; + const accountID = policyMemberEmailsToAccountIDs?.[email] ?? ''; const details = personalDetails?.[accountID]; if (!details) { Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with accountID: ${accountID}`); From afa82392be784a00edf3cbbf1683f160de223dfb Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 8 Apr 2024 09:41:23 +0200 Subject: [PATCH 12/22] Update PolicyMembersUtils --- src/libs/PolicyMembersUtils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/PolicyMembersUtils.ts b/src/libs/PolicyMembersUtils.ts index 59d1bff22597..43464fca1a18 100644 --- a/src/libs/PolicyMembersUtils.ts +++ b/src/libs/PolicyMembersUtils.ts @@ -5,7 +5,7 @@ import type {PersonalDetailsList, Policy} from '@src/types/onyx'; import {getCurrentUserAccountID} from './actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from './PolicyUtils'; -let policies: OnyxCollection; +let policies: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, @@ -15,9 +15,7 @@ Onyx.connect({ let allPersonalDetails: OnyxEntry = {}; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, - callback: (val) => { - allPersonalDetails = val; - }, + callback: (val) => (allPersonalDetails = val), }); function getPolicyMemberAccountIDs(policyID?: string) { From d682d3278e13430f64554640b188369a679d0191 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 8 Apr 2024 11:50:12 +0200 Subject: [PATCH 13/22] Fix bug with selected item on WorkspaceMemberDetailsRoleSelectionPage --- .../members/WorkspaceMemberDetailsRoleSelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx index 36f24b8922da..c591d47975b7 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx @@ -47,7 +47,7 @@ function WorkspaceMemberDetailsRoleSelectionPage({route, personalDetails, policy { value: CONST.POLICY.ROLE.USER, text: translate('common.member'), - isSelected: member?.role === CONST.POLICY.ROLE.USER, + isSelected: member?.role !== CONST.POLICY.ROLE.ADMIN, keyForList: CONST.POLICY.ROLE.USER, }, ]; From 657e5ec3264c3083345fab1591696af55273fc7d Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 8 Apr 2024 18:26:40 +0200 Subject: [PATCH 14/22] Fix comments --- .../AppNavigator/ReportScreenIDSetter.ts | 2 +- src/libs/PolicyMembersUtils.ts | 16 +++++----------- src/libs/PolicyUtils.ts | 11 ++++++----- src/libs/actions/Policy.ts | 2 +- src/pages/workspace/WorkspaceInvitePage.tsx | 3 +-- src/pages/workspace/WorkspaceMembersPage.tsx | 12 ++++++------ .../workflows/WorkspaceWorkflowsApproverPage.tsx | 2 +- .../workflows/WorkspaceWorkflowsPayerPage.tsx | 2 +- 8 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index a9fbf19b4477..56efcdcf75e3 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -73,7 +73,7 @@ function ReportScreenIDSetter({route, reports, policies, navigation, isFirstTime return; } - const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policies, personalDetails, activeWorkspaceID, accountID); + const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policies, activeWorkspaceID, accountID); // If there is no reportID in route, try to find last accessed and use it for setParams const reportID = getLastAccessedReportID( diff --git a/src/libs/PolicyMembersUtils.ts b/src/libs/PolicyMembersUtils.ts index 43464fca1a18..f826f4fd257a 100644 --- a/src/libs/PolicyMembersUtils.ts +++ b/src/libs/PolicyMembersUtils.ts @@ -1,21 +1,15 @@ -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetailsList, Policy} from '@src/types/onyx'; +import type {Policy} from '@src/types/onyx'; import {getCurrentUserAccountID} from './actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from './PolicyUtils'; -let policies: OnyxCollection = {}; +let allPolicies: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, - callback: (value) => (policies = value), -}); - -let allPersonalDetails: OnyxEntry = {}; -Onyx.connect({ - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - callback: (val) => (allPersonalDetails = val), + callback: (value) => (allPolicies = value), }); function getPolicyMemberAccountIDs(policyID?: string) { @@ -25,7 +19,7 @@ function getPolicyMemberAccountIDs(policyID?: string) { const currentUserAccountID = getCurrentUserAccountID(); - return getPolicyMembersByIdWithoutCurrentUser(policies, allPersonalDetails, policyID, currentUserAccountID); + return getPolicyMembersByIdWithoutCurrentUser(allPolicies, policyID, currentUserAccountID); } export default getPolicyMemberAccountIDs; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 135a0f96d684..643a13f114ff 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,13 +4,14 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; +import type { Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getPolicyIDFromState from './Navigation/getPolicyIDFromState'; import Navigation, {navigationRef} from './Navigation/Navigation'; import type {RootStackParamList, State} from './Navigation/types'; +import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; type MemberEmailsToAccountIDs = Record; @@ -133,7 +134,7 @@ const isPolicyMember = (policyID: string, policies: OnyxCollection): boo * * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(employeeList: PolicyMembers | undefined, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { +function getMemberAccountIDsForWorkspace(employeeList: PolicyMembers | undefined): MemberEmailsToAccountIDs { const members = employeeList ?? {}; const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; Object.keys(members).forEach((email) => { @@ -141,7 +142,7 @@ function getMemberAccountIDsForWorkspace(employeeList: PolicyMembers | undefined if (Object.keys(member?.errors ?? {})?.length > 0) { return; } - const personalDetail = Object.values(personalDetails ?? {})?.find((details) => details?.login === email); + const personalDetail = getPersonalDetailByEmail(email); if (!personalDetail?.login) { return; } @@ -277,9 +278,9 @@ function getPathWithoutPolicyID(path: string) { return path.replace(CONST.REGEX.PATH_WITHOUT_POLICY_ID, '/'); } -function getPolicyMembersByIdWithoutCurrentUser(policies: OnyxCollection, personalDetails: OnyxEntry, currentPolicyID?: string, currentUserAccountID?: number) { +function getPolicyMembersByIdWithoutCurrentUser(policies: OnyxCollection, currentPolicyID?: string, currentUserAccountID?: number) { const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${currentPolicyID}`] ?? null; - const policyMemberEmailsToAccountIDs = getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails); + const policyMemberEmailsToAccountIDs = getMemberAccountIDsForWorkspace(policy?.employeeList); return Object.values(policyMemberEmailsToAccountIDs) .map((policyMemberAccountID) => Number(policyMemberAccountID)) .filter((policyMemberAccountID) => policyMemberAccountID !== currentUserAccountID); diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 19a47d9663a7..af685c517a57 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1275,7 +1275,7 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I * Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details */ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, welcomeNote: string, policyID: string) { - const onyxKey = `${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const; + const policyKey = `${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const; const logins = Object.keys(invitedEmailsToAccountIDs).map((memberLogin) => PhoneNumber.addSMSDomainIfPhoneNumber(memberLogin)); const accountIDs = Object.values(invitedEmailsToAccountIDs); diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 108da0c4c3c8..7ba9cf10bba0 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -55,7 +55,6 @@ type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & function WorkspaceInvitePage({ route, - personalDetails: personalDetailsProp, betas, invitedEmailsToAccountIDsDraft, policy, @@ -69,7 +68,7 @@ function WorkspaceInvitePage({ const [personalDetails, setPersonalDetails] = useState([]); const [usersToInvite, setUsersToInvite] = useState([]); const openWorkspaceInvitePage = () => { - const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetailsProp); + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); Policy.openWorkspaceInvitePage(route.params.policyID, Object.keys(policyMemberEmailsToAccountIDs)); }; const {options, areOptionsInitialized} = useOptionsList({ diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 5b7631b69682..5458079f5d92 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -71,7 +71,7 @@ function invertObject(object: Record): Record { type MemberOption = Omit & {accountID: number}; function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails, isLoadingReportData = true}: WorkspaceMembersPageProps) { - const policyMemberEmailsToAccountIDs = useMemo(() => PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails), [policy?.employeeList, personalDetails]); + const policyMemberEmailsToAccountIDs = useMemo(() => PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList), [policy?.employeeList]); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [selectedEmployees, setSelectedEmployees] = useState([]); @@ -112,8 +112,8 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, * Get members for the current workspace */ const getWorkspaceMembers = useCallback(() => { - Policy.openWorkspaceMembersPage(route.params.policyID, Object.keys(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails))); - }, [route.params.policyID, policy?.employeeList, personalDetails]); + Policy.openWorkspaceMembersPage(route.params.policyID, Object.keys(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList))); + }, [route.params.policyID, policy?.employeeList]); /** * Check if the current selection includes members that cannot be removed @@ -157,7 +157,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, return res?.accountID ?? id; }); // This is an equivalent of the lodash intersection function. The reduce method below is used to filter the items that exist in both arrays. - return [prevSelectedElements, Object.values(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails))].reduce((prev, members) => + return [prevSelectedElements, Object.values(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList))].reduce((prev, members) => prev.filter((item) => members.includes(item)), ); }); @@ -465,7 +465,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, ]; if (PolicyUtils.isPaidGroupPolicy(policy)) { - if (selectedEmployees.find((employee) => policy?.employeeList?.[policyMemberEmailsToAccountIDs[employee]]?.role === CONST.POLICY.ROLE.ADMIN)) { + if (selectedEmployees.find((employeeEmail) => policy?.employeeList?.[policyMemberEmailsToAccountIDs[employeeEmail]]?.role === CONST.POLICY.ROLE.ADMIN)) { options.push({ text: translate('workspace.people.makeMember'), value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_MEMBER, @@ -474,7 +474,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }); } - if (selectedEmployees.find((employee) => policy?.employeeList?.[policyMemberEmailsToAccountIDs[employee]]?.role === CONST.POLICY.ROLE.USER)) { + if (selectedEmployees.find((employeeEmail) => policy?.employeeList?.[policyMemberEmailsToAccountIDs[employeeEmail]]?.role === CONST.POLICY.ROLE.USER)) { options.push({ text: translate('workspace.people.makeAdmin'), value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_ADMIN, diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 230c3595bf4b..ccf446ab077b 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -58,7 +58,7 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor const policyMemberDetails: MemberOption[] = []; const approverDetails: MemberOption[] = []; - const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails); + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { if (isDeletedPolicyMember(policyMember)) { diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx index 51d956a7614d..6691fc7f002c 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx @@ -60,7 +60,7 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR const policyAdminDetails: MemberOption[] = []; const authorizedPayerDetails: MemberOption[] = []; - const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, personalDetails); + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { const accountID = policyMemberEmailsToAccountIDs?.[email] ?? ''; From 57ae55de808f961e0e4c965f5009a85100cde179 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 8 Apr 2024 18:34:57 +0200 Subject: [PATCH 15/22] Fix eslint issues --- src/libs/actions/Policy.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index af685c517a57..3d7b5e667b4b 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1300,7 +1300,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: onyxKey, + key: policyKey, // Convert to object with each key containing {pendingAction: ‘add’} value: { @@ -1315,7 +1315,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: onyxKey, + key: policyKey, value: { employeeList: successMembersState, }, @@ -1328,7 +1328,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: onyxKey, + key: policyKey, // Convert to object with each key containing the error. We don’t // need to remove the members since that is handled by onClose of OfflineWithFeedback. From fd47bb46c2c295d95ba1145b9a586235eeb709f6 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 8 Apr 2024 18:54:29 +0200 Subject: [PATCH 16/22] Fix ts and eslint issues --- src/libs/PolicyUtils.ts | 2 +- src/pages/workspace/WorkspaceInvitePage.tsx | 9 +-------- tests/perf-test/PolicyUtils.perf-test.ts | 12 ++++-------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 643a13f114ff..45539796a7bd 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,7 +4,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type { Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; +import type {Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 7ba9cf10bba0..660f7b2e1ed8 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -53,14 +53,7 @@ type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInvitePageOnyxProps & StackScreenProps; -function WorkspaceInvitePage({ - route, - betas, - invitedEmailsToAccountIDsDraft, - policy, - isLoadingReportData = true, - didScreenTransitionEnd, -}: WorkspaceInvitePageProps) { +function WorkspaceInvitePage({route, betas, invitedEmailsToAccountIDsDraft, policy, isLoadingReportData = true, didScreenTransitionEnd}: WorkspaceInvitePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchTerm, setSearchTerm] = useState(''); diff --git a/tests/perf-test/PolicyUtils.perf-test.ts b/tests/perf-test/PolicyUtils.perf-test.ts index 98403b310f25..4f882ae9f057 100644 --- a/tests/perf-test/PolicyUtils.perf-test.ts +++ b/tests/perf-test/PolicyUtils.perf-test.ts @@ -1,33 +1,29 @@ import {measureFunction} from 'reassure'; import {getMemberAccountIDsForWorkspace} from '@libs/PolicyUtils'; -import type {PersonalDetails, PolicyMember} from '@src/types/onyx'; import createCollection from '../utils/collections/createCollection'; -import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicyMember from '../utils/collections/policyMembers'; describe('PolicyUtils', () => { describe('getMemberAccountIDsForWorkspace', () => { test('500 policy members with personal details', async () => { - const policyMembers = createCollection( + const policyMembers = createCollection( (_, index) => index, () => createRandomPolicyMember(), ); - const personalDetails = createCollection((_, index) => index, createPersonalDetails); - await measureFunction(() => getMemberAccountIDsForWorkspace(policyMembers, personalDetails)); + await measureFunction(() => getMemberAccountIDsForWorkspace(policyMembers)); }); test('500 policy members with errors and personal details', async () => { - const policyMembers = createCollection( + const policyMembers = createCollection( (_, index) => index, () => ({ ...createRandomPolicyMember(), errors: {error: 'Error message'}, }), ); - const personalDetails = createCollection((_, index) => index, createPersonalDetails); - await measureFunction(() => getMemberAccountIDsForWorkspace(policyMembers, personalDetails)); + await measureFunction(() => getMemberAccountIDsForWorkspace(policyMembers)); }); }); }); From 9809d4503db916deb65d0e73fb2f076852954b38 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 9 Apr 2024 20:28:12 +0200 Subject: [PATCH 17/22] Fix bug and rename policyMembers --- src/ONYXKEYS.ts | 2 +- .../AppNavigator/ReportScreenIDSetter.ts | 4 +- src/libs/Navigation/dismissModalWithReport.ts | 4 +- ...bscribeToReportCommentPushNotifications.ts | 6 +-- ...ersUtils.ts => PolicyEmployeeListUtils.ts} | 8 ++-- src/libs/PolicyUtils.ts | 22 +++++------ src/libs/ReportUtils.ts | 4 +- src/libs/actions/Policy.ts | 14 +++---- src/libs/actions/Report.ts | 6 +-- src/pages/ReportDetailsPage.tsx | 12 +++--- src/pages/RoomInvitePage.tsx | 4 +- src/pages/RoomMembersPage.tsx | 6 +-- src/pages/home/HeaderView.tsx | 6 +-- src/pages/home/sidebar/SidebarLinksData.js | 4 +- src/pages/workspace/WorkspaceInvitePage.tsx | 2 +- src/pages/workspace/WorkspaceMembersPage.tsx | 38 ++++++++++--------- .../WorkspaceWorkflowsApproverPage.tsx | 28 +++++++------- .../workflows/WorkspaceWorkflowsPayerPage.tsx | 20 +++++----- src/types/onyx/Policy.ts | 2 +- .../{PolicyMember.ts => PolicyEmployee.ts} | 8 ++-- src/types/onyx/index.ts | 8 ++-- tests/perf-test/PolicyUtils.perf-test.ts | 14 +++---- tests/utils/collections/policyEmployeeList.ts | 9 +++++ tests/utils/collections/policyMembers.ts | 9 ----- 24 files changed, 122 insertions(+), 118 deletions(-) rename src/libs/{PolicyMembersUtils.ts => PolicyEmployeeListUtils.ts} (65%) rename src/types/onyx/{PolicyMember.ts => PolicyEmployee.ts} (73%) create mode 100644 tests/utils/collections/policyEmployeeList.ts delete mode 100644 tests/utils/collections/policyMembers.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index bc3b2da49ecd..6df9a534d689 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -517,7 +517,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; - [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; + [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyEmployeeList; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index 56efcdcf75e3..5407a451682a 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -3,7 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import usePermissions from '@hooks/usePermissions'; -import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, Report, ReportMetadata} from '@src/types/onyx'; @@ -73,7 +73,7 @@ function ReportScreenIDSetter({route, reports, policies, navigation, isFirstTime return; } - const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policies, activeWorkspaceID, accountID); + const policyMemberAccountIDs = getPolicyEmployeeListByIdWithoutCurrentUser(policies, activeWorkspaceID, accountID); // If there is no reportID in route, try to find last accessed and use it for setParams const reportID = getLastAccessedReportID( diff --git a/src/libs/Navigation/dismissModalWithReport.ts b/src/libs/Navigation/dismissModalWithReport.ts index 2622cc2b9855..c0405c2c9da0 100644 --- a/src/libs/Navigation/dismissModalWithReport.ts +++ b/src/libs/Navigation/dismissModalWithReport.ts @@ -3,7 +3,7 @@ import type {NavigationContainerRef} from '@react-navigation/native'; import {StackActions} from '@react-navigation/native'; import {findLastIndex} from 'lodash'; import Log from '@libs/Log'; -import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils'; +import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils'; import {doesReportBelongToWorkspace} from '@libs/ReportUtils'; import NAVIGATORS from '@src/NAVIGATORS'; import ROUTES from '@src/ROUTES'; @@ -47,7 +47,7 @@ function dismissModalWithReport(targetReport: Report | EmptyObject, navigationRe if (targetReport.reportID !== getTopmostReportId(state)) { const reportState = getStateFromPath(ROUTES.REPORT_WITH_ID.getRoute(targetReport.reportID)); const policyID = getPolicyIDFromState(state as State); - const policyMemberAccountIDs = getPolicyMemberAccountIDs(policyID); + const policyMemberAccountIDs = getPolicyEmployeeAccountIDs(policyID); const shouldOpenAllWorkspace = isEmptyObject(targetReport) ? true : !doesReportBelongToWorkspace(targetReport, policyMemberAccountIDs, policyID); if (shouldOpenAllWorkspace) { diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts index 71f68d69acc7..7b8340c76c73 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx'; import * as OnyxUpdates from '@libs/actions/OnyxUpdates'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; -import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils'; +import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils'; import {extractPolicyIDFromPath} from '@libs/PolicyUtils'; import {doesReportBelongToWorkspace, getReport} from '@libs/ReportUtils'; import Visibility from '@libs/Visibility'; @@ -60,9 +60,9 @@ export default function subscribeToReportCommentPushNotifications() { const policyID = lastVisitedPath && extractPolicyIDFromPath(lastVisitedPath); const report = getReport(reportID.toString()); - const policyMembersAccountIDs = policyID ? getPolicyMemberAccountIDs(policyID) : []; + const policyEmployeeAccountIDs = policyID ? getPolicyEmployeeAccountIDs(policyID) : []; - const reportBelongsToWorkspace = policyID && !isEmptyObject(report) && doesReportBelongToWorkspace(report, policyMembersAccountIDs, policyID); + const reportBelongsToWorkspace = policyID && !isEmptyObject(report) && doesReportBelongToWorkspace(report, policyEmployeeAccountIDs, policyID); Log.info('[PushNotification] onSelected() - called', false, {reportID, reportActionID}); Navigation.isNavigationReady() diff --git a/src/libs/PolicyMembersUtils.ts b/src/libs/PolicyEmployeeListUtils.ts similarity index 65% rename from src/libs/PolicyMembersUtils.ts rename to src/libs/PolicyEmployeeListUtils.ts index f826f4fd257a..305ffda87e28 100644 --- a/src/libs/PolicyMembersUtils.ts +++ b/src/libs/PolicyEmployeeListUtils.ts @@ -3,7 +3,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; import {getCurrentUserAccountID} from './actions/Report'; -import {getPolicyMembersByIdWithoutCurrentUser} from './PolicyUtils'; +import {getPolicyEmployeeListByIdWithoutCurrentUser} from './PolicyUtils'; let allPolicies: OnyxCollection = {}; Onyx.connect({ @@ -12,14 +12,14 @@ Onyx.connect({ callback: (value) => (allPolicies = value), }); -function getPolicyMemberAccountIDs(policyID?: string) { +function getPolicyEmployeeAccountIDs(policyID?: string) { if (!policyID) { return []; } const currentUserAccountID = getCurrentUserAccountID(); - return getPolicyMembersByIdWithoutCurrentUser(allPolicies, policyID, currentUserAccountID); + return getPolicyEmployeeListByIdWithoutCurrentUser(allPolicies, policyID, currentUserAccountID); } -export default getPolicyMemberAccountIDs; +export default getPolicyEmployeeAccountIDs; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 45539796a7bd..f3bc13295530 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,7 +4,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; +import type {Policy, PolicyCategories, PolicyEmployeeList, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -127,14 +127,14 @@ const isPolicyAdmin = (policy: OnyxEntry | EmptyObject): boolean => poli */ const isFreeGroupPolicy = (policy: OnyxEntry | EmptyObject): boolean => policy?.type === CONST.POLICY.TYPE.FREE; -const isPolicyMember = (policyID: string, policies: OnyxCollection): boolean => Object.values(policies ?? {}).some((policy) => policy?.id === policyID); +const isPolicyEmployee = (policyID: string, policies: OnyxCollection): boolean => Object.values(policies ?? {}).some((policy) => policy?.id === policyID); /** * Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID. * * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(employeeList: PolicyMembers | undefined): MemberEmailsToAccountIDs { +function getMemberAccountIDsForWorkspace(employeeList: PolicyEmployeeList | undefined): MemberEmailsToAccountIDs { const members = employeeList ?? {}; const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; Object.keys(members).forEach((email) => { @@ -154,13 +154,13 @@ function getMemberAccountIDsForWorkspace(employeeList: PolicyMembers | undefined /** * Get login list that we should not show in the workspace invite options */ -function getIneligibleInvitees(employeeList?: PolicyMembers): string[] { - const members = employeeList ?? {}; +function getIneligibleInvitees(employeeList?: PolicyEmployeeList): string[] { + const policyEmployeeList = employeeList ?? {}; const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; - Object.keys(members).forEach((email) => { - const policyMember = members?.[email]; + Object.keys(policyEmployeeList).forEach((email) => { + const policyEmployee = policyEmployeeList?.[email]; // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). - if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { + if (policyEmployee?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyEmployee?.errors ?? {}).length > 0) { return; } if (!email) { @@ -278,7 +278,7 @@ function getPathWithoutPolicyID(path: string) { return path.replace(CONST.REGEX.PATH_WITHOUT_POLICY_ID, '/'); } -function getPolicyMembersByIdWithoutCurrentUser(policies: OnyxCollection, currentPolicyID?: string, currentUserAccountID?: number) { +function getPolicyEmployeeListByIdWithoutCurrentUser(policies: OnyxCollection, currentPolicyID?: string, currentUserAccountID?: number) { const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${currentPolicyID}`] ?? null; const policyMemberEmailsToAccountIDs = getMemberAccountIDsForWorkspace(policy?.employeeList); return Object.values(policyMemberEmailsToAccountIDs) @@ -344,11 +344,11 @@ export { getCleanedTagName, getCountOfEnabledTagsOfList, isPendingDeletePolicy, - isPolicyMember, + isPolicyEmployee, isPaidGroupPolicy, extractPolicyIDFromPath, getPathWithoutPolicyID, - getPolicyMembersByIdWithoutCurrentUser, + getPolicyEmployeeListByIdWithoutCurrentUser, goBackFromInvalidPolicy, isPolicyFeatureEnabled, hasTaxRateError, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c2bb77bd97ce..933198231a88 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4887,7 +4887,7 @@ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, isPolicyMember: boolean): boolean { +function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boolean { if (!report?.visibility) { if ( report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS || @@ -4900,7 +4900,7 @@ function canLeaveRoom(report: OnyxEntry, isPolicyMember: boolean): boole // DM chats don't have a chatType return false; } - } else if (isPublicAnnounceRoom(report) && isPolicyMember) { + } else if (isPublicAnnounceRoom(report) && isPolicyEmployee) { return false; } return true; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 1a63be771680..e79d02914935 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -77,7 +77,7 @@ import type { Policy, PolicyCategories, PolicyCategory, - PolicyMember, + PolicyEmployee, PolicyOwnershipChangeChecks, PolicyTag, PolicyTagList, @@ -824,9 +824,9 @@ function removeMembers(accountIDs: number[], policyID: string) { const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policyID, accountIDs); - const optimisticMembersState: OnyxCollection = {}; - const successMembersState: OnyxCollection = {}; - const failureMembersState: OnyxCollection = {}; + const optimisticMembersState: OnyxCollection = {}; + const successMembersState: OnyxCollection = {}; + const failureMembersState: OnyxCollection = {}; emailList.forEach((email) => { optimisticMembersState[email] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}; successMembersState[email] = null; @@ -1286,9 +1286,9 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount // create onyx data for policy expense chats for each new member const membersChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs); - const optimisticMembersState: OnyxCollection = {}; - const successMembersState: OnyxCollection = {}; - const failureMembersState: OnyxCollection = {}; + const optimisticMembersState: OnyxCollection = {}; + const successMembersState: OnyxCollection = {}; + const failureMembersState: OnyxCollection = {}; Object.keys(invitedEmailsToAccountIDs).forEach((email) => { optimisticMembersState[email] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, role: CONST.POLICY.ROLE.USER}; successMembersState[email] = {pendingAction: null}; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 91128ac89178..fd44f976055f 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -53,7 +53,7 @@ import Navigation from '@libs/Navigation/Navigation'; import LocalNotification from '@libs/Notification/LocalNotification'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PhoneNumber from '@libs/PhoneNumber'; -import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils'; +import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils'; import {extractPolicyIDFromPath} from '@libs/PolicyUtils'; import processReportIDDeeplink from '@libs/processReportIDDeeplink'; import * as Pusher from '@libs/Pusher/pusher'; @@ -2137,8 +2137,8 @@ function showReportActionNotification(reportID: string, reportAction: ReportActi const onClick = () => Modal.close(() => { const policyID = lastVisitedPath && extractPolicyIDFromPath(lastVisitedPath); - const policyMembersAccountIDs = policyID ? getPolicyMemberAccountIDs(policyID) : []; - const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(report, policyMembersAccountIDs, policyID) : false; + const policyEmployeeAccountIDs = policyID ? getPolicyEmployeeAccountIDs(policyID) : []; + const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(report, policyEmployeeAccountIDs, policyID) : false; if (!reportBelongsToWorkspace) { Navigation.navigateWithSwitchPolicyID({route: ROUTES.HOME}); } diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 9093bf32b9dd..b4e74252fd61 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -65,7 +65,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD const route = useRoute(); const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`], [policies, report?.policyID]); const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy ?? null), [policy]); - const isPolicyMember = useMemo(() => PolicyUtils.isPolicyMember(report?.policyID ?? '', policies), [report?.policyID, policies]); + const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID ?? '', policies), [report?.policyID, policies]); const shouldUseFullTitle = useMemo(() => ReportUtils.shouldUseFullTitleToDisplay(report), [report]); const isChatRoom = useMemo(() => ReportUtils.isChatRoom(report), [report]); const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(report), [report]); @@ -122,9 +122,9 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD // - The report is not a user created room with participants to show i.e. DM, Group Chat, etc // - The report is a user created room and the room and the current user is a workspace member i.e. non-workspace members should not see this option. if ( - ((isDefaultRoom && isChatThread && isPolicyMember) || + ((isDefaultRoom && isChatThread && isPolicyEmployee) || (!isUserCreatedPolicyRoom && participants.length) || - (isUserCreatedPolicyRoom && (isPolicyMember || (isChatThread && !ReportUtils.isPublicRoom(report))))) && + (isUserCreatedPolicyRoom && (isPolicyEmployee || (isChatThread && !ReportUtils.isPublicRoom(report))))) && !ReportUtils.isConciergeChatReport(report) ) { items.push({ @@ -142,8 +142,8 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD }, }); } else if ( - (isUserCreatedPolicyRoom && (!participants.length || !isPolicyMember)) || - ((isDefaultRoom || ReportUtils.isPolicyExpenseChat(report)) && isChatThread && !isPolicyMember) + (isUserCreatedPolicyRoom && (!participants.length || !isPolicyEmployee)) || + ((isDefaultRoom || ReportUtils.isPolicyExpenseChat(report)) && isChatThread && !isPolicyEmployee) ) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.INVITE, @@ -179,7 +179,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD } return items; - }, [isArchivedRoom, participants.length, isChatThread, isMoneyRequestReport, report, isGroupDMChat, isPolicyMember, isUserCreatedPolicyRoom, session, isSelfDM, isDefaultRoom]); + }, [isArchivedRoom, participants.length, isChatThread, isMoneyRequestReport, report, isGroupDMChat, isPolicyEmployee, isUserCreatedPolicyRoom, session, isSelfDM, isDefaultRoom]); const displayNamesWithTooltips = useMemo(() => { const hasMultipleParticipants = participants.length > 1; diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 49e53381e040..45a66d14e944 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -146,8 +146,8 @@ function RoomInvitePage({betas, report, policies}: RoomInvitePageProps) { // Non policy members should not be able to view the participants of a room const reportID = report?.reportID; - const isPolicyMember = useMemo(() => (report?.policyID ? PolicyUtils.isPolicyMember(report.policyID, policies as Record) : false), [report?.policyID, policies]); - const backRoute = useMemo(() => reportID && (isPolicyMember ? ROUTES.ROOM_MEMBERS.getRoute(reportID) : ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID)), [isPolicyMember, reportID]); + const isPolicyEmployee = useMemo(() => (report?.policyID ? PolicyUtils.isPolicyEmployee(report.policyID, policies as Record) : false), [report?.policyID, policies]); + const backRoute = useMemo(() => reportID && (isPolicyEmployee ? ROUTES.ROOM_MEMBERS.getRoute(reportID) : ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID)), [isPolicyEmployee, reportID]); const reportName = useMemo(() => ReportUtils.getReportName(report), [report]); const inviteUsers = useCallback(() => { if (!validate()) { diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index d025a3bde265..009eaaf15a3c 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -224,11 +224,11 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { return result; }; - const isPolicyMember = useMemo(() => { + const isPolicyEmployee = useMemo(() => { if (!report?.policyID || policies === null) { return false; } - return PolicyUtils.isPolicyMember(report.policyID, policies); + return PolicyUtils.isPolicyEmployee(report.policyID, policies); }, [report?.policyID, policies]); const data = getMemberOptions(); const headerMessage = searchValue.trim() && !data.length ? translate('roomMembersPage.memberNotFound') : ''; @@ -240,7 +240,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { > { diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 1b8957b833b0..ae2e78ff4af5 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -94,8 +94,8 @@ function HeaderView({report, personalDetails, parentReport, parentReportAction, const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(report, parentReportAction); const isWhisperAction = ReportActionsUtils.isWhisperAction(parentReportAction); const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(report); - const isPolicyMember = useMemo(() => !isEmptyObject(policy), [policy]); - const canLeaveRoom = ReportUtils.canLeaveRoom(report, isPolicyMember); + const isPolicyEmployee = useMemo(() => !isEmptyObject(policy), [policy]); + const canLeaveRoom = ReportUtils.canLeaveRoom(report, isPolicyEmployee); const reportDescription = ReportUtils.getReportDescriptionText(report); const policyName = ReportUtils.getPolicyName(report, true); const policyDescription = ReportUtils.getPolicyDescriptionText(policy); @@ -152,7 +152,7 @@ function HeaderView({report, personalDetails, parentReport, parentReportAction, onSelected: join, }); } else if (canLeave) { - const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyMember; + const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyEmployee; threeDotMenuItems.push({ icon: Expensicons.ChatBubbles, text: translate('common.leave'), diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 792e0f9ad7be..d2d02960186f 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -16,7 +16,7 @@ import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; -import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import reportPropTypes from '@pages/reportPropTypes'; @@ -128,7 +128,7 @@ function SidebarLinksData({ const {translate} = useLocalize(); const prevPriorityMode = usePrevious(priorityMode); - const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policies, activeWorkspaceID, currentUserPersonalDetails.accountID); + const policyMemberAccountIDs = getPolicyEmployeeListByIdWithoutCurrentUser(policies, activeWorkspaceID, currentUserPersonalDetails.accountID); // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 660f7b2e1ed8..4ca2ebab80b2 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -91,7 +91,7 @@ function WorkspaceInvitePage({route, betas, invitedEmailsToAccountIDsDraft, poli const newSelectedOptionsDict: Record = {}; const inviteOptions = OptionsListUtils.getMemberInviteOptions(options.personalDetails, betas ?? [], searchTerm, excludedUsers, true); - // Update selectedOptions with the latest personalDetails and policyMembers information + // Update selectedOptions with the latest personalDetails and policyEmployeeList information const detailsMap: Record = {}; inviteOptions.personalDetails.forEach((detail) => { if (!detail.login) { diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index c796113208d2..adbd06d6a32e 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -42,7 +42,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {InvitedEmailsToAccountIDs, PersonalDetailsList, PolicyMember, PolicyMembers, Session} from '@src/types/onyx'; +import type {InvitedEmailsToAccountIDs, PersonalDetailsList, PolicyEmployee, PolicyEmployeeList, Session} 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'; @@ -98,12 +98,13 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, /** * Get filtered personalDetails list with current employeeList */ - const filterPersonalDetails = (members: OnyxEntry, details: OnyxEntry): PersonalDetailsList => + const filterPersonalDetails = (members: OnyxEntry, details: OnyxEntry): PersonalDetailsList => Object.keys(members ?? {}).reduce((result, key) => { - if (details?.[policyMemberEmailsToAccountIDs[key] ?? '']) { + const memberAccountIdKey = policyMemberEmailsToAccountIDs[key] ?? ''; + if (details?.[memberAccountIdKey]) { return { ...result, - [policyMemberEmailsToAccountIDs[key] ?? '']: details[policyMemberEmailsToAccountIDs[key] ?? ''], + [memberAccountIdKey]: details[memberAccountIdKey], }; } return result; @@ -130,7 +131,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedEmployees, policy?.owner, session?.accountID]); - // useFocusEffect would make getWorkspaceMembers get called twice on fresh login because policyMember is a dependency of getWorkspaceMembers. + // useFocus would make getWorkspaceMembers get called twice on fresh login because policyEmployee is a dependency of getWorkspaceMembers. useEffect(() => { if (!isFocused) { return; @@ -156,10 +157,13 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, const res = Object.values(currentPersonalDetails).find((item) => prevItem?.login === item?.login); return res?.accountID ?? id; }); + + const currentSelectedElements = Object.entries(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList)) + .filter((employee) => policy?.employeeList?.[employee[0]]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) + .map((employee) => employee[1]); + // This is an equivalent of the lodash intersection function. The reduce method below is used to filter the items that exist in both arrays. - return [prevSelectedElements, Object.values(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList))].reduce((prev, members) => - prev.filter((item) => members.includes(item)), - ); + return [prevSelectedElements, currentSelectedElements].reduce((prev, members) => prev.filter((item) => members.includes(item))); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [policy?.employeeList, policyMemberEmailsToAccountIDs]); @@ -294,8 +298,8 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, /** * Check if the policy member is deleted from the workspace */ - const isDeletedPolicyMember = useCallback( - (policyMember: PolicyMember): boolean => !isOffline && policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyMember.errors), + const isDeletedPolicyEmployee = useCallback( + (policyEmployee: PolicyEmployee): boolean => !isOffline && policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyEmployee.errors), [isOffline], ); @@ -305,9 +309,9 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, const getUsers = useCallback((): MemberOption[] => { let result: MemberOption[] = []; - Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { + Object.entries(policy?.employeeList ?? {}).forEach(([email, policyEmployee]) => { const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? ''); - if (isDeletedPolicyMember(policyMember)) { + if (isDeletedPolicyEmployee(policyEmployee)) { return; } @@ -330,7 +334,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, const isSelected = selectedEmployees.includes(accountID); const isOwner = policy?.owner === details.login; - const isAdmin = policyMember.role === CONST.POLICY.ROLE.ADMIN; + const isAdmin = policyEmployee.role === CONST.POLICY.ROLE.ADMIN; let roleBadge = null; if (isOwner || isAdmin) { roleBadge = ( @@ -347,7 +351,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, accountID, isSelected, isDisabledCheckbox: !(isPolicyAdmin && accountID !== policy?.ownerAccountID && accountID !== session?.accountID), - isDisabled: isPolicyAdmin && (policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyMember.errors)), + isDisabled: isPolicyAdmin && (policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyEmployee.errors)), text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)), alternateText: formatPhoneNumber(details?.login ?? ''), rightElement: roleBadge, @@ -359,8 +363,8 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, id: accountID, }, ], - errors: policyMember.errors, - pendingAction: policyMember.pendingAction, + errors: policyEmployee.errors, + pendingAction: policyEmployee.pendingAction, // Note which secondary login was used to invite this primary login invitedSecondaryLogin: details?.login ? invitedPrimaryToSecondaryLogins[details.login] ?? '' : '', }); @@ -372,7 +376,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, currentUserLogin, formatPhoneNumber, invitedPrimaryToSecondaryLogins, - isDeletedPolicyMember, + isDeletedPolicyEmployee, isPolicyAdmin, personalDetails, policy?.owner, diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index ccf446ab077b..d32218616662 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -27,7 +27,7 @@ import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPol import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetailsList, PolicyMember} from '@src/types/onyx'; +import type {PersonalDetailsList, PolicyEmployee} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type WorkspaceWorkflowsApproverPageOnyxProps = { @@ -49,19 +49,19 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const isDeletedPolicyMember = useCallback( - (policyMember: PolicyMember) => !isOffline && policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyMember.errors), + const isDeletedPolicyEmployee = useCallback( + (policyEmployee: PolicyEmployee) => !isOffline && policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyEmployee.errors), [isOffline], ); - const [formattedPolicyMembers, formattedApprover] = useMemo(() => { + const [formattedPolicyEmployeeList, formattedApprover] = useMemo(() => { const policyMemberDetails: MemberOption[] = []; const approverDetails: MemberOption[] = []; const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); - Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { - if (isDeletedPolicyMember(policyMember)) { + Object.entries(policy?.employeeList ?? {}).forEach(([email, policyEmployee]) => { + if (isDeletedPolicyEmployee(policyEmployee)) { return; } @@ -74,7 +74,7 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor } const isOwner = policy?.owner === details.login; - const isAdmin = policyMember.role === CONST.POLICY.ROLE.ADMIN; + const isAdmin = policyEmployee.role === CONST.POLICY.ROLE.ADMIN; let roleBadge = null; if (isOwner || isAdmin) { @@ -91,7 +91,7 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor keyForList: String(accountID), accountID, isSelected: policy?.approver === details.login, - isDisabled: policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyMember.errors), + isDisabled: policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyEmployee.errors), text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)), alternateText: formatPhoneNumber(details?.login ?? ''), rightElement: roleBadge, @@ -103,8 +103,8 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor id: accountID, }, ], - errors: policyMember.errors, - pendingAction: policyMember.pendingAction, + errors: policyEmployee.errors, + pendingAction: policyEmployee.pendingAction, }; if (policy?.approver === details.login) { @@ -114,13 +114,13 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor } }); return [policyMemberDetails, approverDetails]; - }, [personalDetails, translate, policy?.approver, StyleUtils, isDeletedPolicyMember, policy?.owner, styles, policy?.employeeList]); + }, [personalDetails, translate, policy?.approver, StyleUtils, isDeletedPolicyEmployee, policy?.owner, styles, policy?.employeeList]); const sections: MembersSection[] = useMemo(() => { const sectionsArray: MembersSection[] = []; if (searchTerm !== '') { - const filteredOptions = [...formattedApprover, ...formattedPolicyMembers].filter((option) => { + const filteredOptions = [...formattedApprover, ...formattedPolicyEmployeeList].filter((option) => { const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(searchTerm); return !!option.text?.toLowerCase().includes(searchValue) || !!option.login?.toLowerCase().includes(searchValue); }); @@ -141,12 +141,12 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor sectionsArray.push({ title: translate('common.all'), - data: formattedPolicyMembers, + data: formattedPolicyEmployeeList, shouldShow: true, }); return sectionsArray; - }, [formattedPolicyMembers, formattedApprover, searchTerm, translate]); + }, [formattedPolicyEmployeeList, formattedApprover, searchTerm, translate]); const headerMessage = useMemo( () => (searchTerm && !sections[0].data.length ? translate('common.noResultsFound') : ''), diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx index 6691fc7f002c..da51b2c3e8e3 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx @@ -28,7 +28,7 @@ import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPol import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetailsList, PolicyMember} from '@src/types/onyx'; +import type {PersonalDetailsList, PolicyEmployee} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type WorkspaceWorkflowsPayerPageOnyxProps = { @@ -51,8 +51,8 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR const [searchTerm, setSearchTerm] = useState(''); - const isDeletedPolicyMember = useCallback( - (policyMember: PolicyMember) => !isOffline && policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyMember.errors), + const isDeletedPolicyEmployee = useCallback( + (policyEmployee: PolicyEmployee) => !isOffline && policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyEmployee.errors), [isOffline], ); @@ -62,7 +62,7 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); - Object.entries(policy?.employeeList ?? {}).forEach(([email, policyMember]) => { + Object.entries(policy?.employeeList ?? {}).forEach(([email, policyEmployee]) => { const accountID = policyMemberEmailsToAccountIDs?.[email] ?? ''; const details = personalDetails?.[accountID]; if (!details) { @@ -71,8 +71,8 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR } const isOwner = policy?.owner === details?.login; - const isAdmin = policyMember.role === CONST.POLICY.ROLE.ADMIN; - const shouldSkipMember = isDeletedPolicyMember(policyMember) || PolicyUtils.isExpensifyTeam(details?.login) || (!isOwner && !isAdmin); + const isAdmin = policyEmployee.role === CONST.POLICY.ROLE.ADMIN; + const shouldSkipMember = isDeletedPolicyEmployee(policyEmployee) || PolicyUtils.isExpensifyTeam(details?.login) || (!isOwner && !isAdmin); if (shouldSkipMember) { return; @@ -92,7 +92,7 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR keyForList: String(accountID), accountID, isSelected: isAuthorizedPayer, - isDisabled: policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyMember.errors), + isDisabled: policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyEmployee.errors), text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)), alternateText: formatPhoneNumber(details?.login ?? ''), rightElement: roleBadge, @@ -104,8 +104,8 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR id: accountID, }, ], - errors: policyMember.errors, - pendingAction: policyMember.pendingAction ?? isAuthorizedPayer ? policy?.pendingFields?.reimburser : null, + errors: policyEmployee.errors, + pendingAction: policyEmployee.pendingAction ?? isAuthorizedPayer ? policy?.pendingFields?.reimburser : null, }; if (isAuthorizedPayer) { @@ -115,7 +115,7 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR } }); return [policyAdminDetails, authorizedPayerDetails]; - }, [personalDetails, policy?.employeeList, translate, policy?.achAccount?.reimburser, isDeletedPolicyMember, policy?.owner, styles, StyleUtils, policy?.pendingFields?.reimburser]); + }, [personalDetails, policy?.employeeList, translate, policy?.achAccount?.reimburser, isDeletedPolicyEmployee, policy?.owner, styles, StyleUtils, policy?.pendingFields?.reimburser]); const sections: MembersSection[] = useMemo(() => { const sectionsArray: MembersSection[] = []; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index cdfe92b3d09d..1e00bc645aee 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -327,7 +327,7 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< submitsTo?: number; /** The employee list of the policy */ - employeeList?: OnyxTypes.PolicyMembers; + employeeList?: OnyxTypes.PolicyEmployeeList; /** The reimbursement choice for policy */ reimbursementChoice?: ValueOf; diff --git a/src/types/onyx/PolicyMember.ts b/src/types/onyx/PolicyEmployee.ts similarity index 73% rename from src/types/onyx/PolicyMember.ts rename to src/types/onyx/PolicyEmployee.ts index 366a7ef7d530..4a5f374de44a 100644 --- a/src/types/onyx/PolicyMember.ts +++ b/src/types/onyx/PolicyEmployee.ts @@ -1,6 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; -type PolicyMember = OnyxCommon.OnyxValueWithOfflineFeedback<{ +type PolicyEmployee = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Role of the user in the policy */ role?: string; @@ -20,7 +20,7 @@ type PolicyMember = OnyxCommon.OnyxValueWithOfflineFeedback<{ errors?: OnyxCommon.Errors; }>; -type PolicyMembers = Record; +type PolicyEmployeeList = Record; -export default PolicyMember; -export type {PolicyMembers}; +export default PolicyEmployee; +export type {PolicyEmployeeList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index e56066440d80..bcac6a541798 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -38,8 +38,8 @@ import type Policy from './Policy'; import type {PolicyReportField, TaxRate, TaxRates, TaxRatesWithDefault} from './Policy'; import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type PolicyJoinMember from './PolicyJoinMember'; -import type {PolicyMembers} from './PolicyMember'; -import type PolicyMember from './PolicyMember'; +import type {PolicyEmployeeList} from './PolicyEmployee'; +import type PolicyEmployee from './PolicyEmployee'; import type PolicyOwnershipChangeChecks from './PolicyOwnershipChangeChecks'; import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; import type PreferredTheme from './PreferredTheme'; @@ -117,8 +117,8 @@ export type { Policy, PolicyCategories, PolicyCategory, - PolicyMember, - PolicyMembers, + PolicyEmployee, + PolicyEmployeeList, PolicyOwnershipChangeChecks, PolicyTag, PolicyTags, diff --git a/tests/perf-test/PolicyUtils.perf-test.ts b/tests/perf-test/PolicyUtils.perf-test.ts index 4f882ae9f057..0fc0a71bef03 100644 --- a/tests/perf-test/PolicyUtils.perf-test.ts +++ b/tests/perf-test/PolicyUtils.perf-test.ts @@ -1,29 +1,29 @@ import {measureFunction} from 'reassure'; import {getMemberAccountIDsForWorkspace} from '@libs/PolicyUtils'; import createCollection from '../utils/collections/createCollection'; -import createRandomPolicyMember from '../utils/collections/policyMembers'; +import createRandomPolicyEmployeeList from '../utils/collections/policyEmployeeList'; describe('PolicyUtils', () => { describe('getMemberAccountIDsForWorkspace', () => { test('500 policy members with personal details', async () => { - const policyMembers = createCollection( + const policyEmployeeList = createCollection( (_, index) => index, - () => createRandomPolicyMember(), + () => createRandomPolicyEmployeeList(), ); - await measureFunction(() => getMemberAccountIDsForWorkspace(policyMembers)); + await measureFunction(() => getMemberAccountIDsForWorkspace(policyEmployeeList)); }); test('500 policy members with errors and personal details', async () => { - const policyMembers = createCollection( + const policyEmployeeList = createCollection( (_, index) => index, () => ({ - ...createRandomPolicyMember(), + ...createRandomPolicyEmployeeList(), errors: {error: 'Error message'}, }), ); - await measureFunction(() => getMemberAccountIDsForWorkspace(policyMembers)); + await measureFunction(() => getMemberAccountIDsForWorkspace(policyEmployeeList)); }); }); }); diff --git a/tests/utils/collections/policyEmployeeList.ts b/tests/utils/collections/policyEmployeeList.ts new file mode 100644 index 000000000000..9e04c71748b7 --- /dev/null +++ b/tests/utils/collections/policyEmployeeList.ts @@ -0,0 +1,9 @@ +import {randWord} from '@ngneat/falso'; +import type {PolicyEmployee} from '@src/types/onyx'; + +export default function createRandomPolicyEmployeeList(): PolicyEmployee { + return { + role: randWord(), + errors: {}, + }; +} diff --git a/tests/utils/collections/policyMembers.ts b/tests/utils/collections/policyMembers.ts deleted file mode 100644 index 076c8ddb2d3d..000000000000 --- a/tests/utils/collections/policyMembers.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {randWord} from '@ngneat/falso'; -import type {PolicyMember} from '@src/types/onyx'; - -export default function createRandomPolicyMember(): PolicyMember { - return { - role: randWord(), - errors: {}, - }; -} From 851ee9b3e85f6540c0835ffaa106394d4087a80a Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 9 Apr 2024 23:46:07 +0200 Subject: [PATCH 18/22] Fix eslint issue --- src/types/onyx/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 0088b8f2644b..ea0870a7b8c6 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -37,9 +37,9 @@ import type PlaidData from './PlaidData'; import type Policy from './Policy'; import type {PolicyConnectionSyncProgress, PolicyReportField, TaxRate, TaxRates, TaxRatesWithDefault} from './Policy'; import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; -import type PolicyJoinMember from './PolicyJoinMember'; import type {PolicyEmployeeList} from './PolicyEmployee'; import type PolicyEmployee from './PolicyEmployee'; +import type PolicyJoinMember from './PolicyJoinMember'; import type PolicyOwnershipChangeChecks from './PolicyOwnershipChangeChecks'; import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; import type PreferredTheme from './PreferredTheme'; From a4c72ec77cac52c363c381f3e24c2b9ce06405fd Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 10 Apr 2024 10:00:14 +0200 Subject: [PATCH 19/22] Fix bug with paddingTop on RoleModal --- .../members/WorkspaceMemberDetailsRoleSelectionModal.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionModal.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionModal.tsx index 9613a697013e..f1d4334f7069 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionModal.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionModal.tsx @@ -43,7 +43,10 @@ function WorkspaceMemberDetailsRoleSelectionModal({isVisible, items, onRoleChang hideModalContentWhileAnimating useNativeDriver > - + Date: Sun, 14 Apr 2024 17:09:02 +0200 Subject: [PATCH 20/22] Fix eslint issue --- src/pages/workspace/WorkspaceInvitePage.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 940abd4279fb..0310104590e5 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -52,13 +52,7 @@ type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInvitePageOnyxProps & StackScreenProps; -function WorkspaceInvitePage({ - route, - betas, - invitedEmailsToAccountIDsDraft, - policy, - isLoadingReportData = true, -}: WorkspaceInvitePageProps) { +function WorkspaceInvitePage({route, betas, invitedEmailsToAccountIDsDraft, policy, isLoadingReportData = true}: WorkspaceInvitePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchTerm, setSearchTerm] = useState(''); From 91f90025e1803dff889adc3b079231899cdf03dd Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sun, 14 Apr 2024 17:39:17 +0200 Subject: [PATCH 21/22] Add test for employeeList --- tests/actions/PolicyTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 8495f822cdb4..44b69bcb86fe 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -59,6 +59,7 @@ describe('actions/Policy', () => { expect(policy?.owner).toBe(ESH_EMAIL); expect(policy?.isPolicyExpenseChatEnabled).toBe(true); expect(policy?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); + expect(policy?.employeeList).toEqual({[ESH_EMAIL]: {errors: {}, role: CONST.POLICY.ROLE.ADMIN}}); let allReports: OnyxCollection = await new Promise((resolve) => { const connectionID = Onyx.connect({ From 48545bb0bd4d87e80568a3e1c2c33c20de198e80 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 15 Apr 2024 14:52:35 +0200 Subject: [PATCH 22/22] Update leaveWorkspace --- src/libs/actions/Policy.ts | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 90f3ac0aeef4..fffc992746d9 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -968,52 +968,47 @@ function removeMembers(accountIDs: number[], policyID: string) { } function leaveWorkspace(policyID: string) { - const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; const workspaceChats = ReportUtils.getAllWorkspaceReports(policyID); const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, - value: { - [sessionAccountID]: { - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - }, - }, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + employeeList: { + [sessionEmail]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, }, }, ]; + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - [sessionAccountID]: null, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + employeeList: { + [sessionEmail]: null, + }, }, }, ]; const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: membersListKey, - value: { - [sessionAccountID]: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericRemove'), - }, - }, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { pendingAction: policy?.pendingAction, + employeeList: { + [sessionEmail]: { + errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericRemove'), + }, + }, }, }, ];