Skip to content

Commit 5af1101

Browse files
committed
feat: allow linking to oldest unread report action page in hook
1 parent 29e1abf commit 5af1101

2 files changed

Lines changed: 93 additions & 26 deletions

File tree

src/hooks/usePaginatedReportActions.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useCallback, useMemo} from 'react';
1+
import {useCallback, useMemo, useRef} from 'react';
22
import type {OnyxEntry} from 'react-native-onyx';
33
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
44
import PaginationUtils from '@libs/PaginationUtils';
@@ -9,10 +9,17 @@ import type {ReportAction, ReportActions} from '@src/types/onyx';
99
import useOnyx from './useOnyx';
1010
import useReportIsArchived from './useReportIsArchived';
1111

12+
type UsePaginatedReportActionsOptions = {
13+
/** Whether to link to the oldest unread report action, if no other report action id is provided. */
14+
shouldLinkToOldestUnreadReportAction?: boolean;
15+
};
16+
1217
/**
1318
* Get the longest continuous chunk of reportActions including the linked reportAction. If not linking to a specific action, returns the continuous chunk of newest reportActions.
1419
*/
15-
function usePaginatedReportActions(reportID: string | undefined, reportActionID?: string) {
20+
function usePaginatedReportActions(reportID: string | undefined, reportActionID?: string, options?: UsePaginatedReportActionsOptions) {
21+
const {shouldLinkToOldestUnreadReportAction = false} = options ?? {};
22+
1623
const nonEmptyStringReportID = getNonEmptyStringOnyxID(reportID);
1724
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${nonEmptyStringReportID}`, {canBeMissing: true});
1825
const isReportArchived = useReportIsArchived(report?.reportID);
@@ -36,25 +43,52 @@ function usePaginatedReportActions(reportID: string | undefined, reportActionID?
3643
);
3744
const [reportActionPages] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${nonEmptyStringReportID}`, {canBeMissing: true});
3845

46+
const initialReportLastReadTime = useRef(report?.lastReadTime);
47+
48+
const id = useMemo(() => {
49+
if (reportActionID) {
50+
return reportActionID;
51+
}
52+
53+
if (!shouldLinkToOldestUnreadReportAction) {
54+
return undefined;
55+
}
56+
57+
return sortedAllReportActions?.findLast((reportAction) => {
58+
if (!initialReportLastReadTime.current) {
59+
return false;
60+
}
61+
62+
return reportAction.created > initialReportLastReadTime.current;
63+
})?.reportActionID;
64+
}, [reportActionID, shouldLinkToOldestUnreadReportAction, sortedAllReportActions]);
65+
3966
const {
4067
data: reportActions,
4168
hasNextPage,
4269
hasPreviousPage,
70+
resourceItem,
4371
} = useMemo(() => {
4472
if (!sortedAllReportActions?.length) {
4573
return {data: [], hasNextPage: false, hasPreviousPage: false};
4674
}
47-
return PaginationUtils.getContinuousChain(sortedAllReportActions, reportActionPages ?? [], (reportAction) => reportAction.reportActionID, reportActionID);
48-
}, [reportActionID, reportActionPages, sortedAllReportActions]);
4975

50-
const linkedAction = useMemo(
51-
() => (reportActionID ? sortedAllReportActions?.find((reportAction) => String(reportAction.reportActionID) === String(reportActionID)) : undefined),
52-
[reportActionID, sortedAllReportActions],
53-
);
76+
return PaginationUtils.getContinuousChain(sortedAllReportActions, reportActionPages ?? [], (reportAction) => reportAction.reportActionID, id);
77+
}, [id, reportActionPages, sortedAllReportActions]);
78+
79+
const linkedAction = useMemo(() => (reportActionID ? resourceItem?.item : undefined), [resourceItem, reportActionID]);
80+
81+
const oldestUnreadReportAction = useMemo(() => {
82+
if (shouldLinkToOldestUnreadReportAction && resourceItem && !reportActionID) {
83+
return resourceItem.item;
84+
}
85+
return undefined;
86+
}, [resourceItem, shouldLinkToOldestUnreadReportAction, reportActionID]);
5487

5588
return {
5689
reportActions,
5790
linkedAction,
91+
oldestUnreadReportAction,
5892
sortedAllReportActions,
5993
hasOlderActions: hasNextPage,
6094
hasNewerActions: hasPreviousPage,

src/libs/PaginationUtils.ts

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ type ItemWithIndex = {
2525
index: number;
2626
};
2727

28+
type ResourceItemResult<TResource> = {
29+
index: number;
30+
id: string;
31+
item: TResource;
32+
};
33+
34+
type ContinuousPageChainResult<TResource> = {
35+
data: TResource[];
36+
hasNextPage: boolean;
37+
hasPreviousPage: boolean;
38+
resourceItem?: ResourceItemResult<TResource>;
39+
};
40+
2841
/**
2942
* Finds the id and index in sortedItems of the first item in the given page that's present in sortedItems.
3043
*/
@@ -163,15 +176,30 @@ function mergeAndSortContinuousPages<TResource>(sortedItems: TResource[], pages:
163176
*
164177
* Note: sortedItems should be sorted in descending order.
165178
*/
166-
function getContinuousChain<TResource>(
167-
sortedItems: TResource[],
168-
pages: Pages,
169-
getID: (item: TResource) => string,
170-
id?: string,
171-
): {data: TResource[]; hasNextPage: boolean; hasPreviousPage: boolean} {
179+
function getContinuousChain<TResource>(sortedItems: TResource[], pages: Pages, getID: (item: TResource) => string, id?: string): ContinuousPageChainResult<TResource> {
180+
// If an id is provided, find the index of the item with that id
181+
let index = -1;
182+
183+
if (id) {
184+
index = sortedItems.findIndex((item) => getID(item) === id);
185+
}
186+
const didFindItem = index !== -1;
187+
188+
// Return the found resource item if it exists
189+
let resourceItem: ResourceItemResult<TResource> | undefined;
190+
if (didFindItem) {
191+
const item = sortedItems.at(index);
192+
if (item) {
193+
resourceItem = {
194+
index,
195+
item,
196+
id: getID(item),
197+
};
198+
}
199+
}
200+
172201
if (pages.length === 0) {
173-
const dataItem = sortedItems.find((item) => getID(item) === id);
174-
return {data: id && !dataItem ? [] : sortedItems, hasNextPage: false, hasPreviousPage: false};
202+
return {data: !!id && !didFindItem ? [] : sortedItems, hasNextPage: false, hasPreviousPage: false, resourceItem};
175203
}
176204

177205
const pagesWithIndexes = getPagesWithIndexes(sortedItems, pages, getID);
@@ -184,37 +212,42 @@ function getContinuousChain<TResource>(
184212
lastIndex: 0,
185213
};
186214

215+
// If we found an item with the resource id, we want link to the specific page with the item
187216
if (id) {
188-
const index = sortedItems.findIndex((item) => getID(item) === id);
189-
190-
// If we are linking to an action that doesn't exist in Onyx, return an empty array
191-
if (index === -1) {
192-
return {data: [], hasNextPage: false, hasPreviousPage: false};
217+
// If we are searching for an item with a specific resource id and
218+
// we are linking to an action that doesn't exist in Onyx, return an empty array
219+
if (!didFindItem) {
220+
return {data: [], hasNextPage: false, hasPreviousPage: false, resourceItem};
193221
}
194222

195223
const linkedPage = pagesWithIndexes.find((pageIndex) => index >= pageIndex.firstIndex && index <= pageIndex.lastIndex);
196224

197-
const item = sortedItems.at(index);
198225
// If we are linked to an action in a gap return it by itself
199-
if (!linkedPage && item) {
200-
return {data: [item], hasNextPage: false, hasPreviousPage: false};
226+
if (!linkedPage && resourceItem) {
227+
return {data: [resourceItem.item], hasNextPage: false, hasPreviousPage: false, resourceItem};
201228
}
202229

203230
if (linkedPage) {
204231
page = linkedPage;
205232
}
206233
} else {
234+
// If we did not find an item with the resource id, we want to link to the first page
207235
const pageAtIndex0 = pagesWithIndexes.at(0);
208236
if (pageAtIndex0) {
209237
page = pageAtIndex0;
210238
}
211239
}
212240

213241
if (!page) {
214-
return {data: sortedItems, hasNextPage: false, hasPreviousPage: false};
242+
return {data: sortedItems, hasNextPage: false, hasPreviousPage: false, resourceItem};
215243
}
216244

217-
return {data: sortedItems.slice(page.firstIndex, page.lastIndex + 1), hasNextPage: page.lastID !== CONST.PAGINATION_END_ID, hasPreviousPage: page.firstID !== CONST.PAGINATION_START_ID};
245+
return {
246+
data: sortedItems.slice(page.firstIndex, page.lastIndex + 1),
247+
hasNextPage: page.lastID !== CONST.PAGINATION_END_ID,
248+
hasPreviousPage: page.firstID !== CONST.PAGINATION_START_ID,
249+
resourceItem,
250+
};
218251
}
219252

220253
export default {mergeAndSortContinuousPages, getContinuousChain};

0 commit comments

Comments
 (0)