diff --git a/src/CONST.ts b/src/CONST.ts index a872636eb427..2ffc48bee694 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1447,7 +1447,12 @@ const CONST = { 3: 'createAccessToken', 4: 'enterCredentials', }, - IMPORT_CUSTOM_FIELDS: ['customSegments', 'customLists'], + IMPORT_CUSTOM_FIELDS: { + CUSTOM_SEGMENTS: 'customSegments', + CUSTOM_LISTS: 'customLists', + }, + CUSTOM_SEGMENT_FIELDS: ['segmentName', 'internalID', 'scriptID', 'mapping'], + CUSTOM_LIST_FIELDS: ['listName', 'internalID', 'transactionFieldID', 'mapping'], CUSTOM_FORM_ID_TYPE: { REIMBURSABLE: 'reimbursable', NON_REIMBURSABLE: 'nonReimbursable', @@ -1466,6 +1471,40 @@ const CONST = { JOBS: 'jobs', }, }, + NETSUITE_CUSTOM_LIST_LIMIT: 8, + NETSUITE_ADD_CUSTOM_LIST_STEP_NAMES: ['1', '2,', '3', '4'], + NETSUITE_ADD_CUSTOM_SEGMENT_STEP_NAMES: ['1', '2,', '3', '4', '5', '6,'], + }, + + NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES: { + CUSTOM_LISTS: { + CUSTOM_LIST_PICKER: 0, + TRANSACTION_FIELD_ID: 1, + MAPPING: 2, + CONFIRM: 3, + }, + CUSTOM_SEGMENTS: { + SEGMENT_TYPE: 0, + SEGMENT_NAME: 1, + INTERNAL_ID: 2, + SCRIPT_ID: 3, + MAPPING: 4, + CONFIRM: 5, + }, + }, + + NETSUITE_CUSTOM_RECORD_TYPES: { + CUSTOM_SEGMENT: 'customSegment', + CUSTOM_RECORD: 'customRecord', + }, + + NETSUITE_FORM_STEPS_HEADER_HEIGHT: 40, + + NETSUITE_IMPORT: { + HELP_LINKS: { + CUSTOM_SEGMENTS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite#custom-segments', + CUSTOM_LISTS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite#custom-lists', + }, }, NETSUITE_EXPORT_DATE: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7f5ee1119bb1..8cb684b983cb 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -560,6 +560,12 @@ const ONYXKEYS = { ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardDraft', SAGE_INTACCT_CREDENTIALS_FORM: 'sageIntacctCredentialsForm', SAGE_INTACCT_CREDENTIALS_FORM_DRAFT: 'sageIntacctCredentialsFormDraft', + NETSUITE_CUSTOM_FIELD_FORM: 'netSuiteCustomFieldForm', + NETSUITE_CUSTOM_FIELD_FORM_DRAFT: 'netSuiteCustomFieldFormDraft', + NETSUITE_CUSTOM_SEGMENT_ADD_FORM: 'netSuiteCustomSegmentAddForm', + NETSUITE_CUSTOM_SEGMENT_ADD_FORM_DRAFT: 'netSuiteCustomSegmentAddFormDraft', + NETSUITE_CUSTOM_LIST_ADD_FORM: 'netSuiteCustomListAddForm', + NETSUITE_CUSTOM_LIST_ADD_FORM_DRAFT: 'netSuiteCustomListAddFormDraft', NETSUITE_TOKEN_INPUT_FORM: 'netsuiteTokenInputForm', NETSUITE_TOKEN_INPUT_FORM_DRAFT: 'netsuiteTokenInputFormDraft', NETSUITE_CUSTOM_FORM_ID_FORM: 'netsuiteCustomFormIDForm', @@ -629,6 +635,9 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm; [ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm; [ONYXKEYS.FORMS.SAGE_INTACCT_CREDENTIALS_FORM]: FormTypes.SageIntactCredentialsForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_FIELD_FORM]: FormTypes.NetSuiteCustomFieldForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_LIST_ADD_FORM]: FormTypes.NetSuiteCustomFieldForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_SEGMENT_ADD_FORM]: FormTypes.NetSuiteCustomFieldForm; [ONYXKEYS.FORMS.NETSUITE_TOKEN_INPUT_FORM]: FormTypes.NetSuiteTokenInputForm; [ONYXKEYS.FORMS.NETSUITE_CUSTOM_FORM_ID_FORM]: FormTypes.NetSuiteCustomFormIDForm; [ONYXKEYS.FORMS.SAGE_INTACCT_DIMENSION_TYPE_FORM]: FormTypes.SageIntacctDimensionForm; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 3f93109463c9..054f38b9ec92 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1012,6 +1012,29 @@ const ROUTES = { getRoute: (policyID: string, importField: TupleToUnion) => `settings/workspaces/${policyID}/accounting/netsuite/import/mapping/${importField}` as const, }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField', + getRoute: (policyID: string, importCustomField: ValueOf) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField/view/:valueIndex', + getRoute: (policyID: string, importCustomField: ValueOf, valueIndex: number) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}/view/${valueIndex}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_EDIT: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField/edit/:valueIndex/:fieldName', + getRoute: (policyID: string, importCustomField: ValueOf, valueIndex: number, fieldName: string) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}/edit/${valueIndex}/${fieldName}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_LIST_ADD: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom-list/new', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import/custom-list/new` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom-segment/new', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import/custom-segment/new` as const, + }, POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS: { route: 'settings/workspaces/:policyID/accounting/netsuite/import/customer-projects', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import/customer-projects` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 923164eff8c5..c6b7da12e572 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -280,6 +280,11 @@ const SCREENS = { XERO_BILL_PAYMENT_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Bill_Payment_Account_Selector', XERO_EXPORT_BANK_ACCOUNT_SELECT: 'Policy_Accounting_Xero_Export_Bank_Account_Select', NETSUITE_IMPORT_MAPPING: 'Policy_Accounting_NetSuite_Import_Mapping', + NETSUITE_IMPORT_CUSTOM_FIELD: 'Policy_Accounting_NetSuite_Import_Custom_Field', + 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_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', NETSUITE_TOKEN_INPUT: 'Policy_Accounting_NetSuite_Token_Input', diff --git a/src/components/ConnectionLayout.tsx b/src/components/ConnectionLayout.tsx index 4bcfdc61077f..3809b4f4f110 100644 --- a/src/components/ConnectionLayout.tsx +++ b/src/components/ConnectionLayout.tsx @@ -61,8 +61,8 @@ type ConnectionLayoutProps = { /** Name of the current connection */ connectionName: ConnectionName; - /** Block the screen when the connection is not empty */ - reverseConnectionEmptyCheck?: boolean; + /** Whether the screen should load for an empty connection */ + shouldLoadForEmptyConnection?: boolean; /** Handler for back button press */ onBackButtonPress?: () => void; @@ -100,7 +100,7 @@ function ConnectionLayout({ shouldUseScrollView = true, headerTitleAlreadyTranslated, titleAlreadyTranslated, - reverseConnectionEmptyCheck = false, + shouldLoadForEmptyConnection = false, onBackButtonPress = () => Navigation.goBack(), shouldBeBlocked = false, }: ConnectionLayoutProps) { @@ -122,12 +122,14 @@ function ConnectionLayout({ [title, titleStyle, children, titleAlreadyTranslated], ); + const shouldBlockByConnection = shouldLoadForEmptyConnection ? !isConnectionEmpty : isConnectionEmpty; + return ( = FormProvider /** Whether to apply flex to the submit button */ submitFlexEnabled?: boolean; + + /** Whether button is disabled */ + isSubmitDisabled?: boolean; }; function FormProvider( diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 5c74fd466a15..77ef44343792 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -38,6 +38,9 @@ type FormWrapperProps = ChildrenProps & /** Assuming refs are React refs */ inputRefs: RefObject; + /** Whether the submit button is disabled */ + isSubmitDisabled?: boolean; + /** Callback to submit the form */ onSubmit: () => void; }; @@ -57,9 +60,11 @@ function FormWrapper({ enabledWhenOffline, isSubmitActionDangerous = false, formID, + shouldUseScrollView = true, scrollContextEnabled = false, shouldHideFixErrorsAlert = false, disablePressOnEnter = true, + isSubmitDisabled = false, }: FormWrapperProps) { const styles = useThemeStyles(); const formRef = useRef(null); @@ -108,6 +113,7 @@ function FormWrapper({ {isSubmitButtonVisible && ( {({safeAreaPaddingBottomStyle}) => diff --git a/src/components/Form/InputWrapper.tsx b/src/components/Form/InputWrapper.tsx index b5535a2fe6c1..c966dd4456e9 100644 --- a/src/components/Form/InputWrapper.tsx +++ b/src/components/Form/InputWrapper.tsx @@ -54,7 +54,7 @@ function computeComponentSpecificRegistrationParams({ shouldSetTouchedOnBlurOnly: false, // Forward the originally provided value blurOnSubmit, - shouldSubmitForm: false, + shouldSubmitForm: !!shouldSubmitForm, }; } diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 8cf59877c8db..5f56bbeceea6 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -21,6 +21,9 @@ import type TextPicker from '@components/TextPicker'; import type ValuePicker from '@components/ValuePicker'; import type BusinessTypePicker from '@pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker'; import type DimensionTypeSelector from '@pages/workspace/accounting/intacct/import/DimensionTypeSelector'; +import type NetSuiteCustomFieldMappingPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker'; +import type NetSuiteCustomListPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker'; +import type NetSuiteMenuWithTopDescriptionForm from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm'; import type {Country} from '@src/CONST'; import type {OnyxFormKey, OnyxValues} from '@src/ONYXKEYS'; import type {BaseForm} from '@src/types/form/Form'; @@ -49,7 +52,10 @@ type ValidInputs = | typeof AmountPicker | typeof TextPicker | typeof AddPlaidBankAccount - | typeof EmojiPickerButtonDropdown; + | typeof EmojiPickerButtonDropdown + | typeof NetSuiteCustomListPicker + | typeof NetSuiteCustomFieldMappingPicker + | typeof NetSuiteMenuWithTopDescriptionForm; type ValueTypeKey = 'string' | 'boolean' | 'date' | 'country' | 'reportFields' | 'disabledListValues'; type ValueTypeMap = { @@ -128,6 +134,9 @@ type FormProps = { /** Whether ScrollWithContext should be used instead of regular ScrollView. Set to true when there's a nested Picker component in Form. */ scrollContextEnabled?: boolean; + /** Whether to use ScrollView */ + shouldUseScrollView?: boolean; + /** Container styles */ style?: StyleProp; diff --git a/src/components/WorkspaceEmptyStateSection.tsx b/src/components/WorkspaceEmptyStateSection.tsx index 6a00aa4bf5eb..252197fda96b 100644 --- a/src/components/WorkspaceEmptyStateSection.tsx +++ b/src/components/WorkspaceEmptyStateSection.tsx @@ -14,6 +14,9 @@ type WorkspaceEmptyStateSectionProps = { /** The text to display in the subtitle of the section */ subtitle?: string; + /** The component to show in the subtitle of the section */ + subtitleComponent?: React.ReactNode; + /** The icon to display along with the title */ icon: IconAsset; @@ -24,7 +27,7 @@ type WorkspaceEmptyStateSectionProps = { shouldStyleAsCard?: boolean; }; -function WorkspaceEmptyStateSection({icon, subtitle, title, containerStyle, shouldStyleAsCard = true}: WorkspaceEmptyStateSectionProps) { +function WorkspaceEmptyStateSection({icon, subtitle, title, containerStyle, shouldStyleAsCard = true, subtitleComponent}: WorkspaceEmptyStateSectionProps) { const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -50,9 +53,9 @@ function WorkspaceEmptyStateSection({icon, subtitle, title, containerStyle, shou {title} - {!!subtitle && ( + {(!!subtitle || !!subtitleComponent) && ( - {subtitle} + {subtitleComponent ?? {subtitle}} )} diff --git a/src/languages/en.ts b/src/languages/en.ts index 5c5cc795f4f8..f789ddc5f840 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2038,6 +2038,7 @@ export default { welcomeNote: ({workspaceName}: WelcomeNoteParams) => `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`, subscription: 'Subscription', + letsDoubleCheck: "Let's double check that everything looks right.", lineItemLevel: 'Line-item level', reportLevel: 'Report level', appliedOnExport: 'Not imported into Expensify, applied on export', @@ -2477,8 +2478,77 @@ export default { }, importTaxDescription: 'Import tax groups from NetSuite.', importCustomFields: { - customSegments: 'Custom segments/records', - customLists: 'Custom lists', + chooseOptionBelow: 'Choose an option below:', + requiredFieldError: (fieldName: string) => `Please enter the ${fieldName}`, + customSegments: { + title: 'Custom segments/records', + addText: 'Add custom segment/record', + recordTitle: 'Custom segment', + helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS, + helpLinkText: 'View detailed instructions', + helpText: ' on configuring custom segments/records.', + emptyTitle: 'Add a custom segment or custom record', + fields: { + segmentName: 'Name', + internalID: 'Internal ID', + scriptID: 'Script ID', + customRecordScriptID: 'Transaction column ID', + mapping: 'Displayed as', + }, + removeTitle: 'Remove custom segment/record', + removePrompt: 'Are you sure you want to remove this custom segment/record?', + addForm: { + customSegmentName: 'custom segment name', + customRecordName: 'custom record name', + segmentTitle: 'Custom segment', + customSegmentAddTitle: 'Add custom segment', + customRecordAddTitle: 'Add custom record', + recordTitle: 'Custom record', + segmentRecordType: 'Do you want to add a custom segment or a custom record?', + customSegmentNameTitle: "What's the custom segment name?", + customRecordNameTitle: "What's the custom record name?", + customSegmentNameFooter: `You can find custom segment names in NetSuite under *Customizations > Links, Records & Fields > Custom Segments* page.\n\n_For more detailed instructions, [visit our help site](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, + customRecordNameFooter: `You can find custom record names in NetSuite by entering the "Transaction Column Field" in global search.\n\n_For more detailed instructions, [visit our help site](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, + customSegmentInternalIDTitle: "What's the internal ID?", + customSegmentInternalIDFooter: `First, make sure you've enabled internal IDs in NetSuite under *Home > Set Preferences > Show Internal ID.*\n\nYou can find custom segment internal IDs in NetSuite under:\n\n1. *Customization > Lists, Records, & Fields > Custom Segments*.\n2. Click into a custom segment.\n3. Click the hyperlink next to *Custom Record Type*.\n4. Find the internal ID in the table at the bottom.\n\n_For more detailed instructions, [visit our help site](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS})_.`, + customRecordInternalIDFooter: `You can find custom record internal IDs in NetSuite by following these steps:\n\n1. Enter "Transaction Line Fields" in global search.\n2. Click into a custom record.\n3. Find the internal ID on the left-hand side.\n\n_For more detailed instructions, [visit our help site](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, + customSegmentScriptIDTitle: "What's the script ID?", + customSegmentScriptIDFooter: `You can find custom segment script IDs in NetSuite under: \n\n1. *Customization > Lists, Records, & Fields > Custom Segments*.\n2. Click into a custom segment.\n3. Click the *Application and Sourcing* tab near the bottom, then:\n a. If you want to display the custom segment as a *tag* (at the line-item level) in Expensify, click the *Transaction Columns* sub-tab and use the *Field ID*.\n b. If you want to display the custom segment as a *report field* (at the report level) in Expensify, click the *Transactions* sub-tab and use the *Field ID*.\n\n_For more detailed instructions, [visit our help site](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS})_.`, + customRecordScriptIDTitle: "What's the transaction column ID?", + customRecordScriptIDFooter: `You can find custom record script IDs in NetSuite under:\n\n1. Enter "Transaction Line Fields" in global search.\n2. Click into a custom record.\n3. Find the script ID on the left-hand side.\n\n_For more detailed instructions, [visit our help site](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, + customSegmentMappingTitle: 'How should this custom segment be displayed in Expensify?', + customRecordMappingTitle: 'How should this custom record be displayed in Expensify?', + }, + errors: { + uniqueFieldError: (fieldName: string) => `A custom segment/record with this ${fieldName?.toLowerCase()} already exists.`, + }, + }, + customLists: { + title: 'Custom lists', + addText: 'Add custom list', + recordTitle: 'Custom list', + helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS, + helpLinkText: 'View detailed instructions', + helpText: ' on configuring custom lists.', + emptyTitle: 'Add a custom list', + fields: { + listName: 'Name', + internalID: 'Internal ID', + transactionFieldID: 'Transaction field ID', + mapping: 'Displayed as', + }, + removeTitle: 'Remove custom list', + removePrompt: 'Are you sure you want to remove this custom list?', + addForm: { + listNameTitle: 'Choose a custom list', + transactionFieldIDTitle: "What's the transaction field ID?", + transactionFieldIDFooter: `You can find transaction field IDs in NetSuite by following these steps:\n\n1. Enter "Transaction Line Fields" in global search.\n2. Click into a custom list.\n3. Find the transaction field ID on the left-hand side.\n\n_For more detailed instructions, [visit our help site](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS})_.`, + mappingTitle: 'How should this custom list be displayed in Expensify?', + }, + errors: { + uniqueTransactionFieldIDError: `A custom list with this transaction field ID already exists.`, + }, + }, }, importTypes: { [CONST.INTEGRATION_ENTITY_MAP_TYPES.NETSUITE_DEFAULT]: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 6b685343d8e2..e9e23396f5d5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2068,6 +2068,7 @@ export default { welcomeNote: ({workspaceName}: WelcomeNoteParams) => `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`, subscription: 'Suscripción', + letsDoubleCheck: 'Verifiquemos que todo esté correcto', reportField: 'Campo del informe', lineItemLevel: 'Nivel de partida', reportLevel: 'Nivel de informe', @@ -2522,8 +2523,77 @@ export default { }, importTaxDescription: 'Importar grupos de impuestos desde NetSuite.', importCustomFields: { - customSegments: 'Segmentos/registros personalizados', - customLists: 'Listas personalizado', + chooseOptionBelow: 'Elija una de las opciones siguientes:', + requiredFieldError: (fieldName: string) => `Por favor, introduzca el ${fieldName}`, + customSegments: { + title: 'Segmentos/registros personalizados', + addText: 'Añadir segmento/registro personalizado', + recordTitle: 'Segmento personalizado', + helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS, + helpLinkText: 'Ver instrucciones detalladas', + helpText: ' sobre la configuración de segmentos/registros personalizado.', + emptyTitle: 'Añadir un segmento personalizado o un registro personalizado', + fields: { + segmentName: 'Name', + internalID: 'Identificación interna', + scriptID: 'ID de guión', + mapping: 'Mostrado como', + customRecordScriptID: 'ID de columna de transacción', + }, + removeTitle: 'Eliminar segmento/registro personalizado', + removePrompt: '¿Está seguro de que desea eliminar este segmento/registro personalizado?', + addForm: { + customSegmentName: 'nombre de segmento personalizado', + customRecordName: 'nombre de registro personalizado', + segmentTitle: 'Segmento personalizado', + customSegmentAddTitle: 'Añadir segmento personalizado', + customRecordAddTitle: 'Añadir registro personalizado', + recordTitle: 'Registro personalizado', + segmentRecordType: '¿Desea añadir un segmento personalizado o un registro personalizado?', + customSegmentNameTitle: '¿Cuál es el nombre del segmento personalizado?', + customRecordNameTitle: '¿Cuál es el nombre del registro personalizado?', + customSegmentNameFooter: `Puede encontrar los nombres de los segmentos personalizados en NetSuite en la página *Personalizaciones > Vínculos, registros y campos > Segmentos personalizados*.\nn_Para obtener instrucciones más detalladas, [visite nuestro sitio de ayuda](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, + customRecordNameFooter: `Puede encontrar nombres de registros personalizados en NetSuite introduciendo el "Campo de columna de transacción" en la búsqueda global.\nn_Para obtener instrucciones más detalladas, [visite nuestro sitio de ayuda](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, + customSegmentInternalIDTitle: '¿Cuál es la identificación interna?', + customSegmentInternalIDFooter: `En primer lugar, asegúrese de que ha habilitado los ID internos en NetSuite en *Inicio > Establecer preferencias > Mostrar ID interno*. *Personalización > Listas, registros y campos > Segmentos personalizados*.\n2. Haga clic en un segmento personalizado. Haga clic en un segmento personalizado. Haga clic en el hipervínculo situado junto a *Tipo de registro personalizado*.\n4. Para obtener instrucciones más detalladas, [visite nuestro sitio de ayuda](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS})_.`, + customRecordInternalIDFooter: `Puede encontrar IDs internos de registros personalizados en NetSuite siguiendo estos pasos:\n\n1. Introduzca "Campos de línea de transacción" en la búsqueda global. Haga clic en un registro personalizado. Para obtener instrucciones más detalladas, [visite nuestro sitio de ayuda](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, + customSegmentScriptIDTitle: '¿Cuál es el ID del guión?', + customSegmentScriptIDFooter: `Puede encontrar IDs de script de segmentos personalizados en NetSuite en: \n\n1. *Personalización > Listas, Registros y Campos > Segmentos Personalizados*.\n2. Haga clic en un segmento personalizado. a. Si desea mostrar el segmento personalizado como una *etiqueta* (a nivel de partida) en Expensify, haga clic en la subpestaña *Columnas de transacción* y utilice el *ID de campo*. b. Si desea mostrar el segmento personalizado como una *etiqueta* (a nivel de partida) en Expensify, haga clic en la subpestaña *Columnas de transacción* y utilice el *ID de campo*. Si desea mostrar el segmento personalizado como un *campo de informe* (a nivel de informe) en Expensify, haga clic en la subpestaña *Transacciones* y utilice el *ID de campo*. Para obtener instrucciones más detalladas, [visite nuestro sitio de ayuda](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS})_.`, + customRecordScriptIDTitle: '¿Cuál es el ID de columna de la transacción?', + customRecordScriptIDFooter: `Puede encontrar IDs de script de registro personalizados en NetSuite en:\n\n1. Introduzca "Campos de línea de transacción" en la búsqueda global.\n2. Haga clic en un registro personalizado.\n3. Para obtener instrucciones más detalladas, [visite nuestro sitio de ayuda](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, + customSegmentMappingTitle: '¿Cómo debería mostrarse este segmento personalizado en Expensify?', + customRecordMappingTitle: '¿Cómo debería mostrarse este registro de segmento personalizado en Expensify?', + }, + errors: { + uniqueFieldError: (fieldName: string) => `Ya existe un segmento/registro personalizado con este ${fieldName?.toLowerCase()}.`, + }, + }, + customLists: { + title: 'Listas personalizados', + addText: 'Añadir lista personalizado', + recordTitle: 'Lista personalizado', + helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS, + helpLinkText: 'Ver instrucciones detalladas', + helpText: ' sobre cómo configurar listas personalizado.', + emptyTitle: 'Añadir una lista personalizado', + fields: { + listName: 'Nombre', + internalID: 'Identificación interna', + transactionFieldID: 'ID del campo de transacción', + mapping: 'Mostrado como', + }, + removeTitle: 'Eliminar lista personalizado', + removePrompt: '¿Está seguro de que desea eliminar esta lista personalizado?', + addForm: { + listNameTitle: 'Elija una lista personalizada', + transactionFieldIDTitle: '¿Cuál es el ID del campo de transacción?', + transactionFieldIDFooter: `Puede encontrar los ID de campo de transacción en NetSuite siguiendo estos pasos:\n\n1. Introduzca "Campos de línea de transacción" en búsqueda global. Introduzca "Campos de línea de transacción" en la búsqueda global.\n2. Haga clic en una lista personalizada.\n3. Para obtener instrucciones más detalladas, [visite nuestro sitio de ayuda](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS})_.`, + mappingTitle: '¿Cómo debería mostrarse esta lista personalizada en Expensify?', + }, + errors: { + uniqueTransactionFieldIDError: `Ya existe una lista personalizada con este ID de campo de transacción.`, + }, + }, }, importTypes: { [CONST.INTEGRATION_ENTITY_MAP_TYPES.NETSUITE_DEFAULT]: { diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 566f493a00e9..ca284321e3bb 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -265,6 +265,8 @@ const WRITE_COMMANDS = { UPDATE_NETSUITE_TAX_POSTING_ACCOUNT: 'UpdateNetSuiteTaxPostingAccount', UPDATE_NETSUITE_ALLOW_FOREIGN_CURRENCY: 'UpdateNetSuiteAllowForeignCurrency', UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD: 'UpdateNetSuiteExportToNextOpenPeriod', + UPDATE_NETSUITE_CUSTOM_SEGMENTS: 'UpdateNetSuiteCustomSegments', + UPDATE_NETSUITE_CUSTOM_LISTS: 'UpdateNetSuiteCustomLists', UPDATE_NETSUITE_AUTO_SYNC: 'UpdateNetSuiteAutoSync', UPDATE_NETSUITE_SYNC_REIMBURSED_REPORTS: 'UpdateNetSuiteSyncReimbursedReports', UPDATE_NETSUITE_SYNC_PEOPLE: 'UpdateNetSuiteSyncPeople', @@ -592,6 +594,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_NETSUITE_TAX_POSTING_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'bankAccountID', string>; [WRITE_COMMANDS.UPDATE_NETSUITE_ALLOW_FOREIGN_CURRENCY]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_SEGMENTS]: Parameters.UpdateNetSuiteGenericTypeParams<'customSegments', string>; // JSON string NetSuiteCustomSegment[] + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_LISTS]: Parameters.UpdateNetSuiteGenericTypeParams<'customLists', string>; // JSON string NetSuiteCustomList[] [WRITE_COMMANDS.UPDATE_NETSUITE_AUTO_SYNC]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_REIMBURSED_REPORTS]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_PEOPLE]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 6dc9e13d0500..adaa25543223 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -325,6 +325,16 @@ const SettingsModalStackNavigator = createModalStackNavigator('../../../../pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: () => require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_MAPPING]: () => require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportMappingPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD]: () => + require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_VIEW]: () => + require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldView').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_EDIT]: () => + require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_ADD]: () => + require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomListPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD]: () => + require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomSegmentPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS]: () => require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomersOrProjectsPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT]: () => 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 1dec713058c4..54804a495754 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -66,6 +66,11 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_MAPPING, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD, + 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_SEGMENT_ADD, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 4dfe3df1df8c..bdccc65ad2a0 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -357,6 +357,11 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_MAPPING]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_MAPPING.route}, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING.route}, + [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_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}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 92263ab8d81e..7e14a59f477b 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -449,6 +449,27 @@ type SettingsNavigatorParamList = { policyID: string; importField: TupleToUnion; }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD]: { + policyID: string; + importCustomField: TupleToUnion; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_VIEW]: { + policyID: string; + importCustomField: TupleToUnion; + internalID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_EDIT]: { + policyID: string; + importCustomField: TupleToUnion; + internalID: string; + fieldName: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_LIST_ADD]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD]: { + policyID: string; + }; [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT]: { policyID: string; }; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 8a967d96729b..140916349c53 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -7,8 +7,20 @@ import type {SelectorType} from '@components/SelectionScreen'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; -import type {ConnectionLastSync, Connections, CustomUnit, NetSuiteAccount, NetSuiteConnection, PolicyFeatureName, Rate, Tenant} from '@src/types/onyx/Policy'; +import type { + ConnectionLastSync, + Connections, + CustomUnit, + NetSuiteAccount, + NetSuiteConnection, + NetSuiteCustomList, + NetSuiteCustomSegment, + PolicyFeatureName, + Rate, + Tenant, +} from '@src/types/onyx/Policy'; import type PolicyEmployee from '@src/types/onyx/PolicyEmployee'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import * as Localize from './Localize'; @@ -608,6 +620,20 @@ function getCustomersOrJobsLabelNetSuite(policy: Policy | undefined, translate: return importedValueLabel.charAt(0).toUpperCase() + importedValueLabel.slice(1); } +function isNetSuiteCustomSegmentRecord(customField: NetSuiteCustomList | NetSuiteCustomSegment): boolean { + return 'segmentName' in customField; +} + +function getNameFromNetSuiteCustomField(customField: NetSuiteCustomList | NetSuiteCustomSegment): string { + return 'segmentName' in customField ? customField.segmentName : customField.listName; +} + +function isNetSuiteCustomFieldPropertyEditable(customField: NetSuiteCustomList | NetSuiteCustomSegment, fieldName: string) { + const fieldsAllowedToEdit = isNetSuiteCustomSegmentRecord(customField) ? [INPUT_IDS.SEGMENT_NAME, INPUT_IDS.INTERNAL_ID, INPUT_IDS.SCRIPT_ID, INPUT_IDS.MAPPING] : [INPUT_IDS.MAPPING]; + const fieldKey = fieldName as keyof typeof customField; + return fieldsAllowedToEdit.includes(fieldKey); +} + function getIntegrationLastSuccessfulDate(connection?: Connections[keyof Connections]) { if (!connection) { return undefined; @@ -780,6 +806,9 @@ export { getIntegrationLastSuccessfulDate, getCurrentConnectionName, getCustomersOrJobsLabelNetSuite, + isNetSuiteCustomSegmentRecord, + getNameFromNetSuiteCustomField, + isNetSuiteCustomFieldPropertyEditable, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index 66bf482d6ef2..adab82a5da8f 100644 --- a/src/libs/actions/connections/NetSuiteCommands.ts +++ b/src/libs/actions/connections/NetSuiteCommands.ts @@ -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, NetSuiteCustomFormID} from '@src/types/onyx/Policy'; +import type {Connections, NetSuiteCustomFormID, NetSuiteCustomList, NetSuiteCustomSegment} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; type SubsidiaryParam = { @@ -427,6 +427,31 @@ function updateNetSuiteCrossSubsidiaryCustomersConfiguration(policyID: string, i API.write(WRITE_COMMANDS.UPDATE_NETSUITE_CROSS_SUBSIDIARY_CUSTOMER_CONFIGURATION, params, onyxData); } +function updateNetSuiteCustomSegments(policyID: string, records: NetSuiteCustomSegment[], oldRecords: NetSuiteCustomSegment[]) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_SEGMENTS, records, oldRecords); + + API.write( + WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_SEGMENTS, + { + policyID, + customSegments: JSON.stringify(records), + }, + onyxData, + ); +} + +function updateNetSuiteCustomLists(policyID: string, records: NetSuiteCustomList[], oldRecords: NetSuiteCustomList[]) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_LISTS, records, oldRecords); + API.write( + WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_LISTS, + { + policyID, + customLists: JSON.stringify(records), + }, + onyxData, + ); +} + function updateNetSuiteExporter(policyID: string, exporter: string, oldExporter: string) { const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.EXPORTER, exporter, oldExporter); @@ -849,6 +874,8 @@ export { updateNetSuiteExportToNextOpenPeriod, updateNetSuiteImportMapping, updateNetSuiteCrossSubsidiaryCustomersConfiguration, + updateNetSuiteCustomSegments, + updateNetSuiteCustomLists, updateNetSuiteAutoSync, updateNetSuiteSyncReimbursedReports, updateNetSuiteSyncPeople, diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx index 41d428257163..8bfcc42a81da 100644 --- a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx @@ -60,8 +60,8 @@ function NetSuiteTokenInputPage({policy}: WithPolicyConnectionsProps) { featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED} contentContainerStyle={[styles.flex1]} titleStyle={styles.ph5} - reverseConnectionEmptyCheck - connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO} + shouldLoadForEmptyConnection + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} onBackButtonPress={handleBackButtonPress} shouldIncludeSafeAreaPaddingBottom > diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit.tsx new file mode 100644 index 000000000000..9fe11b8eb00d --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit.tsx @@ -0,0 +1,170 @@ +import React, {useCallback, useMemo} from 'react'; +import type {ValueOf} from 'type-fest'; +import ConnectionLayout from '@components/ConnectionLayout'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {updateNetSuiteCustomLists, updateNetSuiteCustomSegments} from '@libs/actions/connections/NetSuiteCommands'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {NetSuiteCustomList, NetSuiteCustomSegment} from '@src/types/onyx/Policy'; +import NetSuiteCustomFieldMappingPicker from './NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker'; + +type CustomField = NetSuiteCustomList | NetSuiteCustomSegment; +type ImportCustomFieldsKeys = ValueOf; + +type NetSuiteImportCustomFieldViewProps = WithPolicyConnectionsProps & { + route: { + params: { + /** Whether the record is of type custom segment or list */ + importCustomField: ImportCustomFieldsKeys; + + /** Index of the current record */ + valueIndex: number; + + /** Selected field of the current record */ + fieldName: string; + }; + }; +}; + +function NetSuiteImportCustomFieldEdit({ + policy, + route: { + params: {importCustomField, valueIndex, fieldName}, + }, +}: NetSuiteImportCustomFieldViewProps) { + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const config = policy?.connections?.netsuite?.options?.config; + const allRecords = useMemo(() => config?.syncOptions?.[importCustomField] ?? [], [config?.syncOptions, importCustomField]); + + const customField: CustomField | undefined = allRecords[valueIndex]; + const fieldValue = customField?.[fieldName as keyof CustomField] ?? ''; + + const updateRecord = useCallback( + (formValues: Partial>) => { + const newValue = formValues[fieldName as keyof typeof formValues]; + + if (customField) { + const updatedRecords = allRecords.map((record, index) => { + if (index === Number(valueIndex)) { + return { + ...record, + [fieldName]: newValue, + }; + } + return record; + }); + + if (PolicyUtils.isNetSuiteCustomSegmentRecord(customField)) { + updateNetSuiteCustomSegments(policyID, updatedRecords as NetSuiteCustomSegment[], allRecords as NetSuiteCustomSegment[]); + } else { + updateNetSuiteCustomLists(policyID, updatedRecords as NetSuiteCustomList[], allRecords as NetSuiteCustomList[]); + } + } + + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW.getRoute(policyID, importCustomField, valueIndex)); + }, + [allRecords, customField, fieldName, importCustomField, policyID, valueIndex], + ); + + const validate = useCallback( + (formValues: FormOnyxValues) => { + const errors: FormInputErrors = {}; + + const key = fieldName as keyof typeof formValues; + const fieldLabel = translate(`workspace.netsuite.import.importCustomFields.${importCustomField}.fields.${fieldName}` as TranslationPaths); + if (!formValues[key]) { + ErrorUtils.addErrorMessage(errors, fieldName, translate('workspace.netsuite.import.importCustomFields.requiredFieldError', fieldLabel)); + } else if ( + policy?.connections?.netsuite?.options?.config?.syncOptions?.customSegments?.find( + (customSegment) => customSegment?.[fieldName as keyof typeof customSegment]?.toLowerCase() === formValues[key].toLowerCase(), + ) + ) { + ErrorUtils.addErrorMessage(errors, fieldName, translate('workspace.netsuite.import.importCustomFields.customSegments.errors.uniqueFieldError', fieldLabel)); + } + + return errors; + }, + [fieldName, importCustomField, policy?.connections?.netsuite?.options?.config?.syncOptions?.customSegments, translate], + ); + + const renderForm = useMemo( + () => + customField && ( + + + + ), + [config?.syncOptions?.pendingFields, customField, fieldName, fieldValue, importCustomField, styles.flexGrow1, styles.ph5, translate, updateRecord, validate], + ); + + const renderSelection = useMemo( + () => + customField && ( + { + updateRecord({ + [fieldName]: value, + }); + }} + value={fieldValue} + /> + ), + [customField, fieldName, fieldValue, updateRecord], + ); + + const renderMap: Record = { + mapping: renderSelection, + }; + + return ( + + {renderMap[fieldName] || renderForm} + + ); +} + +NetSuiteImportCustomFieldEdit.displayName = 'NetSuiteImportCustomFieldEdit'; +export default withPolicyConnections(NetSuiteImportCustomFieldEdit); diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker.tsx new file mode 100644 index 000000000000..bf4cd65bd981 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useLocalize from '@hooks/useLocalize'; +import CONST from '@src/CONST'; + +type NetSuiteCustomListPickerProps = { + /** Selected mapping value */ + value?: string; + + /** Callback to fire when mapping is selected */ + onInputChange?: (value: string) => void; +}; + +function NetSuiteCustomFieldMappingPicker({value, onInputChange}: NetSuiteCustomListPickerProps) { + const {translate} = useLocalize(); + + const options = [CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG, CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]; + + const selectionData = + options.map((option) => ({ + text: translate(`workspace.netsuite.import.importTypes.${option}.label`), + keyForList: option, + isSelected: value === option, + value: option, + alternateText: translate(`workspace.netsuite.import.importTypes.${option}.description`), + })) ?? []; + + return ( + { + onInputChange?.(selected.value); + }} + ListItem={RadioListItem} + initiallyFocusedOptionKey={value ?? CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG} + /> + ); +} + +NetSuiteCustomFieldMappingPicker.displayName = 'NetSuiteCustomFieldMappingPicker'; +export default NetSuiteCustomFieldMappingPicker; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker.tsx new file mode 100644 index 000000000000..18a4a8318316 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker.tsx @@ -0,0 +1,67 @@ +import React, {useState} 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'; + +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; + + /** Form Error description */ + errorText?: string; +}; + +function NetSuiteCustomListPicker({value, policy, internalIDInputID, errorText, onInputChange = () => {}}: 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 ( + <> + setIsPickerVisible(true)} + brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + errorText={errorText} + /> + + + ); +} + +NetSuiteCustomListPicker.displayName = 'NetSuiteCustomListPicker'; +export default NetSuiteCustomListPicker; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListSelectorModal.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListSelectorModal.tsx new file mode 100644 index 000000000000..3eeebbafc8a3 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListSelectorModal.tsx @@ -0,0 +1,112 @@ +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 RadioListItem from '@components/SelectionList/RadioListItem'; +import useDebouncedState from '@hooks/useDebouncedState'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {CustomListSelectorType} from '@pages/workspace/accounting/netsuite/types'; +import CONST from '@src/CONST'; +import type {Policy} from '@src/types/onyx'; + +type NetSuiteCustomListSelectorModalProps = { + /** Whether the modal is visible */ + isVisible: boolean; + + /** 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) { + const {translate} = useLocalize(); + const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); + + const {sections, headerMessage, showTextInput} = useMemo(() => { + const customLists = policy?.connections?.netsuite?.options?.data?.customLists ?? []; + const customListData = customLists.map((customListRecord) => ({ + text: customListRecord.name, + value: customListRecord.name, + isSelected: customListRecord.name === currentCustomListValue, + keyForList: customListRecord.name, + id: customListRecord.id, + })); + + const searchRegex = new RegExp(Str.escapeForRegExp(debouncedSearchValue.trim()), 'i'); + const filteredCustomLists = customListData.filter((customListRecord) => searchRegex.test(customListRecord.text ?? '')); + const isEmpty = debouncedSearchValue.trim() && !filteredCustomLists.length; + + return { + sections: isEmpty + ? [] + : [ + { + data: filteredCustomLists, + }, + ], + headerMessage: isEmpty ? translate('common.noResultsFound') : '', + showTextInput: customListData.length > CONST.NETSUITE_CONFIG.NETSUITE_CUSTOM_LIST_LIMIT, + }; + }, [debouncedSearchValue, policy?.connections?.netsuite?.options?.data?.customLists, translate, currentCustomListValue]); + + const styles = useThemeStyles(); + + return ( + + + + + + + ); +} + +NetSuiteCustomListSelectorModal.displayName = 'NetSuiteCustomListSelectorModal'; + +export default NetSuiteCustomListSelectorModal; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomListPage.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomListPage.tsx new file mode 100644 index 000000000000..ae0b205e7373 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomListPage.tsx @@ -0,0 +1,176 @@ +import React, {useCallback, useMemo, useRef} from 'react'; +import type {ForwardedRef} from 'react'; +import {View} from 'react-native'; +import ConnectionLayout from '@components/ConnectionLayout'; +import FormProvider from '@components/Form/FormProvider'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import type {InteractiveStepSubHeaderHandle} from '@components/InteractiveStepSubHeader'; +import useLocalize from '@hooks/useLocalize'; +import useSubStep from '@hooks/useSubStep'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Connections from '@libs/actions/connections/NetSuiteCommands'; +import Navigation from '@libs/Navigation/Navigation'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; +import ChooseCustomListStep from './substeps/ChooseCustomListStep'; +import ConfirmCustomListStep from './substeps/ConfirmCustomListStep'; +import MappingStep from './substeps/MappingStep'; +import TransactionFieldIDStep from './substeps/TransactionFieldIDStep'; + +const formSteps = [ChooseCustomListStep, TransactionFieldIDStep, MappingStep, ConfirmCustomListStep]; + +function NetSuiteImportAddCustomListPage({policy}: WithPolicyConnectionsProps) { + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const ref: ForwardedRef = useRef(null); + + const config = policy?.connections?.netsuite?.options?.config; + const customLists = useMemo(() => config?.syncOptions?.customLists ?? [], [config?.syncOptions]); + + const handleFinishStep = useCallback(() => { + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING.getRoute(policyID, CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_LISTS)); + }, [policyID]); + + const { + componentToRender: SubStep, + isEditing, + nextScreen, + prevScreen, + screenIndex, + moveTo, + goToTheLastStep, + } = useSubStep({ + bodyContent: formSteps, + startFrom: CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_LISTS.CUSTOM_LIST_PICKER, + onFinished: handleFinishStep, + }); + + const handleBackButtonPress = () => { + if (isEditing) { + goToTheLastStep(); + return; + } + + // Clicking back on the first screen should go back to listing + if (screenIndex === CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_LISTS.CUSTOM_LIST_PICKER) { + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING.getRoute(policyID, CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_LISTS)); + return; + } + ref.current?.movePrevious(); + prevScreen(); + }; + + const handleNextScreen = useCallback(() => { + if (isEditing) { + goToTheLastStep(); + return; + } + ref.current?.moveNext(); + nextScreen(); + }, [goToTheLastStep, isEditing, nextScreen]); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + switch (screenIndex) { + case CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_LISTS.CUSTOM_LIST_PICKER: + return ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.LIST_NAME]); + case CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_LISTS.TRANSACTION_FIELD_ID: + if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.TRANSACTION_FIELD_ID])) { + const fieldLabel = translate(`workspace.netsuite.import.importCustomFields.customLists.fields.transactionFieldID`); + errors[INPUT_IDS.TRANSACTION_FIELD_ID] = translate('workspace.netsuite.import.importCustomFields.requiredFieldError', fieldLabel); + } else if (customLists.find((customList) => customList.transactionFieldID.toLowerCase() === values[INPUT_IDS.TRANSACTION_FIELD_ID].toLowerCase())) { + errors[INPUT_IDS.TRANSACTION_FIELD_ID] = translate('workspace.netsuite.import.importCustomFields.customLists.errors.uniqueTransactionFieldIDError'); + } + return errors; + case CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_LISTS.MAPPING: + return ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.MAPPING]); + default: + return errors; + } + }, + [customLists, screenIndex, translate], + ); + + const updateNetSuiteCustomLists = useCallback( + (formValues: FormOnyxValues) => { + const updatedCustomLists = customLists.concat([ + { + listName: formValues[INPUT_IDS.LIST_NAME], + internalID: formValues[INPUT_IDS.INTERNAL_ID], + transactionFieldID: formValues[INPUT_IDS.TRANSACTION_FIELD_ID], + mapping: formValues[INPUT_IDS.MAPPING] ?? CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG, + }, + ]); + Connections.updateNetSuiteCustomLists(policyID, updatedCustomLists, customLists); + nextScreen(); + }, + [customLists, nextScreen, policyID], + ); + + const selectionListForm = [CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_LISTS.MAPPING as number].includes(screenIndex); + const submitFlexAllowed = [ + CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_LISTS.CUSTOM_LIST_PICKER as number, + CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_LISTS.TRANSACTION_FIELD_ID as number, + ].includes(screenIndex); + + return ( + + + + + + + + + + + ); +} + +NetSuiteImportAddCustomListPage.displayName = 'NetSuiteImportAddCustomListPage'; + +export default withPolicyConnections(NetSuiteImportAddCustomListPage); diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomSegmentPage.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomSegmentPage.tsx new file mode 100644 index 000000000000..f6832edcae97 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomSegmentPage.tsx @@ -0,0 +1,220 @@ +import React, {useCallback, useMemo, useRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import ConnectionLayout from '@components/ConnectionLayout'; +import FormProvider from '@components/Form/FormProvider'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import type {InteractiveStepSubHeaderHandle} from '@components/InteractiveStepSubHeader'; +import useLocalize from '@hooks/useLocalize'; +import useSubStep from '@hooks/useSubStep'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Connections from '@libs/actions/connections/NetSuiteCommands'; +import Navigation from '@libs/Navigation/Navigation'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; +import ChooseSegmentTypeStep from './substeps/ChooseSegmentTypeStep'; +import ConfirmCustomSegmentList from './substeps/ConfirmCustomSegmentList'; +import CustomSegmentInternalIdStep from './substeps/CustomSegmentInternalIdStep'; +import CustomSegmentNameStep from './substeps/CustomSegmentNameStep'; +import CustomSegmentScriptIdStep from './substeps/CustomSegmentScriptIdStep'; +import MappingStep from './substeps/MappingStep'; + +const formSteps = [ChooseSegmentTypeStep, CustomSegmentNameStep, CustomSegmentInternalIdStep, CustomSegmentScriptIdStep, MappingStep, ConfirmCustomSegmentList]; + +function NetSuiteImportAddCustomSegmentPage({policy}: WithPolicyConnectionsProps) { + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const ref: ForwardedRef = useRef(null); + + const config = policy?.connections?.netsuite?.options?.config; + const customSegments = useMemo(() => config?.syncOptions?.customSegments ?? [], [config?.syncOptions]); + + const handleFinishStep = useCallback(() => { + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING.getRoute(policyID, CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_SEGMENTS)); + }, [policyID]); + + const { + componentToRender: SubStep, + isEditing, + nextScreen, + prevScreen, + screenIndex, + moveTo, + goToTheLastStep, + } = useSubStep({bodyContent: formSteps, startFrom: CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.SEGMENT_TYPE, onFinished: handleFinishStep}); + + const handleBackButtonPress = () => { + if (isEditing) { + goToTheLastStep(); + return; + } + + // Clicking back on the first screen should go back to listing + if (screenIndex === CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.SEGMENT_TYPE) { + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING.getRoute(policyID, CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_SEGMENTS)); + return; + } + ref.current?.movePrevious(); + prevScreen(); + }; + + const handleNextScreen = useCallback(() => { + if (isEditing) { + goToTheLastStep(); + return; + } + ref.current?.moveNext(); + nextScreen(); + }, [goToTheLastStep, isEditing, nextScreen]); + + const [customSegmentType, setCustomSegmentType] = useState | undefined>(); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + const customSegmentRecordType = customSegmentType ?? CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT; + switch (screenIndex) { + case CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.SEGMENT_NAME: + if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.SEGMENT_NAME])) { + errors[INPUT_IDS.SEGMENT_NAME] = translate( + 'workspace.netsuite.import.importCustomFields.requiredFieldError', + translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.${customSegmentRecordType}Name`), + ); + } else if (customSegments.find((customSegment) => customSegment.segmentName.toLowerCase() === values[INPUT_IDS.SEGMENT_NAME].toLowerCase())) { + const fieldLabel = translate(`workspace.netsuite.import.importCustomFields.customSegments.fields.segmentName`); + errors[INPUT_IDS.SEGMENT_NAME] = translate('workspace.netsuite.import.importCustomFields.customSegments.errors.uniqueFieldError', fieldLabel); + } + return errors; + case CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.INTERNAL_ID: + if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.INTERNAL_ID])) { + const fieldLabel = translate(`workspace.netsuite.import.importCustomFields.customSegments.fields.internalID`); + errors[INPUT_IDS.INTERNAL_ID] = translate('workspace.netsuite.import.importCustomFields.requiredFieldError', fieldLabel); + } else if (customSegments.find((customSegment) => customSegment.internalID.toLowerCase() === values[INPUT_IDS.INTERNAL_ID].toLowerCase())) { + const fieldLabel = translate(`workspace.netsuite.import.importCustomFields.customSegments.fields.internalID`); + errors[INPUT_IDS.INTERNAL_ID] = translate('workspace.netsuite.import.importCustomFields.customSegments.errors.uniqueFieldError', fieldLabel); + } + return errors; + case CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.SCRIPT_ID: + if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.SCRIPT_ID])) { + const fieldLabel = translate( + `workspace.netsuite.import.importCustomFields.customSegments.fields.${ + customSegmentRecordType === CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT ? 'scriptID' : 'customRecordScriptID' + }`, + ); + errors[INPUT_IDS.SCRIPT_ID] = translate('workspace.netsuite.import.importCustomFields.requiredFieldError', fieldLabel); + } else if (customSegments.find((customSegment) => customSegment.scriptID.toLowerCase() === values[INPUT_IDS.SCRIPT_ID].toLowerCase())) { + const fieldLabel = translate( + `workspace.netsuite.import.importCustomFields.customSegments.fields.${ + customSegmentRecordType === CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT ? 'scriptID' : 'customRecordScriptID' + }`, + ); + errors[INPUT_IDS.SCRIPT_ID] = translate('workspace.netsuite.import.importCustomFields.customSegments.errors.uniqueFieldError', fieldLabel); + } + return errors; + case CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.MAPPING: + return ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.MAPPING]); + default: + return errors; + } + }, + [customSegmentType, customSegments, screenIndex, translate], + ); + + const updateNetSuiteCustomSegments = useCallback( + (formValues: FormOnyxValues) => { + const updatedCustomSegments = customSegments.concat([ + { + segmentName: formValues[INPUT_IDS.SEGMENT_NAME], + internalID: formValues[INPUT_IDS.INTERNAL_ID], + scriptID: formValues[INPUT_IDS.SCRIPT_ID], + mapping: formValues[INPUT_IDS.MAPPING] ?? CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG, + }, + ]); + Connections.updateNetSuiteCustomSegments(policyID, updatedCustomSegments, customSegments); + nextScreen(); + }, + [customSegments, nextScreen, policyID], + ); + + const renderSubStepContent = useMemo( + () => ( + + ), + [SubStep, handleNextScreen, isEditing, moveTo, policy, policyID, screenIndex, customSegmentType], + ); + + const selectionListForm = [CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.MAPPING as number].includes(screenIndex); + const submitFlexAllowed = [ + CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.SEGMENT_NAME as number, + CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.INTERNAL_ID as number, + CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.SCRIPT_ID as number, + ].includes(screenIndex); + + return ( + + + + + + {screenIndex === CONST.NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES.CUSTOM_SEGMENTS.SEGMENT_TYPE ? ( + renderSubStepContent + ) : ( + + {renderSubStepContent} + + )} + + + ); +} + +NetSuiteImportAddCustomSegmentPage.displayName = 'NetSuiteImportAddCustomSegmentPage'; + +export default withPolicyConnections(NetSuiteImportAddCustomSegmentPage); diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm.tsx new file mode 100644 index 000000000000..684059a5b846 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import type {MenuItemProps} from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; + +type NetSuiteMenuWithTopDescriptionFormProps = MenuItemProps & { + /** The value of the menu item */ + value?: string; + + /** Callback to format the value */ + valueRenderer?: (value?: string) => string | undefined; +}; + +function NetSuiteMenuWithTopDescriptionForm({value, valueRenderer, ...props}: NetSuiteMenuWithTopDescriptionFormProps) { + return ( + + ); +} + +NetSuiteMenuWithTopDescriptionForm.displayName = 'NetSuiteMenuWithTopDescriptionForm'; +export default NetSuiteMenuWithTopDescriptionForm; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ChooseCustomListStep.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ChooseCustomListStep.tsx new file mode 100644 index 000000000000..473a01d5e7ce --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ChooseCustomListStep.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import InputWrapper from '@components/Form/InputWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import NetSuiteCustomListPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; + +function ChooseCustomListStep({policy}: CustomFieldSubStepWithPolicy) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + <> + {translate(`workspace.netsuite.import.importCustomFields.customLists.addForm.listNameTitle`)} + + + ); +} + +ChooseCustomListStep.displayName = 'ChooseCustomListStep'; +export default ChooseCustomListStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ChooseSegmentTypeStep.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ChooseSegmentTypeStep.tsx new file mode 100644 index 000000000000..93b0ed183b18 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ChooseSegmentTypeStep.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import CONST from '@src/CONST'; + +function ChooseSegmentTypeStep({onNext, customSegmentType, setCustomSegmentType}: CustomFieldSubStepWithPolicy) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const selectionData = [ + { + text: translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.segmentTitle`), + keyForList: CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT, + isSelected: customSegmentType === CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT, + value: CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT, + }, + { + text: translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.recordTitle`), + keyForList: CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_RECORD, + isSelected: customSegmentType === CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_RECORD, + value: CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_RECORD, + }, + ]; + + return ( + <> + + {translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.segmentRecordType`)} + + {translate(`workspace.netsuite.import.importCustomFields.chooseOptionBelow`)} + { + setCustomSegmentType?.(selected.value); + onNext(); + }} + /> + + ); +} + +ChooseSegmentTypeStep.displayName = 'ChooseSegmentTypeStep'; +export default ChooseSegmentTypeStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ConfirmCustomListStep.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ConfirmCustomListStep.tsx new file mode 100644 index 000000000000..e72aa8710753 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ConfirmCustomListStep.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import {View} from 'react-native'; +import InputWrapper from '@components/Form/InputWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import NetSuiteMenuWithTopDescriptionForm from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import type {TranslationPaths} from '@src/languages/types'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; + +function ConfirmCustomListStep({onMove}: CustomFieldSubStepWithPolicy) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const fieldNames = [INPUT_IDS.LIST_NAME, INPUT_IDS.TRANSACTION_FIELD_ID, INPUT_IDS.MAPPING]; + + return ( + + {translate('workspace.common.letsDoubleCheck')} + {fieldNames.map((fieldName, index) => ( + { + onMove(index); + }} + valueRenderer={(value) => (fieldName === INPUT_IDS.MAPPING && value ? translate(`workspace.netsuite.import.importTypes.${value}.label` as TranslationPaths) : value)} + /> + ))} + + ); +} + +ConfirmCustomListStep.displayName = 'ConfirmCustomListStep'; +export default ConfirmCustomListStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ConfirmCustomSegmentList.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ConfirmCustomSegmentList.tsx new file mode 100644 index 000000000000..bf1314491c03 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ConfirmCustomSegmentList.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import {View} from 'react-native'; +import InputWrapper from '@components/Form/InputWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import NetSuiteMenuWithTopDescriptionForm from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; + +function ConfirmCustomListStep({onMove, customSegmentType}: CustomFieldSubStepWithPolicy) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const fieldNames = [INPUT_IDS.SEGMENT_NAME, INPUT_IDS.INTERNAL_ID, INPUT_IDS.SCRIPT_ID, INPUT_IDS.MAPPING]; + + return ( + + {translate('workspace.common.letsDoubleCheck')} + {fieldNames.map((fieldName, index) => ( + { + onMove(index + 1); + }} + valueRenderer={(value) => (fieldName === INPUT_IDS.MAPPING && value ? translate(`workspace.netsuite.import.importTypes.${value}.label` as TranslationPaths) : value)} + /> + ))} + + ); +} + +ConfirmCustomListStep.displayName = 'ConfirmCustomListStep'; +export default ConfirmCustomListStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentInternalIdStep.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentInternalIdStep.tsx new file mode 100644 index 000000000000..366936138d42 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentInternalIdStep.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import {View} from 'react-native'; +import InputWrapper from '@components/Form/InputWrapper'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Parser from '@libs/Parser'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import CONST from '@src/CONST'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; + +function CustomSegmentInternalIdStep({customSegmentType}: CustomFieldSubStepWithPolicy) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const customSegmentRecordType = customSegmentType ?? CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT; + + const fieldLabel = translate(`workspace.netsuite.import.importCustomFields.customSegments.fields.internalID`); + + return ( + + + {translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.customSegmentInternalIDTitle`)} + + + + ${Parser.replace(translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.${customSegmentRecordType}InternalIDFooter`))}`} + /> + + + ); +} + +CustomSegmentInternalIdStep.displayName = 'CustomSegmentInternalIdStep'; +export default CustomSegmentInternalIdStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentNameStep.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentNameStep.tsx new file mode 100644 index 000000000000..52021ceedc67 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentNameStep.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import {View} from 'react-native'; +import InputWrapper from '@components/Form/InputWrapper'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Parser from '@libs/Parser'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import CONST from '@src/CONST'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; + +function CustomSegmentNameStep({customSegmentType}: CustomFieldSubStepWithPolicy) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const fieldLabel = translate(`workspace.netsuite.import.importCustomFields.customSegments.fields.segmentName`); + + const customSegmentRecordType = customSegmentType ?? CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT; + + return ( + + + {translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.${customSegmentRecordType}NameTitle`)} + + + + ${Parser.replace(translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.${customSegmentRecordType}NameFooter`))}`} + /> + + + ); +} + +CustomSegmentNameStep.displayName = 'CustomSegmentNameStep'; +export default CustomSegmentNameStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentScriptIdStep.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentScriptIdStep.tsx new file mode 100644 index 000000000000..20327db7776f --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentScriptIdStep.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import {View} from 'react-native'; +import InputWrapper from '@components/Form/InputWrapper'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Parser from '@libs/Parser'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import CONST from '@src/CONST'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; + +function CustomSegmentScriptIdStep({customSegmentType}: CustomFieldSubStepWithPolicy) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const customSegmentRecordType = customSegmentType ?? CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT; + + const fieldLabel = translate( + `workspace.netsuite.import.importCustomFields.customSegments.fields.${ + customSegmentRecordType === CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT ? 'scriptID' : 'customRecordScriptID' + }`, + ); + + return ( + + + {translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.${customSegmentRecordType}ScriptIDTitle`)} + + + + ${Parser.replace(translate(`workspace.netsuite.import.importCustomFields.customSegments.addForm.${customSegmentRecordType}ScriptIDFooter`))}`} + /> + + + ); +} + +CustomSegmentScriptIdStep.displayName = 'CustomSegmentScriptIdStep'; +export default CustomSegmentScriptIdStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/MappingStep.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/MappingStep.tsx new file mode 100644 index 000000000000..f41269e4954f --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/MappingStep.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import InputWrapper from '@components/Form/InputWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import NetSuiteCustomFieldMappingPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker'; +import type {CustomFieldSubStepWithPolicy} from '@pages/workspace/accounting/netsuite/types'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; + +function MappingStep({importCustomField, customSegmentType}: CustomFieldSubStepWithPolicy) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + let titleKey; + if (importCustomField === CONST.NETSUITE_CONFIG.IMPORT_CUSTOM_FIELDS.CUSTOM_LISTS) { + titleKey = 'workspace.netsuite.import.importCustomFields.customLists.addForm.mappingTitle'; + } else { + const customSegmentRecordType = customSegmentType ?? CONST.NETSUITE_CUSTOM_RECORD_TYPES.CUSTOM_SEGMENT; + titleKey = `workspace.netsuite.import.importCustomFields.customSegments.addForm.${customSegmentRecordType}MappingTitle`; + } + + return ( + <> + {translate(titleKey as TranslationPaths)} + {translate(`workspace.netsuite.import.importCustomFields.chooseOptionBelow`)} + + + ); +} + +MappingStep.displayName = 'MappingStep'; +export default MappingStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/TransactionFieldIDStep.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/TransactionFieldIDStep.tsx new file mode 100644 index 000000000000..a7f7132143f1 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/TransactionFieldIDStep.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import {View} from 'react-native'; +import InputWrapper from '@components/Form/InputWrapper'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Parser from '@libs/Parser'; +import CONST from '@src/CONST'; +import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; + +function TransactionFieldIDStep() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const fieldLabel = translate(`workspace.netsuite.import.importCustomFields.customLists.fields.transactionFieldID`); + + return ( + + {translate(`workspace.netsuite.import.importCustomFields.customLists.addForm.transactionFieldIDTitle`)} + + + ${Parser.replace(translate(`workspace.netsuite.import.importCustomFields.customLists.addForm.transactionFieldIDFooter`))}`} /> + + + ); +} + +TransactionFieldIDStep.displayName = 'TransactionFieldIDStep'; +export default TransactionFieldIDStep; diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage.tsx new file mode 100644 index 000000000000..f438a13e134a --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage.tsx @@ -0,0 +1,164 @@ +import React, {useMemo} from 'react'; +import type {StyleProp, TextStyle} from 'react-native'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import Button from '@components/Button'; +import ConnectionLayout from '@components/ConnectionLayout'; +import FixedFooter from '@components/FixedFooter'; +import * as Illustrations from '@components/Icon/Illustrations'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import type {ThemeStyles} from '@styles/index'; +import * as Policy from '@userActions/Policy/Policy'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +type ImportCustomFieldsKeys = ValueOf; + +type NetSuiteImportCustomFieldPageProps = WithPolicyConnectionsProps & { + route: { + params: { + /** Whether the record is of type custom segment or list */ + importCustomField: ImportCustomFieldsKeys; + }; + }; +}; + +type HelpLinkComponentProps = { + /** Whether the record is of type custom segment or list */ + importCustomField: ImportCustomFieldsKeys; + + /** Callback to localize content */ + translate: LocaleContextProps['translate']; + + /** Theme styles to apply to the component */ + styles: ThemeStyles; + + /** Text alignment style for the Text component */ + alignmentStyle: StyleProp; +}; + +function HelpLinkComponent({importCustomField, styles, translate, alignmentStyle}: HelpLinkComponentProps) { + return ( + + + {translate(`workspace.netsuite.import.importCustomFields.${importCustomField}.helpLinkText`)} + + {translate(`workspace.netsuite.import.importCustomFields.${importCustomField}.helpText`)} + + ); +} + +function NetSuiteImportCustomFieldPage({ + policy, + route: { + params: {importCustomField}, + }, +}: NetSuiteImportCustomFieldPageProps) { + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const config = policy?.connections?.netsuite?.options?.config; + const data = config?.syncOptions?.[importCustomField] ?? []; + + const listEmptyComponent = useMemo( + () => ( + + } + containerStyle={[styles.flex1, styles.justifyContentCenter]} + /> + ), + [importCustomField, styles, translate], + ); + + const listHeaderComponent = useMemo( + () => ( + + + + ), + [styles, importCustomField, translate], + ); + + return ( + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT.getRoute(policyID))} + > + {data.length === 0 ? listEmptyComponent : listHeaderComponent} + Policy.clearNetSuiteErrorField(policyID, importCustomField)} + > + {data.map((record, index) => ( + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW.getRoute(policyID, importCustomField, index))} + /> + ))} + + + +