Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sage intacct export: offline and error #44739

Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7a0ba43
update SelectionScreen and add Offline with feedback to components
war-in Jul 2, 2024
9088361
add offline with feedback to remaining pages
war-in Jul 2, 2024
91c62d9
show errors in correct places
war-in Jul 3, 2024
95da618
style selection screen correctly
war-in Jul 4, 2024
c420893
move error and pending fields up in onyx data
war-in Jul 4, 2024
42f683c
handle offline state
war-in Jul 4, 2024
d2c8472
Merge branch 'refs/heads/main' into war-in/sage-intacct-export-config…
war-in Jul 4, 2024
36ba4c9
Merge branch 'refs/heads/war-in/sage-intacct-export-config' into war-…
war-in Jul 4, 2024
80cc058
track errors in (non)reimbursable in main page
war-in Jul 4, 2024
968970a
Merge branch 'refs/heads/war-in/sage-intacct-export-config' into war-…
war-in Jul 4, 2024
c836521
address comments
war-in Jul 4, 2024
59c0fa8
use already defined type
war-in Jul 4, 2024
76b2328
Merge branch 'refs/heads/war-in/sage-intacct-export-config' into war-…
war-in Jul 5, 2024
32d879f
Merge branch 'refs/heads/war-in/sage-intacct-export-config' into war-…
war-in Jul 8, 2024
847706e
Merge branch 'refs/heads/war-in/sage-intacct-export-config' into war-…
war-in Jul 9, 2024
a6d6588
include safe area padding bottom on errors
war-in Jul 9, 2024
452d279
polish endpoints usages
war-in Jul 9, 2024
759c350
Merge branch 'refs/heads/war-in/sage-intacct-export-config' into war-…
war-in Jul 9, 2024
df588a0
Merge branch 'refs/heads/main' into war-in/sage-intacct-export-config…
war-in Jul 9, 2024
e39b199
Merge branch 'refs/heads/main' into war-in/sage-intacct-export-config…
war-in Jul 10, 2024
97f476a
fix Policy.ts after merge
war-in Jul 10, 2024
43fc0f8
make error message sticky
war-in Jul 10, 2024
b70782a
fix margin in non-reimbursable config
war-in Jul 10, 2024
5f9dc94
fix prettier
war-in Jul 10, 2024
0bd28cc
dry the code
war-in Jul 11, 2024
d465864
Merge branch 'refs/heads/war-in/demonstrate-sticky-footer' into war-i…
war-in Jul 11, 2024
161f0ec
unify spacing
war-in Jul 11, 2024
51fe9f1
Merge branch 'refs/heads/main' into war-in/sage-intacct-export-config…
war-in Jul 11, 2024
4285951
fix selection list padding bottom in (non)reimbursable pages
war-in Jul 11, 2024
fcb95f9
Merge branch 'refs/heads/main' into war-in/sage-intacct-export-config…
war-in Jul 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions src/components/SelectionScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import {isEmpty} from 'lodash';
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as PolicyUtils from '@libs/PolicyUtils';
import type {AccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {TranslationPaths} from '@src/languages/types';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type {ConnectionName, PolicyFeatureName} from '@src/types/onyx/Policy';
import type {ReceiptErrors} from '@src/types/onyx/Transaction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import HeaderWithBackButton from './HeaderWithBackButton';
import OfflineWithFeedback from './OfflineWithFeedback';
import ScreenWrapper from './ScreenWrapper';
import SelectionList from './SelectionList';
import type RadioListItem from './SelectionList/RadioListItem';
Expand Down Expand Up @@ -63,6 +69,18 @@ type SelectionScreenProps = {

/** Name of the current connection */
connectionName: ConnectionName;

/** The type of action that's pending */
pendingAction?: OnyxCommon.PendingAction | null;

/** The errors to display */
errors?: OnyxCommon.Errors | ReceiptErrors | null;

/** Additional style object for the error row */
errorRowStyles?: StyleProp<ViewStyle>;

/** A function to run when the X button next to the error is clicked */
onClose?: () => void;
};

function SelectionScreen({
Expand All @@ -81,8 +99,13 @@ function SelectionScreen({
featureName,
shouldBeBlocked,
connectionName,
pendingAction,
errors,
errorRowStyles,
onClose,
}: SelectionScreenProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();

const policy = PolicyUtils.getPolicy(policyID);
const isConnectionEmpty = isEmpty(policy?.connections?.[connectionName]);
Expand All @@ -95,24 +118,33 @@ function SelectionScreen({
shouldBeBlocked={isConnectionEmpty || shouldBeBlocked}
>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
includeSafeAreaPaddingBottom={!!errors && !isEmptyObject(errors)}
testID={displayName}
>
<HeaderWithBackButton
title={translate(title)}
onBackButtonPress={onBackButtonPress}
/>
<SelectionList
onSelectRow={onSelectRow}
headerContent={headerContent}
sections={sections}
ListItem={listItem}
showScrollIndicator
shouldShowTooltips={false}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
listEmptyContent={listEmptyContent}
listFooterContent={listFooterContent}
/>
{headerContent}
<OfflineWithFeedback
pendingAction={pendingAction}
errors={errors}
errorRowStyles={[errorRowStyles]}
onClose={onClose}
style={[styles.flex1]}
contentContainerStyle={[styles.flex1]}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-07-10 at 09 53 20 Screenshot 2024-07-10 at 09 53 07

This will add the error at the bottom of the screen. cc @Expensify/design for reviewing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, should this be closer to the list options above? How does this error get triggered in the first place?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, the error will show when user selected a card but the api was failing.

Screen.Recording.2024-07-10.at.14.08.58.mov

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I think we need to clean up the spacing here. For example, I would expect there to be at least 12px of space below this error message and above the row below it:
CleanShot 2024-07-10 at 09 13 16@2x

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @war-in

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does seem much better with the error closer to the list.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's the drawback. We will have some duplicated code (we can of course extract it to separate component)

Yeah I think it would be great to further explore how to dry this up, from design perspective I agree it looks better

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@war-in Can you fix this spacing in other places as well? (ie: Advance page).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mountiny Do you think we need separate issue for adjusting error handling in other integrations? I can take care of it once this PR is merged

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mountiny Kind bump :)

<SelectionList
onSelectRow={onSelectRow}
sections={sections}
ListItem={listItem}
showScrollIndicator
shouldShowTooltips={false}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
listEmptyContent={listEmptyContent}
listFooterContent={listFooterContent}
/>
</OfflineWithFeedback>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
);
Expand Down
5 changes: 5 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@ function clearXeroErrorField(policyID: string, fieldName: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {xero: {config: {errorFields: {[fieldName]: null}}}}});
}

function clearSageIntacctErrorField(policyID: string, fieldName: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {intacct: {config: {errorFields: {[fieldName]: null}}}}});
}

function clearNetSuiteErrorField(policyID: string, fieldName: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {netsuite: {options: {config: {errorFields: {[fieldName]: null}}}}}});
}
Expand Down Expand Up @@ -3123,6 +3127,7 @@ export {
generateCustomUnitID,
clearQBOErrorField,
clearXeroErrorField,
clearSageIntacctErrorField,
clearNetSuiteErrorField,
clearNetSuiteAutoSyncErrorField,
clearWorkspaceReimbursementErrors,
Expand Down
44 changes: 19 additions & 25 deletions src/libs/actions/connections/SageIntacct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {WRITE_COMMANDS} from '@libs/API/types';
import * as ErrorUtils from '@libs/ErrorUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Connections} from '@src/types/onyx/Policy';
import type {SageIntacctExportConfig} from '@src/types/onyx/Policy';

type SageIntacctCredentials = {companyID: string; userID: string; password: string};

Expand All @@ -21,7 +21,7 @@ function connectToSageIntacct(policyID: string, credentials: SageIntacctCredenti
API.write(WRITE_COMMANDS.CONNECT_POLICY_TO_SAGE_INTACCT, parameters, {});
}

function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Connections['intacct']['config']['export'], settingValue: string | null) {
function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof SageIntacctExportConfig, settingValue: string | null) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
Expand All @@ -32,12 +32,12 @@ function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Con
config: {
export: {
[settingName]: settingValue,
pendingFields: {
[settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errorFields: {
[settingName]: null,
},
},
pendingFields: {
[settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errorFields: {
[settingName]: null,
},
},
},
Expand All @@ -54,14 +54,11 @@ function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Con
connections: {
intacct: {
config: {
export: {
[settingName]: settingValue,
pendingFields: {
[settingName]: null,
},
errorFields: {
[settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
},
pendingFields: {
[settingName]: null,
},
errorFields: {
[settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
},
},
},
Expand All @@ -78,14 +75,11 @@ function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Con
connections: {
intacct: {
config: {
export: {
[settingName]: settingValue,
pendingFields: {
[settingName]: null,
},
errorFields: {
[settingName]: null,
},
pendingFields: {
[settingName]: null,
},
errorFields: {
[settingName]: null,
},
},
},
Expand Down Expand Up @@ -177,7 +171,7 @@ function updateSageIntacctNonreimbursableExpensesExportVendor(policyID: string,
API.write(WRITE_COMMANDS.UPDATE_SAGE_INTACCT_NON_REIMBURSABLE_EXPENSES_EXPORT_VENDOR, parameters, {optimisticData, failureData, successData});
}

function updateSageIntacctDefaultVendor(policyID: string, settingName: keyof Connections['intacct']['config']['export'], vendor: string) {
function updateSageIntacctDefaultVendor(policyID: string, settingName: keyof SageIntacctExportConfig, vendor: string) {
if (settingName === CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE_VENDOR) {
updateSageIntacctReimbursableExpensesReportExportDefaultVendor(policyID, vendor);
} else if (settingName === CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import type {SelectorType} from '@components/SelectionScreen';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@navigation/Navigation';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import {updateSageIntacctExportDate} from '@userActions/connections/SageIntacct';
import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

Expand All @@ -23,6 +25,7 @@ function SageIntacctDatePage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const policyID = policy?.id ?? '-1';
const styles = useThemeStyles();
const {config} = policy?.connections?.intacct ?? {};
const {export: exportConfig} = policy?.connections?.intacct?.config ?? {};
const data: MenuListItem[] = Object.values(CONST.SAGE_INTACCT_EXPORT_DATE).map((dateType) => ({
value: dateType,
Expand Down Expand Up @@ -65,6 +68,10 @@ function SageIntacctDatePage({policy}: WithPolicyProps) {
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT.getRoute(policyID))}
connectionName={CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}
pendingAction={config?.pendingFields?.exportDate}
errors={ErrorUtils.getLatestErrorField(config ?? {}, CONST.SAGE_INTACCT_CONFIG.EXPORT_DATE)}
errorRowStyles={[styles.ph5, styles.mv2]}
onClose={() => Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.EXPORT_DATE)}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import SelectionScreen from '@components/SelectionScreen';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getSageIntacctNonReimbursableActiveDefaultVendor, getSageIntacctVendors} from '@libs/PolicyUtils';
import type {SettingsNavigatorParamList} from '@navigation/types';
import variables from '@styles/variables';
import {updateSageIntacctDefaultVendor} from '@userActions/connections/SageIntacct';
import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -29,20 +31,22 @@ function SageIntacctDefaultVendorPage({route}: SageIntacctDefaultVendorPageProps

const policyID = route.params.policyID ?? '-1';
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const {config} = policy?.connections?.intacct ?? {};
const {export: exportConfig} = policy?.connections?.intacct?.config ?? {};

const isReimbursable = route.params.reimbursable === CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE;

let defaultVendor;
let settingName: keyof Connections['intacct']['config']['export'];
if (!isReimbursable) {
const {nonReimbursable} = policy?.connections?.intacct?.config.export ?? {};
const {nonReimbursable} = exportConfig ?? {};
defaultVendor = getSageIntacctNonReimbursableActiveDefaultVendor(policy);
settingName =
nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.CREDIT_CARD_CHARGE
? CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR
: CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_VENDOR;
} else {
const {reimbursableExpenseReportDefaultVendor} = policy?.connections?.intacct?.config.export ?? {};
const {reimbursableExpenseReportDefaultVendor} = exportConfig ?? {};
defaultVendor = reimbursableExpenseReportDefaultVendor;
settingName = CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE_VENDOR;
}
Expand Down Expand Up @@ -103,6 +107,10 @@ function SageIntacctDefaultVendorPage({route}: SageIntacctDefaultVendorPageProps
listEmptyContent={listEmptyContent}
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
connectionName={CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}
pendingAction={config?.pendingFields?.[settingName]}
errors={ErrorUtils.getLatestErrorField(config, settingName)}
errorRowStyles={[styles.ph5, styles.mv2]}
onClose={() => Policy.clearSageIntacctErrorField(policyID, settingName)}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function SageIntacctExportPage({policy}: WithPolicyProps) {
const styles = useThemeStyles();
const policyID = policy?.id ?? '-1';

const {config} = policy?.connections?.intacct ?? {};
const {export: exportConfig, credentials} = policy?.connections?.intacct?.config ?? {};

const sections = useMemo(
Expand All @@ -23,36 +24,46 @@ function SageIntacctExportPage({policy}: WithPolicyProps) {
description: translate('workspace.sageIntacct.preferredExporter'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREFERRED_EXPORTER.getRoute(policyID)),
title: exportConfig?.exporter ?? translate('workspace.sageIntacct.notConfigured'),
hasError: !!exportConfig?.errorFields?.exporter,
pendingAction: exportConfig?.pendingFields?.exporter,
hasError: !!config?.errorFields?.exporter,
pendingAction: config?.pendingFields?.exporter,
},
{
description: translate('workspace.sageIntacct.exportDate.label'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT_DATE.getRoute(policyID)),
title: exportConfig?.exportDate ? translate(`workspace.sageIntacct.exportDate.values.${exportConfig.exportDate}.label`) : translate(`workspace.sageIntacct.notConfigured`),
hasError: !!exportConfig?.errorFields?.exportDate,
pendingAction: exportConfig?.pendingFields?.exportDate,
hasError: !!config?.errorFields?.exportDate,
pendingAction: config?.pendingFields?.exportDate,
},
{
description: translate('workspace.sageIntacct.reimbursableExpenses.label'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_REIMBURSABLE_EXPENSES.getRoute(policyID)),
title: exportConfig?.reimbursable
? translate(`workspace.sageIntacct.reimbursableExpenses.values.${exportConfig.reimbursable}`)
: translate('workspace.sageIntacct.notConfigured'),
hasError: !!exportConfig?.errorFields?.reimbursable,
pendingAction: exportConfig?.pendingFields?.reimbursable,
hasError: !!config?.errorFields?.reimbursable || !!config?.errorFields?.reimbursableExpenseReportDefaultVendor,
pendingAction: config?.pendingFields?.reimbursable ?? config?.pendingFields?.reimbursableExpenseReportDefaultVendor,
},
{
description: translate('workspace.sageIntacct.nonReimbursableExpenses.label'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_NON_REIMBURSABLE_EXPENSES.getRoute(policyID)),
title: exportConfig?.nonReimbursable
? translate(`workspace.sageIntacct.nonReimbursableExpenses.values.${exportConfig.nonReimbursable}`)
: translate('workspace.sageIntacct.notConfigured'),
hasError: !!exportConfig?.errorFields?.nonReimbursable,
pendingAction: exportConfig?.pendingFields?.nonReimbursable,
hasError:
!!config?.errorFields?.nonReimbursable ??
!!config?.errorFields?.nonReimbursableAccount ??
config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.VENDOR_BILL
? !!config?.errorFields?.nonReimbursableVendor
: !!config?.errorFields?.nonReimbursableCreditCardChargeDefaultVendor,
pendingAction:
config?.pendingFields?.nonReimbursable ??
config?.errorFields?.nonReimbursableAccount ??
config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.VENDOR_BILL
? config?.pendingFields?.nonReimbursableVendor
: config?.pendingFields?.nonReimbursableCreditCardChargeDefaultVendor,
},
],
[exportConfig, policyID, translate],
[config, exportConfig, policyID, translate],
);

return (
Expand Down
Loading
Loading