Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3623,6 +3623,15 @@ const ROUTES = {
return `workspaces/${policyID}/accounting/netsuite/import/custom-list/new/${subPage}${action ? `/${action}` : ''}` as const;
},
},
POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR: {
route: 'workspaces/:policyID/accounting/netsuite/import/custom-list/list-selector',
getRoute: (policyID: string | undefined) => {
if (!policyID) {
Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR route');
}
return `workspaces/${policyID}/accounting/netsuite/import/custom-list/list-selector` as const;
},
},
POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD: {
route: 'workspaces/:policyID/accounting/netsuite/import/custom-segment/new/:subPage?/:action?',
getRoute: (policyID: string | undefined, subPage?: string, action?: 'edit') => {
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ const SCREENS = {
NETSUITE_IMPORT_CUSTOM_FIELD_VIEW: 'Policy_Accounting_NetSuite_Import_Custom_Field_View',
NETSUITE_IMPORT_CUSTOM_FIELD_EDIT: 'Policy_Accounting_NetSuite_Import_Custom_Field_Edit',
NETSUITE_IMPORT_CUSTOM_LIST_ADD: 'Policy_Accounting_NetSuite_Import_Custom_List_Add',
NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR: 'Policy_Accounting_NetSuite_Import_Custom_List_Selector',
NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD: 'Policy_Accounting_NetSuite_Import_Custom_Segment_Add',
NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS: 'Policy_Accounting_NetSuite_Import_CustomersOrProjects',
NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT: 'Policy_Accounting_NetSuite_Import_CustomersOrProjects_Select',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_ADD]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomListPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListSelectorPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomSegmentPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS]: () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const WORKSPACE_TO_RHP: Partial<Record<keyof WorkspaceSplitNavigatorParamList, s
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_VIEW,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_EDIT,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_ADD,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT,
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 @@ -719,6 +719,7 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_VIEW]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_EDIT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_EDIT.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_ADD]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_LIST_ADD.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT.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 @@ -959,6 +959,9 @@ type SettingsNavigatorParamList = {
subPage?: string;
action?: 'edit';
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR]: {
policyID: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD]: {
policyID: string;
subPage?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,65 +1,38 @@
import React, {useState} from 'react';
import React from 'react';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
import type {CustomListSelectorType} from '@pages/workspace/accounting/netsuite/types';
import CONST from '@src/CONST';
import type {Policy} from '@src/types/onyx';
import NetSuiteCustomListSelectorModal from './NetSuiteCustomListSelectorModal';
import ROUTES from '@src/ROUTES';

type NetSuiteCustomListPickerProps = {
/** Current value of the selected item */
value?: string;

/** Current connected policy */
policy?: Policy;

/** Callback when the list item is selected */
onInputChange?: (value: string, key?: string) => void;

/** Id of the internalID input to be updated on input change */
internalIDInputID?: string;
/** Policy ID from the parent route's URL params (preferred over policy?.id because it is set before the Onyx policy record hydrates) */
policyID?: string;

/** Form Error description */
errorText?: string;
};

function NetSuiteCustomListPicker({value, policy, internalIDInputID, errorText, onInputChange = () => {}}: NetSuiteCustomListPickerProps) {
function NetSuiteCustomListPicker({value, policyID, errorText}: NetSuiteCustomListPickerProps) {
const {translate} = useLocalize();
const [isPickerVisible, setIsPickerVisible] = useState(false);

const hidePickerModal = () => {
setIsPickerVisible(false);
};

const updateInput = (item: CustomListSelectorType) => {
onInputChange?.(item.value);
if (internalIDInputID) {
onInputChange(item.id, internalIDInputID);
}
hidePickerModal();
};

return (
<>
<MenuItemWithTopDescription
shouldShowRightIcon
title={value}
description={translate('workspace.netsuite.import.importCustomFields.customLists.fields.listName')}
onPress={() => setIsPickerVisible(true)}
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={errorText}
/>
<NetSuiteCustomListSelectorModal
isVisible={isPickerVisible}
currentCustomListValue={value ?? ''}
onCustomListSelected={updateInput}
onClose={hidePickerModal}
label={translate('workspace.netsuite.import.importCustomFields.customLists.fields.listName')}
policy={policy}
onBackdropPress={Navigation.dismissModal}
/>
</>
<MenuItemWithTopDescription
shouldShowRightIcon
title={value}
description={translate('workspace.netsuite.import.importCustomFields.customLists.fields.listName')}
onPress={() => {
if (!policyID) {
return;
}
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR.getRoute(policyID));
}}
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={errorText}
/>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,36 @@
import {Str} from 'expensify-common';
import React, {useMemo} from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
import {setDraftValues} from '@libs/actions/FormActions';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {CustomListSelectorType} from '@pages/workspace/accounting/netsuite/types';
import CONST from '@src/CONST';
import type {Policy} from '@src/types/onyx';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm';

type NetSuiteCustomListSelectorModalProps = {
/** Whether the modal is visible */
isVisible: boolean;
type NetSuiteCustomListSelectorPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR>;

/** Function to call when the user closes the business type selector modal */
onClose: () => void;

/** Label to display on field */
label: string;

/** Custom List value selected */
currentCustomListValue: string;

policy?: Policy;

/** Function to call when the user selects a custom list */
onCustomListSelected: (value: CustomListSelectorType) => void;

/** Function to call when the user presses on the modal backdrop */
onBackdropPress?: () => void;
};

function NetSuiteCustomListSelectorModal({isVisible, currentCustomListValue, onCustomListSelected, onClose, label, policy, onBackdropPress}: NetSuiteCustomListSelectorModalProps) {
function NetSuiteCustomListSelectorPage({
route: {
params: {policyID},
},
}: NetSuiteCustomListSelectorPageProps) {
const {translate} = useLocalize();
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
const policy = usePolicy(policyID);
const [formDraft] = useOnyx(ONYXKEYS.FORMS.NETSUITE_CUSTOM_LIST_ADD_FORM_DRAFT);
const currentCustomListValue = formDraft?.[INPUT_IDS.LIST_NAME] ?? '';

const rawCustomLists = policy?.connections?.netsuite?.options?.data?.customLists;

Expand Down Expand Up @@ -69,38 +64,47 @@ function NetSuiteCustomListSelectorModal({isVisible, currentCustomListValue, onC
[searchValue, showTextInput, translate, setSearchValue, debouncedSearchValue, options.length],
);

const label = translate('workspace.netsuite.import.importCustomFields.customLists.fields.listName');

const onSelectRow = (item: CustomListSelectorType) => {
setDraftValues(ONYXKEYS.FORMS.NETSUITE_CUSTOM_LIST_ADD_FORM, {
[INPUT_IDS.LIST_NAME]: item.value,
[INPUT_IDS.INTERNAL_ID]: item.id,
});
Navigation.goBack();
};

return (
<Modal
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
isVisible={isVisible}
onClose={onClose}
onModalHide={onClose}
onBackdropPress={onBackdropPress}
enableEdgeToEdgeBottomSafeAreaPadding
<AccessOrNotFoundWrapper
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.CONTROL]}
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
>
<ScreenWrapper
includePaddingTop={false}
enableEdgeToEdgeBottomSafeAreaPadding
testID="NetSuiteCustomListSelectorModal"
testID="NetSuiteCustomListSelectorPage"
>
<HeaderWithBackButton
title={label}
shouldShowBackButton
onBackButtonPress={onClose}
onBackButtonPress={() => Navigation.goBack()}
/>
<SelectionList
data={options}
textInputOptions={textInputOptions}
onSelectRow={onCustomListSelected}
onSelectRow={onSelectRow}
ListItem={SingleSelectListItem}
initiallyFocusedItemKey={currentCustomListValue}
shouldSingleExecuteRowSelect
shouldStopPropagation
addBottomSafeAreaPadding
/>
</ScreenWrapper>
</Modal>
</AccessOrNotFoundWrapper>
);
}

export default NetSuiteCustomListSelectorModal;
NetSuiteCustomListSelectorPage.displayName = 'NetSuiteCustomListSelectorPage';

export default NetSuiteCustomListSelectorPage;
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ function NetSuiteImportAddCustomListContent({policy, draftValues, policyIDParam}
onNext={handleNextScreen}
onMove={moveTo}
policy={policy}
policyIDParam={policyIDParam}
importCustomField={CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_LISTS}
netSuiteCustomFieldFormValues={values}
customLists={customLists}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ function NetSuiteImportAddCustomSegmentContent({policy, policyIDParam, draftValu
onNext={handleNextScreen}
onMove={moveTo}
policy={policy}
policyIDParam={policyIDParam}
importCustomField={CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_SEGMENTS}
customSegmentType={customSegmentType}
setCustomSegmentType={setCustomSegmentType}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm';

const STEP_FIELDS = [INPUT_IDS.LIST_NAME, INPUT_IDS.INTERNAL_ID];

function ChooseCustomListStep({policy, onNext, isEditing, netSuiteCustomFieldFormValues}: CustomFieldSubPageWithPolicy) {
function ChooseCustomListStep({policyIDParam, onNext, isEditing, netSuiteCustomFieldFormValues}: CustomFieldSubPageWithPolicy) {
const styles = useThemeStyles();
const {translate} = useLocalize();

Expand Down Expand Up @@ -49,8 +49,7 @@ function ChooseCustomListStep({policy, onNext, isEditing, netSuiteCustomFieldFor
<InputWrapper
InputComponent={NetSuiteCustomListPicker}
inputID={INPUT_IDS.LIST_NAME}
policy={policy}
internalIDInputID={INPUT_IDS.INTERNAL_ID}
policyID={policyIDParam}
defaultValue={netSuiteCustomFieldFormValues[INPUT_IDS.LIST_NAME]}
/>
</FormProvider>
Expand Down
3 changes: 3 additions & 0 deletions src/pages/workspace/accounting/netsuite/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ type CustomFieldSubPageWithPolicy = SubPageProps & {
/** Current policy in the form steps */
policy: Policy | undefined;

/** Policy ID from the parent route's URL params (set before the policy Onyx record finishes hydrating) */
policyIDParam: string | undefined;

/** Whether the page is a custom segment or custom list */
importCustomField: ValueOf<typeof CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS>;

Expand Down
42 changes: 42 additions & 0 deletions tests/ui/NetSuiteCustomListPickerTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {render} from '@testing-library/react-native';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import Navigation from '@libs/Navigation/Navigation';
import NetSuiteCustomListPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker';
import ROUTES from '@src/ROUTES';

jest.mock('@components/MenuItemWithTopDescription', () => jest.fn(() => null));
jest.mock('@hooks/useLocalize', () =>
jest.fn(() => ({
translate: (key: string) => key,
})),
);
jest.mock('@libs/Navigation/Navigation', () => ({
navigate: jest.fn(),
}));

describe('NetSuiteCustomListPicker', () => {
const mockedMenuItem = jest.mocked(MenuItemWithTopDescription);
const mockedNavigate = jest.mocked(Navigation.navigate);

beforeEach(() => {
mockedMenuItem.mockClear();
mockedNavigate.mockClear();
});

it('navigates to the selector route using the route policyID when the picker is pressed', () => {
render(<NetSuiteCustomListPicker policyID="P1" />);

mockedMenuItem.mock.lastCall?.[0].onPress?.({} as never);

expect(mockedNavigate).toHaveBeenCalledTimes(1);
expect(mockedNavigate).toHaveBeenCalledWith(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_LIST_SELECTOR.getRoute('P1'));
});

it('does not navigate when policyID is undefined so an "undefined" deep link is never produced', () => {
render(<NetSuiteCustomListPicker />);

mockedMenuItem.mock.lastCall?.[0].onPress?.({} as never);

expect(mockedNavigate).not.toHaveBeenCalled();
});
});
Loading
Loading