Skip to content

Commit

Permalink
Merge pull request #44739 from software-mansion-labs/war-in/sage-inta…
Browse files Browse the repository at this point in the history
…cct-export-config-offline-error

Sage intacct export: offline and error
  • Loading branch information
yuwenmemon authored Jul 15, 2024
2 parents e956bee + fcb95f9 commit 99bdad9
Show file tree
Hide file tree
Showing 17 changed files with 268 additions and 165 deletions.
43 changes: 43 additions & 0 deletions src/components/ErrorMessageRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {mapValues} from 'lodash';
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type {ReceiptError, ReceiptErrors} from '@src/types/onyx/Transaction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import MessagesRow from './MessagesRow';

type ErrorMessageRowProps = {
/** 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;

/** Whether we can dismiss the error message */
canDismissError?: boolean;
};

function ErrorMessageRow({errors, errorRowStyles, onClose, canDismissError = true}: ErrorMessageRowProps) {
// Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages.
const errorEntries = Object.entries(errors ?? {});
const filteredErrorEntries = errorEntries.filter((errorEntry): errorEntry is [string, string | ReceiptError] => errorEntry[1] !== null);
const errorMessages = mapValues(Object.fromEntries(filteredErrorEntries), (error) => error);
const hasErrorMessages = !isEmptyObject(errorMessages);

return hasErrorMessages ? (
<MessagesRow
messages={errorMessages}
type="error"
onClose={onClose}
containerStyles={errorRowStyles}
canDismiss={canDismissError}
/>
) : null;
}

ErrorMessageRow.displayName = 'ErrorMessageRow';

export default ErrorMessageRow;
22 changes: 7 additions & 15 deletions src/components/OfflineWithFeedback.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {mapValues} from 'lodash';
import React, {useCallback} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
Expand All @@ -10,11 +9,11 @@ import shouldRenderOffscreen from '@libs/shouldRenderOffscreen';
import type {AllStyles} from '@styles/utils/types';
import CONST from '@src/CONST';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type {ReceiptError, ReceiptErrors} from '@src/types/onyx/Transaction';
import type {ReceiptErrors} from '@src/types/onyx/Transaction';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import CustomStylesForChildrenProvider from './CustomStylesForChildrenProvider';
import MessagesRow from './MessagesRow';
import ErrorMessageRow from './ErrorMessageRow';

/**
* This component should be used when we are using the offline pattern B (offline with feedback).
Expand Down Expand Up @@ -83,12 +82,6 @@ function OfflineWithFeedback({

const hasErrors = !isEmptyObject(errors ?? {});

// Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages.
const errorEntries = Object.entries(errors ?? {});
const filteredErrorEntries = errorEntries.filter((errorEntry): errorEntry is [string, string | ReceiptError] => errorEntry[1] !== null);
const errorMessages = mapValues(Object.fromEntries(filteredErrorEntries), (error) => error);

const hasErrorMessages = !isEmptyObject(errorMessages);
const isOfflinePendingAction = !!isOffline && !!pendingAction;
const isUpdateOrDeleteError = hasErrors && (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
const isAddError = hasErrors && pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;
Expand Down Expand Up @@ -139,13 +132,12 @@ function OfflineWithFeedback({
<CustomStylesForChildrenProvider style={needsStrikeThrough ? [styles.offlineFeedback.deleted, styles.userSelectNone] : null}>{children}</CustomStylesForChildrenProvider>
</View>
)}
{shouldShowErrorMessages && hasErrorMessages && (
<MessagesRow
messages={errorMessages}
type="error"
{shouldShowErrorMessages && (
<ErrorMessageRow
errors={errors}
errorRowStyles={errorRowStyles}
onClose={onClose}
containerStyles={errorRowStyles}
canDismiss={canDismissError}
canDismissError={canDismissError}
/>
)}
</View>
Expand Down
3 changes: 2 additions & 1 deletion src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function BaseSelectionList<TItem extends ListItem>(
showConfirmButton = false,
shouldPreventDefaultFocusOnSelectRow = false,
containerStyle,
sectionListStyle,
disableKeyboardShortcuts = false,
children,
shouldStopPropagation = false,
Expand Down Expand Up @@ -729,7 +730,7 @@ function BaseSelectionList<TItem extends ListItem>(
viewabilityConfig={{viewAreaCoveragePercentThreshold: 95}}
testID="selection-list"
onLayout={onSectionListLayout}
style={(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0}
style={[(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0, sectionListStyle]}
ListHeaderComponent={listHeaderContent}
ListFooterComponent={listFooterContent ?? ShowMoreButtonInstance}
ListEmptyComponent={listEmptyContent}
Expand Down
3 changes: 3 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ type BaseSelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {
/** Styles to apply to SelectionList container */
containerStyle?: StyleProp<ViewStyle>;

/** Styles to apply to SectionList component */
sectionListStyle?: StyleProp<ViewStyle>;

/** Whether focus event should be delayed */
shouldDelayFocus?: boolean;

Expand Down
13 changes: 9 additions & 4 deletions src/components/SelectionScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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 ErrorMessageRow from './ErrorMessageRow';
import HeaderWithBackButton from './HeaderWithBackButton';
import OfflineWithFeedback from './OfflineWithFeedback';
import ScreenWrapper from './ScreenWrapper';
Expand Down Expand Up @@ -128,9 +129,6 @@ function SelectionScreen({
{headerContent}
<OfflineWithFeedback
pendingAction={pendingAction}
errors={errors}
errorRowStyles={[errorRowStyles]}
onClose={onClose}
style={[styles.flex1]}
contentContainerStyle={[styles.flex1]}
>
Expand All @@ -143,7 +141,14 @@ function SelectionScreen({
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
listEmptyContent={listEmptyContent}
listFooterContent={listFooterContent}
/>
sectionListStyle={[styles.flexGrow0]}
>
<ErrorMessageRow
errors={errors}
errorRowStyles={errorRowStyles}
onClose={onClose}
/>
</SelectionList>
</OfflineWithFeedback>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
10 changes: 5 additions & 5 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,14 +530,14 @@ function clearNetSuiteErrorField(policyID: string, fieldName: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {netsuite: {options: {config: {errorFields: {[fieldName]: null}}}}}});
}

function clearNetSuiteAutoSyncErrorField(policyID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {netsuite: {config: {errorFields: {autoSync: null}}}}});
}

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

function clearNetSuiteAutoSyncErrorField(policyID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {netsuite: {config: {errorFields: {autoSync: null}}}}});
}

function setWorkspaceReimbursement(policyID: string, reimbursementChoice: ValueOf<typeof CONST.POLICY.REIMBURSEMENT_CHOICES>, reimburserEmail: string) {
const policy = getPolicy(policyID);

Expand Down Expand Up @@ -3127,6 +3127,7 @@ export {
generateCustomUnitID,
clearQBOErrorField,
clearXeroErrorField,
clearSageIntacctErrorField,
clearNetSuiteErrorField,
clearNetSuiteAutoSyncErrorField,
clearWorkspaceReimbursementErrors,
Expand All @@ -3144,7 +3145,6 @@ export {
openPolicyExpensifyCardsPage,
requestExpensifyCardLimitIncrease,
getAdminPoliciesConnectedToSageIntacct,
clearSageIntacctErrorField,
};

export type {NewCustomUnit};
43 changes: 19 additions & 24 deletions src/libs/actions/connections/SageIntacct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
Connections,
SageIntacctConnectionsConfig,
SageIntacctDimension,
SageIntacctExportConfig,
SageIntacctMappingName,
SageIntacctMappingType,
SageIntacctMappingValue,
Expand Down Expand Up @@ -316,7 +317,7 @@ function removeSageIntacctUserDimensions(policyID: string, dimensionName: string
);
}

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 @@ -327,12 +328,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 @@ -349,14 +350,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 @@ -373,14 +371,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 @@ -472,7 +467,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 @@ -118,7 +118,7 @@ function SageIntacctAdvancedPage({policy}: WithPolicyProps) {
switchAccessibilityLabel={section.label}
isActive={section.isActive}
onToggle={section.onToggle}
wrapperStyle={[styles.ph5, styles.pv5]}
wrapperStyle={[styles.ph5, styles.pb3]}
pendingAction={section.pendingAction}
errors={section.error}
onCloseError={section.onCloseError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function SageIntacctPaymentAccountPage({policy}: WithPolicyConnectionsProps) {
connectionName={CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}
pendingAction={config?.pendingFields?.reimbursementAccountID}
errors={ErrorUtils.getLatestErrorField(config ?? {}, CONST.SAGE_INTACCT_CONFIG.REIMBURSEMENT_ACCOUNT_ID)}
errorRowStyles={[styles.ph5, styles.mv2]}
errorRowStyles={[styles.ph5, styles.pv3]}
onClose={() => Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.REIMBURSEMENT_ACCOUNT_ID)}
/>
);
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.pv3]}
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.pv3]}
onClose={() => Policy.clearSageIntacctErrorField(policyID, settingName)}
/>
);
}
Expand Down
Loading

0 comments on commit 99bdad9

Please sign in to comment.