diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 3fbe9cf86e15..f9cf62c536c4 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2566,7 +2566,7 @@ const updatePrivateNotes = (reportID: string, accountID: number, note: string) = }; /** Fetches all the private notes for a given report */ -function getReportPrivateNote(reportID: string) { +function getReportPrivateNote(reportID: string | undefined) { if (Session.isAnonymousUser()) { return; } diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index eb3dd00ff802..aa02dfddd44c 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -22,6 +22,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; +import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import * as ReportActions from '@userActions/Report'; import CONST from '@src/CONST'; @@ -36,7 +37,8 @@ type PrivateNotesEditPageOnyxProps = { personalDetailsList: OnyxCollection; }; -type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps & +type PrivateNotesEditPageProps = WithReportAndPrivateNotesOrNotFoundProps & + PrivateNotesEditPageOnyxProps & StackScreenProps & { /** The report currently being looked at */ report: Report; diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx index d7fb1f6497be..97ebc7dee2fb 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx @@ -1,7 +1,7 @@ import React, {useMemo} from 'react'; import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -10,24 +10,23 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails, Report, Session} from '@src/types/onyx'; +import type {PersonalDetails, Report} from '@src/types/onyx'; type PrivateNotesListPageOnyxProps = { /** All of the personal details for everyone */ personalDetailsList: OnyxCollection; - - /** Session info for the currently logged in user. */ - session: OnyxEntry; }; -type PrivateNotesListPageProps = PrivateNotesListPageOnyxProps & { - /** The report currently being looked at */ - report: Report; -}; +type PrivateNotesListPageProps = WithReportAndPrivateNotesOrNotFoundProps & + PrivateNotesListPageOnyxProps & { + /** The report currently being looked at */ + report: Report; + }; type NoteListItem = { title: string; @@ -101,8 +100,5 @@ export default withReportAndPrivateNotesOrNotFound('privateNotes.title')( personalDetailsList: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, - session: { - key: ONYXKEYS.SESSION, - }, })(PrivateNotesListPage), ); diff --git a/src/pages/ReportDescriptionPage.tsx b/src/pages/ReportDescriptionPage.tsx index 3ccabf30c1b7..6062ef748f36 100644 --- a/src/pages/ReportDescriptionPage.tsx +++ b/src/pages/ReportDescriptionPage.tsx @@ -1,22 +1,14 @@ -import type {RouteProp} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; -import type {OnyxCollection} from 'react-native-onyx'; import * as ReportUtils from '@libs/ReportUtils'; -import type * as OnyxTypes from '@src/types/onyx'; +import type {ReportDescriptionNavigatorParamList} from '@navigation/types'; +import type SCREENS from '@src/SCREENS'; +import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; import withReportOrNotFound from './home/report/withReportOrNotFound'; import RoomDescriptionPage from './RoomDescriptionPage'; import TaskDescriptionPage from './tasks/TaskDescriptionPage'; -type ReportDescriptionPageProps = { - /** The report currently being looked at */ - report: OnyxTypes.Report; - - /** Policy for the current report */ - policies: OnyxCollection; - - /** Route params */ - route: RouteProp<{params: {reportID: string}}>; -}; +type ReportDescriptionPageProps = WithReportOrNotFoundProps & StackScreenProps; function ReportDescriptionPage(props: ReportDescriptionPageProps) { const isTask = ReportUtils.isTaskReport(props.report); diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js deleted file mode 100644 index 6ca7b4b26f91..000000000000 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js +++ /dev/null @@ -1,140 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; -import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; -import * as Report from '@libs/actions/Report'; -import compose from '@libs/compose'; -import getComponentDisplayName from '@libs/getComponentDisplayName'; -import * as ReportUtils from '@libs/ReportUtils'; -import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; -import LoadingPage from '@pages/LoadingPage'; -import reportPropTypes from '@pages/reportPropTypes'; -import ONYXKEYS from '@src/ONYXKEYS'; -import withReportOrNotFound from './withReportOrNotFound'; - -const propTypes = { - /** The HOC takes an optional ref as a prop and passes it as a ref to the wrapped component. - * That way, if a ref is passed to a component wrapped in the HOC, the ref is a reference to the wrapped component, not the HOC. */ - forwardedRef: PropTypes.func, - - /** The report currently being looked at */ - report: reportPropTypes, - - /** Information about the network */ - network: networkPropTypes.isRequired, - - /** Session of currently logged in user */ - session: PropTypes.shape({ - /** accountID of currently logged in user */ - accountID: PropTypes.number, - }), - - route: PropTypes.shape({ - /** Params from the URL path */ - params: PropTypes.shape({ - /** reportID and accountID passed via route: /r/:reportID/notes/:accountID */ - reportID: PropTypes.string, - accountID: PropTypes.string, - }), - }).isRequired, -}; - -const defaultProps = { - forwardedRef: () => {}, - report: {}, - session: { - accountID: null, - }, -}; - -export default function (pageTitle) { - // eslint-disable-next-line rulesdir/no-negated-variables - return (WrappedComponent) => { - // eslint-disable-next-line rulesdir/no-negated-variables - function WithReportAndPrivateNotesOrNotFound({forwardedRef, ...props}) { - const {translate} = useLocalize(); - const {route, report, network, session} = props; - const accountID = route.params.accountID; - const isPrivateNotesFetchTriggered = !_.isUndefined(report.isLoadingPrivateNotes); - const prevIsOffline = usePrevious(network.isOffline); - const isReconnecting = prevIsOffline && !network.isOffline; - const isOtherUserNote = accountID && Number(session.accountID) !== Number(accountID); - const isPrivateNotesFetchFinished = isPrivateNotesFetchTriggered && !report.isLoadingPrivateNotes; - const isPrivateNotesEmpty = accountID ? _.has(lodashGet(report, ['privateNotes', accountID, 'note'], '')) : _.isEmpty(report.privateNotes); - - useEffect(() => { - // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. - if ((isPrivateNotesFetchTriggered && !isReconnecting) || network.isOffline) { - return; - } - - Report.getReportPrivateNote(report.reportID); - // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add report.isLoadingPrivateNotes to dependencies - }, [report.reportID, network.isOffline, isPrivateNotesFetchTriggered, isReconnecting]); - - const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchFinished || (isPrivateNotesEmpty && (report.isLoadingPrivateNotes || !isOtherUserNote)); - - // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = useMemo(() => { - // Show not found view if the report is archived, or if the note is not of current user. - if (ReportUtils.isArchivedRoom(report) || (accountID && Number(session.accountID) !== Number(accountID))) { - return true; - } - - // Don't show not found view if the notes are still loading, or if the notes are non-empty. - if (shouldShowFullScreenLoadingIndicator || !isPrivateNotesEmpty || isReconnecting) { - return false; - } - - // As notes being empty and not loading is a valid case, show not found view only in offline mode. - return network.isOffline; - }, [report, network.isOffline, accountID, session.accountID, isPrivateNotesEmpty, shouldShowFullScreenLoadingIndicator, isReconnecting]); - - if (shouldShowFullScreenLoadingIndicator) { - return ; - } - - if (shouldShowNotFoundPage) { - return ; - } - - return ( - - ); - } - - WithReportAndPrivateNotesOrNotFound.propTypes = propTypes; - WithReportAndPrivateNotesOrNotFound.defaultProps = defaultProps; - WithReportAndPrivateNotesOrNotFound.displayName = `withReportAndPrivateNotesOrNotFound(${getComponentDisplayName(WrappedComponent)})`; - - // eslint-disable-next-line rulesdir/no-negated-variables - const WithReportAndPrivateNotesOrNotFoundWithRef = React.forwardRef((props, ref) => ( - - )); - - WithReportAndPrivateNotesOrNotFoundWithRef.displayName = 'WithReportAndPrivateNotesOrNotFoundWithRef'; - - return compose( - withReportOrNotFound(), - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), - withNetwork(), - )(WithReportAndPrivateNotesOrNotFoundWithRef); - }; -} diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx new file mode 100644 index 000000000000..d8d461568a45 --- /dev/null +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -0,0 +1,102 @@ +import React, {useEffect, useMemo} from 'react'; +import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import usePrevious from '@hooks/usePrevious'; +import * as Report from '@libs/actions/Report'; +import getComponentDisplayName from '@libs/getComponentDisplayName'; +import * as ReportUtils from '@libs/ReportUtils'; +import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; +import LoadingPage from '@pages/LoadingPage'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {WithReportOrNotFoundOnyxProps, WithReportOrNotFoundProps} from './withReportOrNotFound'; +import withReportOrNotFound from './withReportOrNotFound'; + +type WithReportAndPrivateNotesOrNotFoundOnyxProps = { + /** Session of currently logged in user */ + session: OnyxEntry; +}; + +type WithReportAndPrivateNotesOrNotFoundProps = WithReportAndPrivateNotesOrNotFoundOnyxProps & WithReportOrNotFoundProps; + +export default function (pageTitle: TranslationPaths) { + // eslint-disable-next-line rulesdir/no-negated-variables + return ( + WrappedComponent: ComponentType>, + ): React.ComponentType & RefAttributes, keyof WithReportOrNotFoundOnyxProps>> => { + // eslint-disable-next-line rulesdir/no-negated-variables + function WithReportAndPrivateNotesOrNotFound(props: TProps, ref: ForwardedRef) { + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + const {route, report, session} = props; + const accountID = ('accountID' in route.params && route.params.accountID) || ''; + const isPrivateNotesFetchTriggered = report.isLoadingPrivateNotes !== undefined; + const prevIsOffline = usePrevious(isOffline); + const isReconnecting = prevIsOffline && !isOffline; + const isOtherUserNote = !!accountID && Number(session?.accountID) !== Number(accountID); + const isPrivateNotesFetchFinished = isPrivateNotesFetchTriggered && !report.isLoadingPrivateNotes; + const isPrivateNotesEmpty = accountID ? !report?.privateNotes?.[Number(accountID)]?.note : isEmptyObject(report?.privateNotes); + + useEffect(() => { + // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. + if ((isPrivateNotesFetchTriggered && !isReconnecting) || isOffline) { + return; + } + + Report.getReportPrivateNote(report.reportID); + // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add report.isLoadingPrivateNotes to dependencies + }, [report.reportID, isOffline, isPrivateNotesFetchTriggered, isReconnecting]); + + const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchFinished; + + // eslint-disable-next-line rulesdir/no-negated-variables + const shouldShowNotFoundPage = useMemo(() => { + // Show not found view if the report is archived, or if the note is not of current user. + if (ReportUtils.isArchivedRoom(report) || isOtherUserNote) { + return true; + } + + // Don't show not found view if the notes are still loading, or if the notes are non-empty. + if (shouldShowFullScreenLoadingIndicator || !isPrivateNotesEmpty || isReconnecting) { + return false; + } + + // As notes being empty and not loading is a valid case, show not found view only in offline mode. + return isOffline; + }, [report, isOtherUserNote, shouldShowFullScreenLoadingIndicator, isPrivateNotesEmpty, isReconnecting, isOffline]); + + if (shouldShowFullScreenLoadingIndicator) { + return ; + } + + if (shouldShowNotFoundPage) { + return ; + } + + return ( + + ); + } + + WithReportAndPrivateNotesOrNotFound.displayName = `withReportAndPrivateNotesOrNotFound(${getComponentDisplayName(WrappedComponent)})`; + + return withReportOrNotFound()( + withOnyx, WithReportAndPrivateNotesOrNotFoundOnyxProps>({ + session: { + key: ONYXKEYS.SESSION, + }, + })(WithReportAndPrivateNotesOrNotFound), + ); + }; +} + +export type {WithReportAndPrivateNotesOrNotFoundProps}; diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx index 238b97dd190d..8054245e1b71 100644 --- a/src/pages/home/report/withReportOrNotFound.tsx +++ b/src/pages/home/report/withReportOrNotFound.tsx @@ -7,9 +7,11 @@ import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import * as ReportUtils from '@libs/ReportUtils'; +import type {PrivateNotesNavigatorParamList, ReportDescriptionNavigatorParamList} from '@navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Report from '@userActions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -31,7 +33,7 @@ type WithReportOrNotFoundOnyxProps = { }; type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & { - route: RouteProp<{params: {reportID: string}}>; + route: RouteProp | RouteProp; /** The report currently being looked at */ report: OnyxTypes.Report; @@ -117,4 +119,4 @@ export default function ( }; } -export type {WithReportOrNotFoundProps}; +export type {WithReportOrNotFoundProps, WithReportOrNotFoundOnyxProps};