Skip to content

Commit

Permalink
Merge pull request #41511 from hungvu193/feat/invoice-collection-acou…
Browse files Browse the repository at this point in the history
…nt-selector-page

Feat: Invoice collection account selector page
  • Loading branch information
lakchote authored May 6, 2024
2 parents 942cad9 + fa7ba27 commit 92696df
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 13 deletions.
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
104 changes: 104 additions & 0 deletions src/components/SelectionScreen.tsx
Original file line number Diff line number Diff line change
@@ -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<SectionListDataType<SelectorType>>;

/** 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 (
<AccessOrNotFoundWrapper
policyID={policyID}
accessVariants={accessVariants}
featureName={featureName}
shouldBeBlocked={shouldBeBlocked}
>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={displayName}
>
<HeaderWithBackButton
title={translate(title)}
onBackButtonPress={onBackButtonPress}
/>
<SelectionList
onSelectRow={onSelectRow}
headerContent={headerContent}
sections={sections}
ListItem={listItem}
showScrollIndicator
shouldShowTooltips={false}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
/>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
);
}

export type {SelectorType};

SelectionScreen.displayName = 'SelectionScreen';
export default SelectionScreen;
3 changes: 2 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand Down
4 changes: 3 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER]: () => 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
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,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ const config: LinkingOptions<RootStackParamList>['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,
},
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
8 changes: 6 additions & 2 deletions src/pages/workspace/AccessOrNotFoundWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<FullPageNotFoundViewProps, 'subtitleKey' | 'onLinkPress'>;

type PageNotFoundFallbackProps = Pick<AccessOrNotFoundWrapperProps, 'policyID' | 'fullPageNotFoundViewProps'> & {shouldShowFullScreenFallback: boolean};
Expand All @@ -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;
Expand All @@ -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 <FullscreenLoadingIndicator />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyConnectionsProps) {
const listHeaderComponent = useMemo(
() => (
<View style={[styles.pb2, styles.ph5]}>
<Text style={[styles.pb5, styles.textNormal]}>{translate('workspace.qbo.advancedConfig.invoiceAccountSelectDescription')}</Text>
<Text style={[styles.pb5, styles.textNormal]}>{translate('workspace.qbo.advancedConfig.invoiceAccountSelectorDescription')}</Text>
</View>
),
[translate, styles.pb2, styles.ph5, styles.pb5, styles.textNormal],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyConnectionsProps
const listHeaderComponent = useMemo(
() => (
<View style={[styles.pb2, styles.ph5]}>
<Text style={[styles.pb5, styles.textNormal]}>{translate('workspace.qbo.advancedConfig.invoiceAccountSelectDescription')}</Text>
<Text style={[styles.pb5, styles.textNormal]}>{translate('workspace.qbo.advancedConfig.invoiceAccountSelectorDescription')}</Text>
</View>
),
[translate, styles.pb2, styles.ph5, styles.pb5, styles.textNormal],
Expand Down
23 changes: 17 additions & 6 deletions src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React from 'react';
import React, {useMemo} from 'react';
import ConnectionLayout from '@components/ConnectionLayout';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
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();
Expand All @@ -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 (
<ConnectionLayout
Expand Down Expand Up @@ -49,7 +58,7 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) {
<OfflineWithFeedback pendingAction={pendingFields?.export}>
<MenuItemWithTopDescription
shouldShowRightIcon
title={xeroConfig?.export.billStatus.purchase}
title={xeroConfig?.export?.billStatus?.purchase}
description={translate('workspace.xero.advancedConfig.purchaseBillStatusTitle')}
key={translate('workspace.xero.advancedConfig.purchaseBillStatusTitle')}
wrapperStyle={[styles.sectionMenuItemTopDescription]}
Expand Down Expand Up @@ -77,7 +86,7 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) {
<OfflineWithFeedback pendingAction={pendingFields?.sync}>
<MenuItemWithTopDescription
shouldShowRightIcon
title={String(xeroData?.bankAccounts)}
title={String(bankAccounts)}
description={translate('workspace.xero.advancedConfig.xeroBillPaymentAccount')}
key={translate('workspace.xero.advancedConfig.xeroBillPaymentAccount')}
wrapperStyle={[styles.sectionMenuItemTopDescription]}
Expand All @@ -87,11 +96,13 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) {
<OfflineWithFeedback pendingAction={pendingFields?.sync}>
<MenuItemWithTopDescription
shouldShowRightIcon
title={String(xeroData?.bankAccounts)}
title={String(selectedBankAccountName)}
description={translate('workspace.xero.advancedConfig.xeroInvoiceCollectionAccount')}
key={translate('workspace.xero.advancedConfig.xeroInvoiceCollectionAccount')}
wrapperStyle={[styles.sectionMenuItemTopDescription]}
onPress={() => {}}
onPress={() => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR.getRoute(policyID));
}}
/>
</OfflineWithFeedback>
</>
Expand Down
Loading

0 comments on commit 92696df

Please sign in to comment.