diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a9fcf8cf9c20..d59bb9a3a981 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -792,6 +792,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/xero/advanced', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/advanced` as const, }, + POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR: { + route: 'settings/workspaces/:policyID/accounting/xero/advanced/invoice-account-selector', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/advanced/invoice-account-selector` as const, + }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 45c9906e8ca3..d9f92382bc95 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -245,6 +245,7 @@ const SCREENS = { XERO_CUSTOMER: 'Policy_Acounting_Xero_Import_Customer', XERO_TAXES: 'Policy_Accounting_Xero_Taxes', XERO_ADVANCED: 'Policy_Accounting_Xero_Advanced', + XERO_INVOICE_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Invoice_Account_Selector', }, INITIAL: 'Workspace_Initial', PROFILE: 'Workspace_Profile', diff --git a/src/components/SelectionScreen.tsx b/src/components/SelectionScreen.tsx new file mode 100644 index 000000000000..a2ab477accef --- /dev/null +++ b/src/components/SelectionScreen.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import type {PolicyAccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import type {TranslationPaths} from '@src/languages/types'; +import type {PolicyFeatureName} from '@src/types/onyx/Policy'; +import HeaderWithBackButton from './HeaderWithBackButton'; +import ScreenWrapper from './ScreenWrapper'; +import SelectionList from './SelectionList'; +import type RadioListItem from './SelectionList/RadioListItem'; +import type TableListItem from './SelectionList/TableListItem'; +import type {ListItem, SectionListDataType} from './SelectionList/types'; +import type UserListItem from './SelectionList/UserListItem'; + +type SelectorType = ListItem & { + value: string; +}; + +type SelectionScreenProps = { + /** Used to set the testID for tests */ + displayName: string; + + /** Title of the selection component */ + title: TranslationPaths; + + /** Custom content to display in the header */ + headerContent?: React.ReactNode; + + /** Sections for the section list */ + sections: Array>; + + /** Default renderer for every item in the list */ + listItem: typeof RadioListItem | typeof UserListItem | typeof TableListItem; + + /** Item `keyForList` to focus initially */ + initiallyFocusedOptionKey?: string | null | undefined; + + /** Callback to fire when a row is pressed */ + onSelectRow: (selection: SelectorType) => void; + + /** Callback to fire when back button is pressed */ + onBackButtonPress: () => void; + + /** The current policyID */ + policyID: string; + + /** Defines which types of access should be verified */ + accessVariants?: PolicyAccessVariant[]; + + /** The current feature name that the user tries to get access to */ + featureName?: PolicyFeatureName; + + /** Whether or not to block user from accessing the page */ + shouldBeBlocked?: boolean; +}; + +function SelectionScreen({ + displayName, + title, + headerContent, + sections, + listItem, + initiallyFocusedOptionKey, + onSelectRow, + onBackButtonPress, + policyID, + accessVariants, + featureName, + shouldBeBlocked, +}: SelectionScreenProps) { + const {translate} = useLocalize(); + return ( + + + + + + + ); +} + +export type {SelectorType}; + +SelectionScreen.displayName = 'SelectionScreen'; +export default SelectionScreen; diff --git a/src/languages/en.ts b/src/languages/en.ts index aadd6ac03f58..179ad777234d 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1989,7 +1989,7 @@ export default { qboInvoiceCollectionAccount: 'QuickBooks invoice collections account', accountSelectDescription: "As you've enabled sync reimbursed reports, you will need select the bank account your reimbursements are coming out of, and we'll create the payment in QuickBooks.", - invoiceAccountSelectDescription: + invoiceAccountSelectorDescription: 'If you are exporting invoices from Expensify to Quickbooks Online, this is the account the invoice will appear against once marked as paid.', }, accounts: { @@ -2033,6 +2033,7 @@ export default { reimbursedReportsDescription: 'Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the Xero account below.', xeroBillPaymentAccount: 'Xero Bill Payment Account', xeroInvoiceCollectionAccount: 'Xero Invoice Collections Account', + invoiceAccountSelectorDescription: "As you've enabled exporting invoices from Expensify to Xero, this is the account the invoice will appear against once marked as paid.", }, }, type: { diff --git a/src/languages/es.ts b/src/languages/es.ts index d4efe11a51b0..b7c32027c40a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2016,7 +2016,7 @@ export default { qboInvoiceCollectionAccount: 'Cuenta de cobro de las facturas QuickBooks', accountSelectDescription: 'Como has activado la sincronización de los informes de reembolso, tendrás que seleccionar la cuenta bancaria de la que saldrán tus reembolsos y crearemos el pago en QuickBooks.', - invoiceAccountSelectDescription: + invoiceAccountSelectorDescription: 'Si está exportando facturas de Expensify a Quickbooks Online, ésta es la cuenta en la que aparecerá la factura una vez marcada como pagada.', }, accounts: { @@ -2067,6 +2067,8 @@ export default { 'Cada vez que se pague un informe utilizando Expensify ACH, se creará el correspondiente pago de la factura en la cuenta de Xero indicadas a continuación.', xeroBillPaymentAccount: 'Cuenta de pago de las facturas de Xero', xeroInvoiceCollectionAccount: 'Cuenta de cobro de las facturas Xero', + invoiceAccountSelectorDescription: + 'Como ha activado la exportación de facturas de Expensify a Xero, esta es la cuenta en la que aparecerá la factura una vez marcada como pagada.', }, }, type: { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 77e1337d04ce..0190434f73a3 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -306,6 +306,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage').default as React.ComponentType, [SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES]: () => require('../../../../pages/workspace/accounting/xero/XeroTaxesConfigurationPage').default as React.ComponentType, [SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: () => require('../../../../pages/workspace/accounting/xero/advanced/XeroAdvancedPage').default as React.ComponentType, + [SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: () => + require('../../../../pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage').default as React.ComponentType, [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: () => require('../../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index f5a45b2ece19..7f9a366145da 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -45,6 +45,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER, SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES, SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED, + SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR, ], [SCREENS.WORKSPACE.TAXES]: [ SCREENS.WORKSPACE.TAXES_SETTINGS, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 618805f3c02a..71f1bb3e761b 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -332,6 +332,7 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER]: {path: ROUTES.POLICY_ACCOUNTING_XERO_CUSTOMER.route}, [SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TAXES.route}, [SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: {path: ROUTES.POLICY_ACCOUNTING_XERO_ADVANCED.route}, + [SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR.route}, [SCREENS.WORKSPACE.DESCRIPTION]: { path: ROUTES.WORKSPACE_PROFILE_DESCRIPTION.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index d6511937ad43..37ab9e3e4032 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -327,6 +327,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: { policyID: string; }; + [SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: { + policyID: string; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index 16e81ea747c6..b90a9cb38151 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -46,6 +46,9 @@ type AccessOrNotFoundWrapperProps = AccessOrNotFoundWrapperOnyxProps & { /** Props for customizing fallback pages */ fullPageNotFoundViewProps?: FullPageNotFoundViewProps; + + /** Whether or not to block user from accessing the page */ + shouldBeBlocked?: boolean; } & Pick; type PageNotFoundFallbackProps = Pick & {shouldShowFullScreenFallback: boolean}; @@ -68,7 +71,7 @@ function PageNotFoundFallback({policyID, shouldShowFullScreenFallback, fullPageN ); } -function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps, ...props}: AccessOrNotFoundWrapperProps) { +function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps, shouldBeBlocked, ...props}: AccessOrNotFoundWrapperProps) { const {policy, policyID, featureName, isLoadingReportData} = props; const isPolicyIDInRoute = !!policyID?.length; @@ -92,7 +95,8 @@ function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps return acc && accessFunction(policy); }, true); - const shouldShowNotFoundPage = isEmptyObject(policy) || (Object.keys(policy).length === 1 && !isEmptyObject(policy.errors)) || !policy?.id || !isPageAccessible || !isFeatureEnabled; + const shouldShowNotFoundPage = + isEmptyObject(policy) || (Object.keys(policy).length === 1 && !isEmptyObject(policy.errors)) || !policy?.id || !isPageAccessible || !isFeatureEnabled || shouldBeBlocked; if (shouldShowFullScreenLoadingIndicator) { return ; diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx index 38dc517d57de..e9cc481eb23e 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx @@ -42,7 +42,7 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyConnectionsProps) { const listHeaderComponent = useMemo( () => ( - {translate('workspace.qbo.advancedConfig.invoiceAccountSelectDescription')} + {translate('workspace.qbo.advancedConfig.invoiceAccountSelectorDescription')} ), [translate, styles.pb2, styles.ph5, styles.pb5, styles.textNormal], diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx index 31667da4ff6d..fd6bfde869f3 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx @@ -43,7 +43,7 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyConnectionsProps const listHeaderComponent = useMemo( () => ( - {translate('workspace.qbo.advancedConfig.invoiceAccountSelectDescription')} + {translate('workspace.qbo.advancedConfig.invoiceAccountSelectorDescription')} ), [translate, styles.pb2, styles.ph5, styles.pb5, styles.textNormal], diff --git a/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx b/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx index 87892ce60cef..154f5f8a97f9 100644 --- a/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx +++ b/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import ConnectionLayout from '@components/ConnectionLayout'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -6,11 +6,13 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Connections from '@libs/actions/connections'; import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; import withPolicyConnections from '@pages/workspace/withPolicyConnections'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) { const styles = useThemeStyles(); @@ -19,7 +21,14 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) { const policyID = policy?.id ?? ''; const xeroConfig = policy?.connections?.xero?.config; const {autoSync, pendingFields, sync} = xeroConfig ?? {}; - const xeroData = policy?.connections?.xero?.data; + const {bankAccounts} = policy?.connections?.xero?.data ?? {}; + const {invoiceCollectionsAccountID} = sync ?? {}; + + const selectedBankAccountName = useMemo(() => { + const selectedAccount = (bankAccounts ?? []).find((bank) => bank.id === invoiceCollectionsAccountID); + + return selectedAccount?.name ?? ''; + }, [bankAccounts, invoiceCollectionsAccountID]); return ( {}} + onPress={() => { + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR.getRoute(policyID)); + }} /> diff --git a/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx b/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx new file mode 100644 index 000000000000..460eca1a153b --- /dev/null +++ b/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx @@ -0,0 +1,77 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {SelectorType} from '@components/SelectionScreen'; +import SelectionScreen from '@components/SelectionScreen'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Connections from '@libs/actions/connections'; +import Navigation from '@libs/Navigation/Navigation'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +function XeroInvoiceAccountSelectorPage({policy}: WithPolicyConnectionsProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const policyID = policy?.id ?? ''; + const {bankAccounts} = policy?.connections?.xero?.data ?? {}; + + const {invoiceCollectionsAccountID, syncReimbursedReports} = policy?.connections?.xero?.config.sync ?? {}; + + const xeroSelectorOptions = useMemo( + () => + (bankAccounts ?? []).map(({id, name}) => ({ + value: id, + text: name, + keyForList: id, + isSelected: invoiceCollectionsAccountID === id, + })), + [invoiceCollectionsAccountID, bankAccounts], + ); + + const listHeaderComponent = useMemo( + () => ( + + {translate('workspace.xero.advancedConfig.invoiceAccountSelectorDescription')} + + ), + [translate, styles.pb2, styles.ph5, styles.pb5, styles.textNormal], + ); + + const initiallyFocusedOptionKey = useMemo(() => xeroSelectorOptions?.find((mode) => mode.isSelected)?.keyForList, [xeroSelectorOptions]); + + const updateMode = useCallback( + ({value}: SelectorType) => { + Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.SYNC, { + invoiceCollectionsAccountID: value, + }); + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_ADVANCED.getRoute(policyID)); + }, + [policyID], + ); + + return ( + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_ADVANCED.getRoute(policyID))} + title="workspace.xero.advancedConfig.xeroInvoiceCollectionAccount" + /> + ); +} + +XeroInvoiceAccountSelectorPage.displayName = 'XeroInvoiceAccountSelectorPage'; + +export default withPolicyConnections(XeroInvoiceAccountSelectorPage); diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index fed42477c635..3a322405c6e1 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -206,7 +206,7 @@ type Tenant = { }; type XeroConnectionData = { - bankAccounts: unknown[]; + bankAccounts: Account[]; countryCode: string; organisationID: string; revenueAccounts: Array<{