diff --git a/src/CONST.ts b/src/CONST.ts index e5b44042a550..79bc62ab8ac0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1294,6 +1294,7 @@ const CONST = { CUSTOM_UNIT_RATE_BASE_OFFSET: 100, OWNER_EMAIL_FAKE: '_FAKE_', OWNER_ACCOUNT_ID_FAKE: 0, + ID_FAKE: '_FAKE_', }, CUSTOM_UNITS: { diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index e6e9a7746585..0cc6aa27e313 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -34,8 +34,19 @@ const getLastAccessedReportID = ( isFirstTimeNewExpensifyUser: OnyxEntry, openOnAdminRoom: boolean, reportMetadata: OnyxCollection, + policyID?: string, + policyMemberAccountIDs?: number[], ): string | undefined => { - const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, !!isFirstTimeNewExpensifyUser, openOnAdminRoom, reportMetadata); + const lastReport = ReportUtils.findLastAccessedReport( + reports, + ignoreDefaultRooms, + policies, + !!isFirstTimeNewExpensifyUser, + openOnAdminRoom, + reportMetadata, + policyID, + policyMemberAccountIDs, + ); return lastReport?.reportID; }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 58e93d521827..8e747ab8dafe 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -616,17 +616,22 @@ function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean } /** - * Given a collection of reports returns them sorted by last read + * Checks if the supplied report has a common policy member with the array passed in params. */ -function sortReportsByLastRead(reports: OnyxCollection, reportMetadata: OnyxCollection): Array> { - return Object.values(reports ?? {}) - .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime)) - .sort((a, b) => { - const aTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${a?.reportID}`]?.lastVisitTime ?? a?.lastReadTime ?? ''); - const bTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${b?.reportID}`]?.lastVisitTime ?? b?.lastReadTime ?? ''); +function hasParticipantInArray(report: Report, policyMemberAccountIDs: number[]) { + if (!report.participantAccountIDs) { + return false; + } - return aTime.valueOf() - bTime.valueOf(); - }); + const policyMemberAccountIDsSet = new Set(policyMemberAccountIDs); + + for (const reportParticipant of report.participantAccountIDs) { + if (policyMemberAccountIDsSet.has(reportParticipant)) { + return true; + } + } + + return false; } /** @@ -826,6 +831,37 @@ function isConciergeChatReport(report: OnyxEntry): boolean { return report?.participantAccountIDs?.length === 1 && Number(report.participantAccountIDs?.[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); } +/** + * Checks if the supplied report belongs to workspace based on the provided params. If the report's policyID is _FAKE_ or has no value, it means this report is a DM. + * In this case report and workspace members must be compared to determine whether the report belongs to the workspace. + */ +function doesReportBelongToWorkspace(report: Report, policyID: string, policyMemberAccountIDs: number[]) { + return ( + isConciergeChatReport(report) || (report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : report.policyID === policyID) + ); +} + +/** + * Given an array of reports, return them filtered by a policyID and policyMemberAccountIDs. + */ +function filterReportsByPolicyIdAndMemberAccountIDs(reports: Report[], policyID = '', policyMemberAccountIDs: number[] = []) { + return reports.filter((report) => !!report && doesReportBelongToWorkspace(report, policyID, policyMemberAccountIDs)); +} + +/** + * Given an array of reports, return them sorted by the last read timestamp. + */ +function sortReportsByLastRead(reports: Report[], reportMetadata: OnyxCollection): Array> { + return reports + .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime)) + .sort((a, b) => { + const aTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${a?.reportID}`]?.lastVisitTime ?? a?.lastReadTime ?? ''); + const bTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${b?.reportID}`]?.lastVisitTime ?? b?.lastReadTime ?? ''); + + return aTime.valueOf() - bTime.valueOf(); + }); +} + /** * Returns true if report is still being processed */ @@ -896,13 +932,22 @@ function findLastAccessedReport( isFirstTimeNewExpensifyUser: boolean, openOnAdminRoom = false, reportMetadata: OnyxCollection = {}, + policyID?: string, + policyMemberAccountIDs: number[] = [], ): OnyxEntry { // If it's the user's first time using New Expensify, then they could either have: // - just a Concierge report, if so we'll return that // - their Concierge report, and a separate report that must have deeplinked them to the app before they created their account. // If it's the latter, we'll use the deeplinked report over the Concierge report, // since the Concierge report would be incorrectly selected over the deep-linked report in the logic below. - let sortedReports = sortReportsByLastRead(reports, reportMetadata); + + let reportsValues = Object.values(reports ?? {}) as Report[]; + + if (!!policyID || policyMemberAccountIDs.length > 0) { + reportsValues = filterReportsByPolicyIdAndMemberAccountIDs(reportsValues, policyID, policyMemberAccountIDs); + } + + let sortedReports = sortReportsByLastRead(reportsValues, reportMetadata); let adminReport: OnyxEntry | undefined; if (openOnAdminRoom) { @@ -4575,6 +4620,7 @@ export { getReportFieldTitle, shouldDisplayThreadReplies, shouldDisableThread, + doesReportBelongToWorkspace, getChildReportNotificationPreference, }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 6e46ec320066..332ee61a29d8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -120,11 +120,22 @@ function getOrderedReportIDs( policies: Record, priorityMode: ValueOf, allReportActions: OnyxCollection, + currentPolicyID = '', + policyMemberAccountIDs: number[] = [], ): string[] { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - [currentReportId, allReports, betas, policies, priorityMode, allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], + [ + currentReportId, + allReports, + betas, + policies, + priorityMode, + allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length ?? 1, + currentPolicyID, + policyMemberAccountIDs, + ], (key, value: unknown) => { /** * Exclude some properties not to overwhelm a cached key value with huge data, @@ -151,7 +162,7 @@ function getOrderedReportIDs( const isInDefaultMode = !isInGSDMode; const allReportsDictValues = Object.values(allReports); // Filter out all the reports that shouldn't be displayed - const reportsToDisplay = allReportsDictValues.filter((report) => ReportUtils.shouldReportBeInOptionList(report, currentReportId ?? '', isInGSDMode, betas, policies, true)); + let reportsToDisplay = allReportsDictValues.filter((report) => ReportUtils.shouldReportBeInOptionList(report, currentReportId ?? '', isInGSDMode, betas, policies, true)); if (reportsToDisplay.length === 0) { // Display Concierge chat report when there is no report to be displayed @@ -175,6 +186,9 @@ function getOrderedReportIDs( const nonArchivedReports: Report[] = []; const archivedReports: Report[] = []; + if (currentPolicyID || policyMemberAccountIDs.length > 0) { + reportsToDisplay = reportsToDisplay.filter((report) => ReportUtils.doesReportBelongToWorkspace(report, currentPolicyID, policyMemberAccountIDs)); + } // There are a few properties that need to be calculated for the report which are used when sorting reports. reportsToDisplay.forEach((report) => { // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 263d5fb68529..cbbc00dd42fc 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1488,6 +1488,28 @@ function openWorkspaceReimburseView(policyID: string) { API.read('OpenWorkspaceReimburseView', params, {successData, failureData}); } +/** + * Returns the accountIDs of the members of the policy whose data is passed in the parameters + */ +function openWorkspace(policyID: string, clientMemberAccountIDs: number[]) { + if (!policyID || !clientMemberAccountIDs) { + Log.warn('openWorkspace invalid params', {policyID, clientMemberAccountIDs}); + return; + } + + type OpenWorkspaceParams = { + policyID: string; + clientMemberAccountIDs: string; + }; + + const params: OpenWorkspaceParams = { + policyID, + clientMemberAccountIDs: JSON.stringify(clientMemberAccountIDs), + }; + + API.read('OpenWorkspace', params); +} + function openWorkspaceMembersPage(policyID: string, clientMemberEmails: string[]) { if (!policyID || !clientMemberEmails) { Log.warn('openWorkspaceMembersPage invalid params', {policyID, clientMemberEmails}); @@ -2063,6 +2085,7 @@ export { createWorkspace, openWorkspaceMembersPage, openWorkspaceInvitePage, + openWorkspace, removeWorkspace, createWorkspaceFromIOUPayment, setWorkspaceInviteMembersDraft, diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index c9e8053e3146..23b748068b65 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -588,15 +588,15 @@ describe('ReportUtils', () => { describe('sortReportsByLastRead', () => { it('should filter out report without reportID & lastReadTime and sort lastReadTime in ascending order', () => { - const reports = { - 1: {reportID: 1, lastReadTime: '2023-07-08 07:15:44.030'}, - 2: {reportID: 2, lastReadTime: null}, - 3: {reportID: 3, lastReadTime: '2023-07-06 07:15:44.030'}, - 4: {reportID: 4, lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, - 5: {lastReadTime: '2023-07-09 07:15:44.030'}, - 6: {reportID: 6}, - 7: {}, - }; + const reports = [ + {reportID: 1, lastReadTime: '2023-07-08 07:15:44.030'}, + {reportID: 2, lastReadTime: null}, + {reportID: 3, lastReadTime: '2023-07-06 07:15:44.030'}, + {reportID: 4, lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, + {lastReadTime: '2023-07-09 07:15:44.030'}, + {reportID: 6}, + {}, + ]; const sortedReports = [ {reportID: 3, lastReadTime: '2023-07-06 07:15:44.030'}, {reportID: 4, lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU},