diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 45ee00ac7823..79271bdc03c7 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -529,12 +529,6 @@ function validateReportDraftProperty(key: keyof Report, value: string) { }, 'number', ); - case 'pendingChatMembers': - return validateArray>(value, { - accountID: 'string', - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION, - errors: 'object', - }); case 'fieldList': return validateObject>( value, @@ -616,7 +610,6 @@ function validateReportDraftProperty(key: keyof Report, value: string) { iouReportID: CONST.RED_BRICK_ROAD_PENDING_ACTION, preexistingReportID: CONST.RED_BRICK_ROAD_PENDING_ACTION, nonReimbursableTotal: CONST.RED_BRICK_ROAD_PENDING_ACTION, - pendingChatMembers: CONST.RED_BRICK_ROAD_PENDING_ACTION, fieldList: CONST.RED_BRICK_ROAD_PENDING_ACTION, permissions: CONST.RED_BRICK_ROAD_PENDING_ACTION, tripData: CONST.RED_BRICK_ROAD_PENDING_ACTION, diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 7722744465e0..91381a0b0119 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -246,7 +246,13 @@ const isPolicyUser = (policy: OnyxInputOrEntry, currentUserLogin?: strin const isPolicyAuditor = (policy: OnyxInputOrEntry, currentUserLogin?: string): boolean => (policy?.role ?? (currentUserLogin && policy?.employeeList?.[currentUserLogin]?.role)) === CONST.POLICY.ROLE.AUDITOR; -const isPolicyEmployee = (policyID: string, policies: OnyxCollection): boolean => Object.values(policies ?? {}).some((policy) => policy?.id === policyID); +const isPolicyEmployee = (policyID: string | undefined, policies: OnyxCollection): boolean => { + if (!policyID) { + return false; + } + + return Object.values(policies ?? {}).some((policy) => policy?.id === policyID); +}; /** * Checks if the current user is an owner (creator) of the policy. @@ -1112,6 +1118,10 @@ function hasVBBA(policyID: string) { } function getTagApproverRule(policyOrID: string | SearchPolicy | OnyxEntry, tagName: string) { + if (!policyOrID) { + return; + } + const policy = typeof policyOrID === 'string' ? getPolicy(policyOrID) : policyOrID; const approvalRules = policy?.rules?.approvalRules ?? []; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 6431deb6d75b..4fa704944bc9 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -927,7 +927,11 @@ function getLinkedTransactionID(reportActionOrID: string | OnyxEntry isActionableTrackExpense(action) && getOriginalMessage(action)?.transactionID === transactionID); } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e9c017db43ce..1d07cb576df3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -52,8 +52,9 @@ import type {ErrorFields, Errors, Icon, PendingAction} from '@src/types/onyx/Ony import type {OriginalMessageChangeLog, PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {ConnectionName} from '@src/types/onyx/Policy'; -import type {NotificationPreference, Participants, PendingChatMember, Participant as ReportParticipant} from '@src/types/onyx/Report'; +import type {NotificationPreference, Participants, Participant as ReportParticipant} from '@src/types/onyx/Report'; import type {Message, OldDotReportAction, ReportActions} from '@src/types/onyx/ReportAction'; +import type {PendingChatMember} from '@src/types/onyx/ReportMetadata'; import type {SearchPolicy, SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults'; import type {Comment, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -384,6 +385,7 @@ type OptimisticWorkspaceChats = { expenseChatData: OptimisticChatReport; expenseReportActionData: Record; expenseCreatedReportActionID: string; + pendingChatMembers: PendingChatMember[]; }; type OptimisticModifiedExpenseReportAction = Pick< @@ -701,6 +703,7 @@ Onyx.connect({ }); let allReportMetadata: OnyxCollection; +const allReportMetadataKeyValue: Record = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_METADATA, waitForCollectionCallback: true, @@ -709,6 +712,15 @@ Onyx.connect({ return; } allReportMetadata = value; + + Object.entries(value).forEach(([reportID, reportMetadata]) => { + if (!reportMetadata) { + return; + } + + const [, id] = reportID.split('_'); + allReportMetadataKeyValue[id] = reportMetadata; + }); }, }); @@ -1759,7 +1771,11 @@ function isPayAtEndExpenseReport(reportID: string, transactions: Transaction[] | /** * Checks if a report is a transaction thread associated with a report that has only one transaction */ -function isOneTransactionThread(reportID: string, parentReportID: string, threadParentReportAction: OnyxEntry): boolean { +function isOneTransactionThread(reportID: string | undefined, parentReportID: string | undefined, threadParentReportAction: OnyxEntry): boolean { + if (!reportID || !parentReportID) { + return false; + } + const parentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? ([] as ReportAction[]); const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(parentReportID, parentReportActions); return reportID === transactionThreadReportID && !ReportActionsUtils.isSentMoneyReportAction(threadParentReportAction); @@ -2213,6 +2229,7 @@ function getDisplayNameForParticipant( function getParticipantsAccountIDsForDisplay(report: OnyxEntry, shouldExcludeHidden = false, shouldExcludeDeleted = false, shouldForceExcludeCurrentUser = false): number[] { const reportParticipants = report?.participants ?? {}; + const reportMetadata = getReportMetadata(report?.reportID); let participantsEntries = Object.entries(reportParticipants); // We should not show participants that have an optimistic entry with the same login in the personal details @@ -2252,7 +2269,7 @@ function getParticipantsAccountIDsForDisplay(report: OnyxEntry, shouldEx if ( shouldExcludeDeleted && - report?.pendingChatMembers?.findLast((member) => Number(member.accountID) === accountID)?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE + reportMetadata?.pendingChatMembers?.findLast((member) => Number(member.accountID) === accountID)?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ) { return false; } @@ -2313,8 +2330,10 @@ function getGroupChatName(participants?: SelectedParticipant[], shouldApplyLimit return report.reportName; } + const reportMetadata = getReportMetadata(report?.reportID); + const pendingMemberAccountIDs = new Set( - report?.pendingChatMembers?.filter((member) => member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).map((member) => member.accountID), + reportMetadata?.pendingChatMembers?.filter((member) => member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).map((member) => member.accountID), ); let participantAccountIDs = participants?.map((participant) => participant.accountID) ?? @@ -3017,7 +3036,11 @@ function getTitleReportField(reportFields: Record) { /** * Get the key for a report field */ -function getReportFieldKey(reportFieldId: string) { +function getReportFieldKey(reportFieldId: string | undefined) { + if (!reportFieldId) { + return ''; + } + // We don't need to add `expensify_` prefix to the title field key, because backend stored title under a unique key `text_title`, // and all the other report field keys are stored under `expensify_FIELD_ID`. if (reportFieldId === CONST.REPORT_FIELD_TITLE_FIELD_ID) { @@ -6071,7 +6094,6 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string, exp false, policyName, ), - pendingChatMembers, }; const adminsChatReportID = adminsChatData.reportID; const adminsCreatedAction = buildOptimisticCreatedReportAction(CONST.POLICY.OWNER_EMAIL_FAKE); @@ -6111,6 +6133,7 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string, exp expenseChatData, expenseReportActionData, expenseCreatedReportActionID: expenseReportCreatedAction.reportActionID, + pendingChatMembers, }; } @@ -8225,7 +8248,16 @@ function createDraftWorkspaceAndNavigateToConfirmationScreen(transactionID: stri } } -function createDraftTransactionAndNavigateToParticipantSelector(transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string): void { +function createDraftTransactionAndNavigateToParticipantSelector( + transactionID: string | undefined, + reportID: string | undefined, + actionName: IOUAction, + reportActionID: string | undefined, +): void { + if (!transactionID || !reportID || !reportActionID) { + return; + } + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? ([] as ReportAction[]); @@ -8538,7 +8570,7 @@ function hasInvoiceReports() { } function getReportMetadata(reportID?: string) { - return allReportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`]; + return allReportMetadataKeyValue[reportID ?? '-1']; } export { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 46da4f283945..c0906f77850a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5748,7 +5748,11 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT * @param isSingleTransactionView - whether we are in the transaction thread report * @returns The URL to navigate to */ -function getNavigationUrlOnMoneyRequestDelete(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): Route | undefined { +function getNavigationUrlOnMoneyRequestDelete(transactionID: string | undefined, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): Route | undefined { + if (!transactionID) { + return undefined; + } + const {shouldDeleteTransactionThread, shouldDeleteIOUReport, iouReport} = prepareToCleanUpMoneyRequest(transactionID, reportAction); // Determine which report to navigate back to @@ -5771,7 +5775,16 @@ function getNavigationUrlOnMoneyRequestDelete(transactionID: string, reportActio * @param isSingleTransactionView - Whether we're in single transaction view * @returns The URL to navigate to */ -function getNavigationUrlAfterTrackExpenseDelete(chatReportID: string, transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): Route | undefined { +function getNavigationUrlAfterTrackExpenseDelete( + chatReportID: string | undefined, + transactionID: string | undefined, + reportAction: OnyxTypes.ReportAction, + isSingleTransactionView = false, +): Route | undefined { + if (!chatReportID || !transactionID) { + return undefined; + } + const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] ?? null; // If not a self DM, handle it as a regular money request @@ -5946,7 +5959,11 @@ function cleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repo * @param isSingleTransactionView - whether we are in the transaction thread report * @return the url to navigate back once the money request is deleted */ -function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { +function deleteMoneyRequest(transactionID: string | undefined, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { + if (!transactionID) { + return; + } + // STEP 1: Calculate and prepare the data const { shouldDeleteTransactionThread, @@ -6207,7 +6224,11 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor return urlToNavigateBack; } -function deleteTrackExpense(chatReportID: string, transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { +function deleteTrackExpense(chatReportID: string | undefined, transactionID: string | undefined, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { + if (!chatReportID || !transactionID) { + return; + } + const urlToNavigateBack = getNavigationUrlAfterTrackExpenseDelete(chatReportID, transactionID, reportAction, isSingleTransactionView); // STEP 1: Get all collections we're updating diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index a40617f27cbd..ea3edb8d7a4d 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -90,7 +90,7 @@ Onyx.connect({ key: ONYXKEYS.SESSION, callback: (val) => { sessionEmail = val?.email ?? ''; - sessionAccountID = val?.accountID ?? -1; + sessionAccountID = val?.accountID ?? CONST.DEFAULT_NUMBER_ID; }, }); @@ -134,6 +134,7 @@ function getPolicy(policyID: string | undefined): OnyxEntry { */ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]): AnnounceRoomMembersOnyxData { const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID); + const announceReportMetadata = ReportUtils.getReportMetadata(announceReport?.reportID); const announceRoomMembers: AnnounceRoomMembersOnyxData = { onyxOptimisticData: [], onyxFailureData: [], @@ -145,33 +146,49 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[] } const participantAccountIDs = [...Object.keys(announceReport.participants ?? {}).map(Number), ...accountIDs]; - const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReport?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); + const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReportMetadata?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - announceRoomMembers.onyxOptimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`, - value: { - participants: ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs), - pendingChatMembers, + announceRoomMembers.onyxOptimisticData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`, + value: { + participants: ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs), + }, }, - }); + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${announceReport?.reportID}`, + value: { + pendingChatMembers, + }, + }, + ); - announceRoomMembers.onyxFailureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`, - value: { - participants: accountIDs.reduce((acc, curr) => { - Object.assign(acc, {[curr]: null}); - return acc; - }, {}), - pendingChatMembers: announceReport?.pendingChatMembers ?? null, + announceRoomMembers.onyxFailureData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`, + value: { + participants: accountIDs.reduce((acc, curr) => { + Object.assign(acc, {[curr]: null}); + return acc; + }, {}), + }, }, - }); + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${announceReport?.reportID}`, + value: { + pendingChatMembers: announceReportMetadata?.pendingChatMembers ?? null, + }, + }, + ); announceRoomMembers.onyxSuccessData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${announceReport?.reportID}`, value: { - pendingChatMembers: announceReport?.pendingChatMembers ?? null, + pendingChatMembers: announceReportMetadata?.pendingChatMembers ?? null, }, }); return announceRoomMembers; @@ -213,53 +230,75 @@ function updateImportSpreadsheetData(membersLength: number): OnyxData { /** * Build optimistic data for removing users from the announcement room */ -function removeOptimisticAnnounceRoomMembers(policyID: string, policyName: string, accountIDs: number[]): AnnounceRoomMembersOnyxData { - const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID); +function removeOptimisticAnnounceRoomMembers(policyID: string | undefined, policyName: string, accountIDs: number[]): AnnounceRoomMembersOnyxData { const announceRoomMembers: AnnounceRoomMembersOnyxData = { onyxOptimisticData: [], onyxFailureData: [], onyxSuccessData: [], }; + if (!policyID) { + return announceRoomMembers; + } + + const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID); + const announceReportMetadata = ReportUtils.getReportMetadata(announceReport?.reportID); + if (!announceReport) { return announceRoomMembers; } - const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReport?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReportMetadata?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - announceRoomMembers.onyxOptimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, - value: { - pendingChatMembers, - ...(accountIDs.includes(sessionAccountID) - ? { - statusNum: CONST.REPORT.STATUS_NUM.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.APPROVED, - oldPolicyName: policyName, - } - : {}), + announceRoomMembers.onyxOptimisticData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, + value: { + ...(accountIDs.includes(sessionAccountID) + ? { + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + oldPolicyName: policyName, + } + : {}), + }, }, - }); - announceRoomMembers.onyxFailureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, - value: { - pendingChatMembers: announceReport?.pendingChatMembers ?? null, - ...(accountIDs.includes(sessionAccountID) - ? { - statusNum: announceReport.statusNum, - stateNum: announceReport.stateNum, - oldPolicyName: announceReport.oldPolicyName, - } - : {}), + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${announceReport.reportID}`, + value: { + pendingChatMembers, + }, }, - }); + ); + announceRoomMembers.onyxFailureData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, + value: { + ...(accountIDs.includes(sessionAccountID) + ? { + statusNum: announceReport.statusNum, + stateNum: announceReport.stateNum, + oldPolicyName: announceReport.oldPolicyName, + } + : {}), + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${announceReport.reportID}`, + value: { + pendingChatMembers: announceReportMetadata?.pendingChatMembers ?? null, + }, + }, + ); announceRoomMembers.onyxSuccessData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${announceReport.reportID}`, value: { - pendingChatMembers: announceReport?.pendingChatMembers ?? null, + pendingChatMembers: announceReportMetadata?.pendingChatMembers ?? null, }, }); @@ -286,7 +325,7 @@ function removeMembers(accountIDs: number[], policyID: string) { ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy?.name ?? '', CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY), ); - const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policy?.id ?? '-1', policy?.name ?? '', accountIDs); + const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policy?.id, policy?.name ?? '', accountIDs); const optimisticMembersState: OnyxCollectionInputValue = {}; const successMembersState: OnyxCollectionInputValue = {}; @@ -373,26 +412,34 @@ function removeMembers(accountIDs: number[], policyID: string) { const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); workspaceChats.forEach((report) => { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`, - value: { - statusNum: CONST.REPORT.STATUS_NUM.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.APPROVED, - oldPolicyName: policy?.name, - pendingChatMembers, + optimisticData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`, + value: { + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + oldPolicyName: policy?.name, + }, }, - }); + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, + value: { + pendingChatMembers, + }, + }, + ); successData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, value: { pendingChatMembers: null, }, }); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, value: { pendingChatMembers: null, }, @@ -873,7 +920,7 @@ function acceptJoinRequest(reportID: string, reportAction: OnyxEntry): WorkspaceF expenseChatData: workspaceChatData, expenseReportActionData: workspaceChatReportActionData, expenseCreatedReportActionID: workspaceChatCreatedReportActionID, + pendingChatMembers, } = ReportUtils.buildOptimisticWorkspaceChats(policyID, workspaceName); if (!employeeAccountID || !oldPersonalPolicyID) { @@ -2312,6 +2329,13 @@ function createWorkspaceFromIOUPayment(iouReport: OnyxEntry): WorkspaceF ...adminsChatData, }, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${adminsChatReportID}`, + value: { + pendingChatMembers, + }, + }, { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 5d390221822b..462d291acf84 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -459,7 +459,7 @@ function unsubscribeFromReportChannel(reportID: string) { /** * Remove our pusher subscriptions to listen for someone leaving a report. */ -function unsubscribeFromLeavingRoomReportChannel(reportID: string) { +function unsubscribeFromLeavingRoomReportChannel(reportID: string | undefined) { if (!reportID) { return; } @@ -1333,7 +1333,11 @@ function expandURLPreview(reportID: string, reportActionID: string) { * @param shouldResetUnreadMarker Indicates whether the unread indicator should be reset. * Currently, the unread indicator needs to be reset only when users mark a report as read. */ -function readNewestAction(reportID: string, shouldResetUnreadMarker = false) { +function readNewestAction(reportID: string | undefined, shouldResetUnreadMarker = false) { + if (!reportID) { + return; + } + const lastReadTime = DateUtils.getDBTime(); const optimisticData: OnyxUpdate[] = [ @@ -3091,6 +3095,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal /** Invites people to a room */ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs) { const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const reportMetadata = ReportUtils.getReportMetadata(reportID); if (!report) { return; } @@ -3117,7 +3122,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails ); const newPersonalDetailsOnyxData = PersonalDetailsUtils.getPersonalDetailsOnyxDataForOptimisticUsers(newLogins, newAccountIDs); - const pendingChatMembers = ReportUtils.getPendingChatMembers(inviteeAccountIDs, report?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); + const pendingChatMembers = ReportUtils.getPendingChatMembers(inviteeAccountIDs, reportMetadata?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); const newParticipantAccountCleanUp = newAccountIDs.reduce>((participantCleanUp, newAccountID) => { // eslint-disable-next-line no-param-reassign @@ -3131,14 +3136,20 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participants: participantsAfterInvitation, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, + value: { pendingChatMembers, }, }, ]; optimisticData.push(...newPersonalDetailsOnyxData.optimisticData); - const successPendingChatMembers = report?.pendingChatMembers - ? report?.pendingChatMembers?.filter( + const successPendingChatMembers = reportMetadata?.pendingChatMembers + ? reportMetadata?.pendingChatMembers?.filter( (pendingMember) => !(inviteeAccountIDs.includes(Number(pendingMember.accountID)) && pendingMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE), ) : null; @@ -3147,17 +3158,23 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { - pendingChatMembers: successPendingChatMembers, participants: newParticipantAccountCleanUp, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, + value: { + pendingChatMembers: successPendingChatMembers, + }, + }, ]; successData.push(...newPersonalDetailsOnyxData.finallyData); const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, value: { pendingChatMembers: pendingChatMembers.map((pendingChatMember) => { @@ -3195,13 +3212,15 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails } function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { - const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const reportMetadata = ReportUtils.getReportMetadata(reportID); Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - pendingChatMembers: report?.pendingChatMembers?.filter((pendingChatMember) => pendingChatMember.accountID !== invitedAccountID), participants: { [invitedAccountID]: null, }, }); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, { + pendingChatMembers: reportMetadata?.pendingChatMembers?.filter((pendingChatMember) => pendingChatMember.accountID !== invitedAccountID), + }); Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { [invitedAccountID]: null, }); @@ -3258,6 +3277,7 @@ function inviteToGroupChat(reportID: string, inviteeEmailsToAccountIDs: InvitedE */ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const reportMetadata = ReportUtils.getReportMetadata(reportID); if (!report) { return; } @@ -3266,12 +3286,12 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { targetAccountIDs.forEach((accountID) => { removeParticipantsData[accountID] = null; }); - const pendingChatMembers = ReportUtils.getPendingChatMembers(targetAccountIDs, report?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + const pendingChatMembers = ReportUtils.getPendingChatMembers(targetAccountIDs, reportMetadata?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, value: { pendingChatMembers, }, @@ -3281,9 +3301,9 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, value: { - pendingChatMembers: report?.pendingChatMembers ?? null, + pendingChatMembers: reportMetadata?.pendingChatMembers ?? null, }, }, ]; @@ -3296,7 +3316,13 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participants: removeParticipantsData, - pendingChatMembers: report?.pendingChatMembers ?? null, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, + value: { + pendingChatMembers: reportMetadata?.pendingChatMembers ?? null, }, }, ]; diff --git a/src/libs/migrateOnyx.ts b/src/libs/migrateOnyx.ts index 7053c2f6f75f..f0a9ec9db977 100644 --- a/src/libs/migrateOnyx.ts +++ b/src/libs/migrateOnyx.ts @@ -2,6 +2,7 @@ import Log from './Log'; import KeyReportActionsDraftByReportActionID from './migrations/KeyReportActionsDraftByReportActionID'; import MoveIsOptimisticReportToMetadata from './migrations/MoveIsOptimisticReportToMetadata'; import NVPMigration from './migrations/NVPMigration'; +import PendingMembersToMetadata from './migrations/PendingMembersToMetadata'; import PronounsMigration from './migrations/PronounsMigration'; import RemoveEmptyReportActionsDrafts from './migrations/RemoveEmptyReportActionsDrafts'; import RenameCardIsVirtual from './migrations/RenameCardIsVirtual'; @@ -23,6 +24,7 @@ export default function () { NVPMigration, PronounsMigration, MoveIsOptimisticReportToMetadata, + PendingMembersToMetadata, ]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the diff --git a/src/libs/migrations/PendingMembersToMetadata.ts b/src/libs/migrations/PendingMembersToMetadata.ts new file mode 100644 index 000000000000..d71dc0d17e83 --- /dev/null +++ b/src/libs/migrations/PendingMembersToMetadata.ts @@ -0,0 +1,54 @@ +import Onyx from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; +import Log from '@libs/Log'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; +import type {PendingChatMember} from '@src/types/onyx/ReportMetadata'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type OldReport = Report & {pendingChatMembers?: PendingChatMember[]}; + +/** + * This migration moves pendingChatMembers from the report object to reportMetadata + */ +export default function (): Promise { + return new Promise((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (reports: OnyxCollection) => { + Onyx.disconnect(connection); + if (!reports || isEmptyObject(reports)) { + Log.info('[Migrate Onyx] Skipping migration PendingMembersToMetadata because there are no reports'); + return resolve(); + } + + const promises: Array> = []; + Object.entries(reports).forEach(([reportID, report]) => { + if (report?.pendingChatMembers === undefined) { + return; + } + + promises.push( + Promise.all([ + // @ts-expect-error pendingChatMembers is not a valid property of Report anymore + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(reportID, {pendingChatMembers: null}), + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`, {pendingChatMembers: report.pendingChatMembers}), + ]).then(() => { + Log.info(`[Migrate Onyx] Successfully moved pendingChatMembers to reportMetadata for ${reportID}`); + }), + ); + }); + + if (promises.length === 0) { + Log.info('[Migrate Onyx] Skipping migration PendingMembersToMetadata because there are no reports with pendingChatMembers'); + return resolve(); + } + + Promise.all(promises).then(() => resolve()); + }, + }); + }); +} diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index dc751bae7bff..15de7a6e5f4a 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -87,12 +87,10 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const backTo = route.params.backTo; // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. - /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ - const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || '-1'}`); - const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); - const [parentReportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.parentReportID || '-1'}`); - const {reportActions} = usePaginatedReportActions(report.reportID || '-1'); - /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`); + const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`); + const [parentReportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.parentReportID}`); + const {reportActions} = usePaginatedReportActions(report.reportID); const {currentSearchHash} = useSearchContext(); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply the correct modal type for the decision modal @@ -116,9 +114,9 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); const [offlineModalVisible, setOfflineModalVisible] = useState(false); const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false); - const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '-1'}`], [policies, report?.policyID]); + const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`], [policies, report?.policyID]); const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]); - const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID ?? '-1', policies), [report?.policyID, policies]); + const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID, policies), [report?.policyID, policies]); const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(report), [report]); const shouldUseFullTitle = useMemo(() => ReportUtils.shouldUseFullTitleToDisplay(report), [report]); const isChatRoom = useMemo(() => ReportUtils.isChatRoom(report), [report]); @@ -133,7 +131,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const isTaskReport = useMemo(() => ReportUtils.isTaskReport(report), [report]); const isSelfDM = useMemo(() => ReportUtils.isSelfDM(report), [report]); const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(report); - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); + const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(report, parentReportAction); const canEditReportDescription = useMemo(() => ReportUtils.canEditReportDescription(report, policy), [report, policy]); const shouldShowReportDescription = isChatRoom && (canEditReportDescription || report.description !== ''); @@ -143,7 +141,15 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const shouldDisableRename = useMemo(() => ReportUtils.shouldDisableRename(report), [report]); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx - const chatRoomSubtitle = useMemo(() => ReportUtils.getChatRoomSubtitle(report), [report, policy]); + const chatRoomSubtitle = useMemo(() => { + const subtitle = ReportUtils.getChatRoomSubtitle(report); + + if (subtitle) { + return subtitle; + } + + return ''; + }, [report]); const isSystemChat = useMemo(() => ReportUtils.isSystemChat(report), [report]); const isGroupChat = useMemo(() => ReportUtils.isGroupChat(report), [report]); const isRootGroupChat = useMemo(() => ReportUtils.isRootGroupChat(report), [report]); @@ -163,7 +169,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // Get the active chat members by filtering out the pending members with delete action const activeChatMembers = participants.flatMap((accountID) => { - const pendingMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); + const pendingMember = reportMetadata?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); const detail = personalDetails?.[accountID]; if (!detail) { return []; @@ -209,8 +215,8 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const moneyRequestAction = transactionThreadReportID ? requestParentReportAction : parentReportAction; - const canModifyTask = Task.canModifyTask(report, session?.accountID ?? -1); - const canActionTask = Task.canActionTask(report, session?.accountID ?? -1); + const canModifyTask = Task.canModifyTask(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); + const canActionTask = Task.canActionTask(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); const shouldShowTaskDeleteButton = isTaskReport && !isCanceledTaskReport && @@ -239,7 +245,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - Report.getReportPrivateNote(report?.reportID ?? '-1'); + Report.getReportPrivateNote(report?.reportID); }, [report?.reportID, isOffline, isPrivateNotesFetchTriggered, isSelfDM]); const leaveChat = useCallback(() => { @@ -293,14 +299,12 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const shouldShowMenuItem = shouldShowNotificationPref || shouldShowWriteCapability || (!!report?.visibility && report.chatType !== CONST.REPORT.CHAT_TYPE.INVOICE); const isPayer = ReportUtils.isPayer(session, moneyRequestReport); - const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID ?? '-1'); + const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); const shouldShowCancelPaymentButton = caseID === CASES.MONEY_REPORT && isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport); - const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport?.chatReportID ?? '-1'}`); + const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport?.chatReportID}`); - const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) - ? ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID ?? '' - : ''; + const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) ? ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID : ''; const cancelPayment = useCallback(() => { if (!chatReport) { @@ -343,9 +347,9 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta shouldShowRightIcon: true, action: () => { if (shouldOpenRoomMembersPage) { - Navigation.navigate(ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? '-1', backTo)); + Navigation.navigate(ROUTES.ROOM_MEMBERS.getRoute(report?.reportID, backTo)); } else { - Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID ?? '-1', backTo)); + Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID, backTo)); } }, }); @@ -357,7 +361,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(report?.reportID ?? '-1')); + Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(report?.reportID)); }, }); } @@ -370,15 +374,15 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - Navigation.navigate(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1', backTo)); + Navigation.navigate(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID, backTo)); }, }); } if (isTrackExpenseReport && !isDeletedParentAction) { - const actionReportID = ReportUtils.getOriginalReportID(report.reportID, parentReportAction) ?? '0'; - const whisperAction = ReportActionsUtils.getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID ?? '0'); - const actionableWhisperReportActionID = whisperAction?.reportActionID ?? '0'; + const actionReportID = ReportUtils.getOriginalReportID(report.reportID, parentReportAction); + const whisperAction = ReportActionsUtils.getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID); + const actionableWhisperReportActionID = whisperAction?.reportActionID; items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS, translationKey: 'actionableMentionTrackExpense.submit', @@ -493,7 +497,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta icon: Expensicons.Upload, isAnonymousAction: false, action: () => { - Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_EXPORT.getRoute(report?.reportID ?? '', connectedIntegration, backTo)); + Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_EXPORT.getRoute(report?.reportID, connectedIntegration, backTo)); }, }); } @@ -608,18 +612,18 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isUsingDefaultAvatar={!report.avatarUrl} size={CONST.AVATAR_SIZE.XLARGE} avatarStyle={styles.avatarXLarge} - onViewPhotoPress={() => Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(report.reportID ?? '-1'))} + onViewPhotoPress={() => Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(report.reportID))} onImageRemoved={() => { // Calling this without a file will remove the avatar - Report.updateGroupChatAvatar(report.reportID ?? ''); + Report.updateGroupChatAvatar(report.reportID); }} - onImageSelected={(file) => Report.updateGroupChatAvatar(report.reportID ?? '-1', file)} + onImageSelected={(file) => Report.updateGroupChatAvatar(report.reportID, file)} editIcon={Expensicons.Camera} editIconStyle={styles.smallEditIconAccount} pendingAction={report.pendingFields?.avatar ?? undefined} errors={report.errorFields?.avatar ?? null} errorRowStyles={styles.mt6} - onErrorClose={() => Report.clearAvatarErrors(report.reportID ?? '-1')} + onErrorClose={() => Report.clearAvatarErrors(report.reportID)} shouldUseStyleUtilityForAnchorPosition style={[styles.w100, styles.mb3]} /> @@ -655,7 +659,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta PromotedActions.hold({ isTextHold: canHoldUnholdReportAction.canHoldRequest, reportAction: moneyRequestAction, - reportID: transactionThreadReportID ? report.reportID : moneyRequestAction?.childReportID ?? '-1', + reportID: transactionThreadReportID ? report.reportID : moneyRequestAction?.childReportID, isDelegateAccessRestricted, setIsNoDelegateAccessMenuVisible, currentSearchHash, @@ -689,7 +693,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta <> { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(report?.policyID ?? '')); + let policyID = report?.policyID; + + if (!policyID) { + policyID = ''; + } + + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }} > {chatRoomSubtitleText} @@ -755,7 +765,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const fields = ReportUtils.getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); return fields.find((reportField) => ReportUtils.isReportFieldOfTypeTitle(reportField)); }, [report, policy?.fieldList]); - const fieldKey = ReportUtils.getReportFieldKey(titleField?.fieldID ?? '-1'); + const fieldKey = ReportUtils.getReportFieldKey(titleField?.fieldID); const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, titleField, policy); const shouldShowTitleField = caseID !== CASES.MONEY_REQUEST && !isFieldDisabled && ReportUtils.isAdminOwnerApproverOrReportOwner(report, policy); @@ -790,7 +800,15 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta titleStyle={styles.newKansasLarge} shouldCheckActionAllowedOnPress={false} description={Str.UCFirst(titleField.name)} - onPress={() => Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '-1', titleField.fieldID ?? '-1', backTo))} + onPress={() => { + let policyID = report.policyID; + + if (!policyID) { + policyID = ''; + } + + Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, policyID, titleField.fieldID, backTo)); + }} furtherDetailsComponent={nameSectionFurtherDetailsContent} /> @@ -810,7 +828,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + IOU.deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); } @@ -851,7 +869,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta if (!isEmptyObject(requestParentReportAction)) { const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { - urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { urlToNavigateBack = IOU.getNavigationUrlOnMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); } diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index 4c63cc4b4492..e02a51b26502 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -57,7 +57,8 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { const selectionListRef = useRef(null); const textInputRef = useRef(null); const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE); - const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`); + const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`); + const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`); const {selectionMode} = useMobileSelectionMode(); const [session] = useOnyx(ONYXKEYS.SESSION); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); @@ -78,7 +79,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { const chatParticipants = ReportUtils.getParticipantsList(report, personalDetails); - const pendingChatMembers = report?.pendingChatMembers; + const pendingChatMembers = reportMetadata?.pendingChatMembers; const reportParticipants = report?.participants; // Get the active chat members by filtering out the pending members with delete action diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 0f29f1d00ba9..a14ada3d3f00 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -48,6 +48,7 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { const route = useRoute>(); const styles = useThemeStyles(); const [session] = useOnyx(ONYXKEYS.SESSION); + const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`); const currentUserAccountID = Number(session?.accountID); const {formatPhoneNumber, translate} = useLocalize(); const [selectedMembers, setSelectedMembers] = useState([]); @@ -56,7 +57,7 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { const [searchValue, setSearchValue] = useState(''); const [didLoadRoomMembers, setDidLoadRoomMembers] = useState(false); const personalDetails = usePersonalDetails(); - const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`], [policies, report?.policyID]); + const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`], [policies, report?.policyID]); const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(report), [report]); const backTo = route.params.backTo; @@ -175,7 +176,7 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { const shouldShowTextInput = useMemo(() => { // Get the active chat members by filtering out the pending members with delete action const activeParticipants = participants.filter((accountID) => { - const pendingMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); + const pendingMember = reportMetadata?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); if (!personalDetails?.[accountID]) { return false; } @@ -183,7 +184,7 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { return !pendingMember || isOffline || pendingMember.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; }); return activeParticipants.length >= CONST.STANDARD_LIST_ITEM_LIMIT; - }, [participants, personalDetails, isOffline, report]); + }, [participants, reportMetadata?.pendingChatMembers, personalDetails, isOffline]); useEffect(() => { if (!isFocusedScreen || !shouldShowTextInput) { @@ -218,7 +219,7 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { if (!details || (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue))) { return; } - const pendingChatMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); + const pendingChatMember = reportMetadata?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); const isAdmin = PolicyUtils.isUserPolicyAdmin(policy, details.login); const isDisabled = pendingChatMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || details.isOptimisticPersonalDetail; const isDisabledCheckbox = @@ -251,11 +252,22 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { result = result.sort((value1, value2) => localeCompare(value1.text ?? '', value2.text ?? '')); return result; - }, [formatPhoneNumber, isPolicyExpenseChat, participants, personalDetails, policy, report.ownerAccountID, report?.pendingChatMembers, searchValue, selectedMembers, session?.accountID]); + }, [ + formatPhoneNumber, + isPolicyExpenseChat, + participants, + personalDetails, + policy, + report.ownerAccountID, + reportMetadata?.pendingChatMembers, + searchValue, + selectedMembers, + session?.accountID, + ]); const dismissError = useCallback( (item: ListItem) => { - Report.clearAddRoomMemberError(report.reportID, String(item.accountID ?? '-1')); + Report.clearAddRoomMemberError(report.reportID, String(item.accountID)); }, [report.reportID], ); @@ -313,7 +325,11 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { /** Opens the room member details page */ const openRoomMemberDetails = useCallback( (item: ListItem) => { - Navigation.navigate(ROUTES.ROOM_MEMBER_DETAILS.getRoute(report.reportID, item?.accountID ?? -1, backTo)); + if (!item?.accountID) { + return; + } + + Navigation.navigate(ROUTES.ROOM_MEMBER_DETAILS.getRoute(report.reportID, item?.accountID, backTo)); }, [report, backTo], ); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index d66a17bbe9b8..0ed29c175cd7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -229,7 +229,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro permissions, invoiceReceiver: reportOnyx.invoiceReceiver, policyAvatar: reportOnyx.policyAvatar, - pendingChatMembers: reportOnyx.pendingChatMembers, }, [reportOnyx, permissions], ); diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 4c7909169fc4..8e79ff1accf5 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -23,18 +23,6 @@ type Note = OnyxCommon.OnyxValueWithOfflineFeedback<{ errors?: OnyxCommon.Errors; }>; -/** The pending member of report */ -type PendingChatMember = { - /** Account ID of the pending member */ - accountID: string; - - /** Action to be applied to the pending member of report */ - pendingAction: OnyxCommon.PendingAction; - - /** Collection of errors to show to the user */ - errors?: OnyxCommon.Errors; -}; - /** Report participant properties */ type Participant = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** What is the role of the participant in the report */ @@ -208,9 +196,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** Collection of participant private notes, indexed by their accountID */ privateNotes?: Record; - /** Pending members of the report */ - pendingChatMembers?: PendingChatMember[]; - /** Collection of policy report fields, indexed by their fieldID */ fieldList?: Record; @@ -241,4 +226,4 @@ type ReportCollectionDataSet = CollectionDataSet