diff --git a/src/components/CategoryPicker.tsx b/src/components/CategoryPicker.tsx index e5c85a8f5f6d..9957a170b3e7 100644 --- a/src/components/CategoryPicker.tsx +++ b/src/components/CategoryPicker.tsx @@ -1,34 +1,31 @@ import React, {useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import SelectionList from './SelectionList'; import RadioListItem from './SelectionList/RadioListItem'; import type {ListItem} from './SelectionList/types'; -type CategoryPickerOnyxProps = { - policyCategories: OnyxEntry; - policyCategoriesDraft: OnyxEntry; - policyRecentlyUsedCategories: OnyxEntry; -}; - -type CategoryPickerProps = CategoryPickerOnyxProps & { - /** It's used by withOnyx HOC */ - // eslint-disable-next-line react/no-unused-prop-types +type CategoryPickerProps = { policyID: string; selectedCategory?: string; onSubmit: (item: ListItem) => void; }; -function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, policyCategoriesDraft, onSubmit}: CategoryPickerProps) { +function CategoryPicker({selectedCategory, policyID, onSubmit}: CategoryPickerProps) { + const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); + const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`); + const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`); + const {isOffline} = useNetwork(); + const {translate} = useLocalize(); const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); + const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const selectedOptions = useMemo(() => { if (!selectedCategory) { @@ -79,6 +76,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC headerMessage={headerMessage} textInputValue={searchValue} textInputLabel={shouldShowTextInput ? translate('common.search') : undefined} + textInputHint={offlineMessage} onChangeText={setSearchValue} onSelectRow={onSubmit} ListItem={RadioListItem} @@ -90,14 +88,4 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC CategoryPicker.displayName = 'CategoryPicker'; -export default withOnyx({ - policyCategories: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - }, - policyCategoriesDraft: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`, - }, - policyRecentlyUsedCategories: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`, - }, -})(CategoryPicker); +export default CategoryPicker; diff --git a/src/libs/API/parameters/GetPolicyCategories.ts b/src/libs/API/parameters/GetPolicyCategories.ts new file mode 100644 index 000000000000..d97edc67858c --- /dev/null +++ b/src/libs/API/parameters/GetPolicyCategories.ts @@ -0,0 +1,5 @@ +type GetPolicyCategories = { + policyID: string; +}; + +export default GetPolicyCategories; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 9f51cab3f360..6d16c29af0d4 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -26,6 +26,7 @@ export type {default as ExpandURLPreviewParams} from './ExpandURLPreviewParams'; export type {default as GetMissingOnyxMessagesParams} from './GetMissingOnyxMessagesParams'; export type {default as GetNewerActionsParams} from './GetNewerActionsParams'; export type {default as GetOlderActionsParams} from './GetOlderActionsParams'; +export type {default as GetPolicyCategoriesParams} from './GetPolicyCategories'; export type {default as GetReportPrivateNoteParams} from './GetReportPrivateNoteParams'; export type {default as GetRouteParams} from './GetRouteParams'; export type {default as GetStatementPDFParams} from './GetStatementPDFParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index b72b77ae4739..763b51fec705 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -859,6 +859,7 @@ const READ_COMMANDS = { BEGIN_SIGNIN: 'BeginSignIn', SIGN_IN_WITH_SHORT_LIVED_AUTH_TOKEN: 'SignInWithShortLivedAuthToken', SIGN_IN_WITH_SUPPORT_AUTH_TOKEN: 'SignInWithSupportAuthToken', + GET_POLICY_CATEGORIES: 'GetPolicyCategories', OPEN_WORKSPACE: 'OpenWorkspace', OPEN_WORKSPACE_MEMBERS_PAGE: 'OpenWorkspaceMembersPage', OPEN_POLICY_CATEGORIES_PAGE: 'OpenPolicyCategoriesPage', @@ -916,6 +917,7 @@ type ReadCommandParameters = { [READ_COMMANDS.BEGIN_SIGNIN]: Parameters.BeginSignInParams; [READ_COMMANDS.SIGN_IN_WITH_SHORT_LIVED_AUTH_TOKEN]: Parameters.SignInWithShortLivedAuthTokenParams; [READ_COMMANDS.SIGN_IN_WITH_SUPPORT_AUTH_TOKEN]: Parameters.SignInWithSupportAuthTokenParams; + [READ_COMMANDS.GET_POLICY_CATEGORIES]: Parameters.GetPolicyCategoriesParams; [READ_COMMANDS.OPEN_WORKSPACE]: Parameters.OpenWorkspaceParams; [READ_COMMANDS.OPEN_WORKSPACE_MEMBERS_PAGE]: Parameters.OpenWorkspaceMembersPageParams; [READ_COMMANDS.OPEN_POLICY_CATEGORIES_PAGE]: Parameters.OpenPolicyCategoriesPageParams; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 2a25178f26a6..787142902053 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -274,11 +274,11 @@ Onyx.connect({ }, }); -let allPolicyCategories: OnyxCollection = {}; +let allPolicies: OnyxCollection = {}; Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY_CATEGORIES, + key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, - callback: (val) => (allPolicyCategories = val), + callback: (val) => (allPolicies = val), }); const lastReportActions: ReportActions = {}; @@ -1975,8 +1975,8 @@ function getOptions( reportOption.isBold = shouldBoldTitleByDefault || shouldUseBoldText(reportOption); if (action === CONST.IOU.ACTION.CATEGORIZE) { - const policyCategories = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${reportOption.policyID}`] ?? {}; - if (getEnabledCategoriesCount(policyCategories) !== 0) { + const reportPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportOption.policyID}`]; + if (reportPolicy?.areCategoriesEnabled) { recentReportOptions.push(reportOption); } } else { diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index c342fe6eedb6..ee25accf61a7 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -5,6 +5,7 @@ import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; import type { EnablePolicyCategoriesParams, + GetPolicyCategoriesParams, OpenPolicyCategoriesPageParams, RemovePolicyCategoryReceiptsRequiredParams, SetPolicyCategoryApproverParams, @@ -187,6 +188,19 @@ function openPolicyCategoriesPage(policyID: string) { API.read(READ_COMMANDS.OPEN_POLICY_CATEGORIES_PAGE, params); } +function getPolicyCategories(policyID: string) { + if (!policyID || policyID === '-1' || policyID === CONST.POLICY.ID_FAKE) { + Log.warn('GetPolicyCategories invalid params', {policyID}); + return; + } + + const params: GetPolicyCategoriesParams = { + policyID, + }; + + API.read(READ_COMMANDS.GET_POLICY_CATEGORIES, params); +} + function buildOptimisticPolicyRecentlyUsedCategories(policyID?: string, category?: string) { if (!policyID || !category) { return []; @@ -1312,6 +1326,7 @@ function setPolicyCategoryTax(policyID: string, categoryName: string, taxID: str } export { + getPolicyCategories, openPolicyCategoriesPage, buildOptimisticPolicyRecentlyUsedCategories, setWorkspaceCategoryEnabled, diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index f3388fcc7b0e..10c10f932aee 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -1,8 +1,8 @@ import lodashIsEmpty from 'lodash/isEmpty'; import React, {useEffect} from 'react'; import {ActivityIndicator, View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; +import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import Button from '@components/Button'; import CategoryPicker from '@components/CategoryPicker'; import FixedFooter from '@components/FixedFooter'; @@ -21,46 +21,18 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; -import * as PolicyActions from '@userActions/Policy/Policy'; +import * as Category from '@userActions/Policy/Category'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Policy, PolicyCategories, PolicyTagLists, ReportActions, Session, Transaction} from '@src/types/onyx'; import StepScreenWrapper from './StepScreenWrapper'; import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -type IOURequestStepCategoryOnyxProps = { - /** The draft transaction that holds data to be persisted on the current transaction */ - splitDraftTransaction: OnyxEntry; - - /** The policy of the report */ - policy: OnyxEntry; - - /** The draft policy of the report */ - policyDraft: OnyxEntry; - - /** Collection of categories attached to a policy */ - policyCategories: OnyxEntry; - - /** Collection of draft categories attached to a policy */ - policyCategoriesDraft: OnyxEntry; - - /** Collection of tags attached to a policy */ - policyTags: OnyxEntry; - - /** The actions from the parent report */ - reportActions: OnyxEntry; - - /** Session info for the currently logged in user. */ - session: OnyxEntry; -}; - -type IOURequestStepCategoryProps = IOURequestStepCategoryOnyxProps & - WithWritableReportOrNotFoundProps & +type IOURequestStepCategoryProps = WithWritableReportOrNotFoundProps & WithFullTransactionOrNotFoundProps; function IOURequestStepCategory({ @@ -70,15 +42,24 @@ function IOURequestStepCategory({ params: {transactionID, backTo, action, iouType, reportActionID}, }, transaction, - splitDraftTransaction, - policy: policyReal, - policyDraft, - policyTags, - policyCategories: policyCategoriesReal, - policyCategoriesDraft, - reportActions, - session, }: IOURequestStepCategoryProps) { + const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID ?? '-1'}`); + const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, reportReal)}`); + const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`); + const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, reportReal)}`); + const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`); + const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, reportReal)}`); + let reportID = '-1'; + if (action === CONST.IOU.ACTION.EDIT && reportReal) { + if (iouType === CONST.IOU.TYPE.SPLIT) { + reportID = reportReal.reportID; + } else if (reportReal.parentReportID) { + reportID = reportReal.parentReportID; + } + } + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {canEvict: false}); + const [session] = useOnyx(ONYXKEYS.SESSION); + const report = reportReal ?? reportDraft; const policy = policyReal ?? policyDraft; const policyCategories = policyCategoriesReal ?? policyCategoriesDraft; @@ -108,11 +89,12 @@ function IOURequestStepCategory({ return; } - PolicyActions.openDraftWorkspaceRequest(report?.policyID ?? '-1'); + Category.getPolicyCategories(report?.policyID ?? '-1'); }; const {isOffline} = useNetwork({onReconnect: fetchData}); const isLoading = !isOffline && policyCategories === undefined; - const shouldShowEmptyState = !isLoading && !shouldShowCategory; + const shouldShowEmptyState = policyCategories !== undefined && !shouldShowCategory; + const shouldShowOfflineView = policyCategories === undefined && isOffline; useEffect(() => { fetchData(); @@ -159,6 +141,7 @@ function IOURequestStepCategory({ onBackButtonPress={navigateBack} shouldShowWrapper shouldShowNotFoundPage={shouldShowNotFoundPage} + shouldShowOfflineIndicator={policyCategories !== undefined} testID={IOURequestStepCategory.displayName} > {isLoading && ( @@ -168,6 +151,7 @@ function IOURequestStepCategory({ color={theme.spinner} /> )} + {shouldShowOfflineView && {null}} {shouldShowEmptyState && ( )} - {!shouldShowEmptyState && !isLoading && ( + {!shouldShowEmptyState && !isLoading && !shouldShowOfflineView && ( <> {translate('iou.categorySelection')} ({ - splitDraftTransaction: { - key: ({route}) => { - const transactionID = route?.params.transactionID ?? -1; - return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`; - }, - }, - policy: { - key: ({report, transaction}) => `${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, report)}`, - }, - policyDraft: { - key: ({reportDraft, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`, - }, - policyCategories: { - key: ({report, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, report)}`, - }, - policyCategoriesDraft: { - key: ({reportDraft, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`, - }, - policyTags: { - key: ({report, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, report)}`, - }, - reportActions: { - key: ({ - report, - route: { - params: {action, iouType}, - }, - }) => { - let reportID = '-1'; - if (action === CONST.IOU.ACTION.EDIT && report) { - if (iouType === CONST.IOU.TYPE.SPLIT) { - reportID = report.reportID; - } else if (report.parentReportID) { - reportID = report.parentReportID; - } - } - return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`; - }, - canEvict: false, - }, - session: { - key: ONYXKEYS.SESSION, - }, -})(IOURequestStepCategory); /* eslint-disable rulesdir/no-negated-variables */ -const IOURequestStepCategoryWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepCategoryWithOnyx); +const IOURequestStepCategoryWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepCategory); /* eslint-disable rulesdir/no-negated-variables */ const IOURequestStepCategoryWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepCategoryWithFullTransactionOrNotFound); export default IOURequestStepCategoryWithWritableReportOrNotFound; diff --git a/src/pages/iou/request/step/StepScreenWrapper.tsx b/src/pages/iou/request/step/StepScreenWrapper.tsx index 077711b3a919..e67a78236506 100644 --- a/src/pages/iou/request/step/StepScreenWrapper.tsx +++ b/src/pages/iou/request/step/StepScreenWrapper.tsx @@ -25,6 +25,9 @@ type StepScreenWrapperProps = { /** Whether or not to display not found page */ shouldShowNotFoundPage?: boolean; + /** Whether to show offline indicator */ + shouldShowOfflineIndicator?: boolean; + /** An ID used for unit testing */ testID: string; @@ -44,6 +47,7 @@ function StepScreenWrapper({ shouldShowWrapper, shouldShowNotFoundPage, includeSafeAreaPaddingBottom, + shouldShowOfflineIndicator = true, }: StepScreenWrapperProps) { const styles = useThemeStyles(); @@ -57,6 +61,7 @@ function StepScreenWrapper({ onEntryTransitionEnd={onEntryTransitionEnd} testID={testID} shouldEnableMaxHeight={DeviceCapabilities.canUseTouchScreen()} + shouldShowOfflineIndicator={shouldShowOfflineIndicator} > {({insets, safeAreaPaddingBottomStyle, didScreenTransitionEnd}) => (