diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 906f8ef7095ea..44f3fcb55520c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -460,8 +460,8 @@ const ONYXKEYS = { WORKSPACE_RATE_AND_UNIT_FORM_DRAFT: 'workspaceRateAndUnitFormDraft', WORKSPACE_TAX_CUSTOM_NAME: 'workspaceTaxCustomName', WORKSPACE_TAX_CUSTOM_NAME_DRAFT: 'workspaceTaxCustomNameDraft', - WORKSPACE_REPORT_FIELDS_FORM: 'workspaceReportFieldsForm', - WORKSPACE_REPORT_FIELDS_FORM_DRAFT: 'workspaceReportFieldsFormDraft', + WORKSPACE_REPORT_FIELDS_FORM: 'workspaceReportFieldForm', + WORKSPACE_REPORT_FIELDS_FORM_DRAFT: 'workspaceReportFieldFormDraft', POLICY_CREATE_DISTANCE_RATE_FORM: 'policyCreateDistanceRateForm', POLICY_CREATE_DISTANCE_RATE_FORM_DRAFT: 'policyCreateDistanceRateFormDraft', POLICY_DISTANCE_RATE_EDIT_FORM: 'policyDistanceRateEditForm', @@ -574,7 +574,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_TAG_FORM]: FormTypes.WorkspaceTagForm; [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: FormTypes.WorkspaceRateAndUnitForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_CUSTOM_NAME]: FormTypes.WorkspaceTaxCustomName; - [ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM]: FormTypes.WorkspaceReportFieldsForm; + [ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM]: FormTypes.WorkspaceReportFieldForm; [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: FormTypes.CloseAccountForm; [ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: FormTypes.ProfileSettingsForm; [ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: FormTypes.DisplayNameForm; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2347bd4f93f42..324fa7c9a5d45 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -787,29 +787,34 @@ const ROUTES = { route: 'settings/workspaces/:policyID/reportFields', getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields` as const, }, - WORKSPACE_REPORT_FIELD_SETTINGS: { - route: 'settings/workspaces/:policyID/reportField/:reportFieldKey', - getRoute: (policyID: string, reportFieldKey: string) => `settings/workspaces/${policyID}/reportField/${encodeURIComponent(reportFieldKey)}` as const, - }, WORKSPACE_CREATE_REPORT_FIELD: { route: 'settings/workspaces/:policyID/reportFields/new', getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields/new` as const, }, + WORKSPACE_REPORT_FIELD_SETTINGS: { + route: 'settings/workspaces/:policyID/reportField/:reportFieldID/edit', + getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportField/${encodeURIComponent(reportFieldID)}/edit` as const, + }, WORKSPACE_REPORT_FIELD_LIST_VALUES: { - route: 'settings/workspaces/:policyID/reportFields/new/listValues', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields/new/listValues` as const, + route: 'settings/workspaces/:policyID/reportField/listValues/:reportFieldID?', + getRoute: (policyID: string, reportFieldID?: string) => `settings/workspaces/${policyID}/reportField/listValues/${encodeURIComponent(reportFieldID ?? '')}` as const, }, WORKSPACE_REPORT_FIELD_ADD_VALUE: { - route: 'settings/workspaces/:policyID/reportFields/new/addValue', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields/new/addValue` as const, + route: 'settings/workspaces/:policyID/reportField/addValue/:reportFieldID?', + getRoute: (policyID: string, reportFieldID?: string) => `settings/workspaces/${policyID}/reportField/addValue/${encodeURIComponent(reportFieldID ?? '')}` as const, }, WORKSPACE_REPORT_FIELD_VALUE_SETTINGS: { - route: 'settings/workspaces/:policyID/reportFields/new/:valueIndex', - getRoute: (policyID: string, valueIndex: number) => `settings/workspaces/${policyID}/reportFields/new/${valueIndex}` as const, + route: 'settings/workspaces/:policyID/reportField/:valueIndex/:reportFieldID?', + getRoute: (policyID: string, valueIndex: number, reportFieldID?: string) => + `settings/workspaces/${policyID}/reportField/${valueIndex}/${encodeURIComponent(reportFieldID ?? '')}` as const, }, WORKSPACE_REPORT_FIELD_EDIT_VALUE: { - route: 'settings/workspaces/:policyID/reportFields/new/:valueIndex/edit', - getRoute: (policyID: string, valueIndex: number) => `settings/workspaces/${policyID}/reportFields/new/${valueIndex}/edit` as const, + route: 'settings/workspaces/:policyID/reportField/new/:valueIndex/edit', + getRoute: (policyID: string, valueIndex: number) => `settings/workspaces/${policyID}/reportField/new/${valueIndex}/edit` as const, + }, + WORKSPACE_EDIT_REPORT_FIELD_INITIAL_VALUE: { + route: 'settings/workspaces/:policyID/reportField/:reportFieldID/edit/initialValue', + getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportField/${encodeURIComponent(reportFieldID)}/edit/initialValue` as const, }, WORKSPACE_EXPENSIFY_CARD: { route: 'settings/workspaces/:policyID/expensify-card', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 8d077d635fcc7..a3395f586dd1d 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -331,6 +331,7 @@ const SCREENS = { REPORT_FIELDS_ADD_VALUE: 'Workspace_ReportFields_AddValue', REPORT_FIELDS_VALUE_SETTINGS: 'Workspace_ReportFields_ValueSettings', REPORT_FIELDS_EDIT_VALUE: 'Workspace_ReportFields_EditValue', + REPORT_FIELDS_EDIT_INITIAL_VALUE: 'Workspace_ReportFields_EditInitialValue', TAX_EDIT: 'Workspace_Tax_Edit', TAX_NAME: 'Workspace_Tax_Name', TAX_VALUE: 'Workspace_Tax_Value', diff --git a/src/libs/API/parameters/CreateWorkspaceReportFieldListValueParams.ts b/src/libs/API/parameters/CreateWorkspaceReportFieldListValueParams.ts new file mode 100644 index 0000000000000..950287bc5d04d --- /dev/null +++ b/src/libs/API/parameters/CreateWorkspaceReportFieldListValueParams.ts @@ -0,0 +1,10 @@ +type CreateWorkspaceReportFieldListValueParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array + */ + reportFields: string; +}; + +export default CreateWorkspaceReportFieldListValueParams; diff --git a/src/libs/API/parameters/CreateWorkspaceReportFieldParams.ts b/src/libs/API/parameters/CreateWorkspaceReportFieldParams.ts index 13844a279905e..33692d2109599 100644 --- a/src/libs/API/parameters/CreateWorkspaceReportFieldParams.ts +++ b/src/libs/API/parameters/CreateWorkspaceReportFieldParams.ts @@ -1,5 +1,9 @@ type CreateWorkspaceReportFieldParams = { policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array + */ reportFields: string; }; diff --git a/src/libs/API/parameters/EnableWorkspaceReportFieldListValueParams.ts b/src/libs/API/parameters/EnableWorkspaceReportFieldListValueParams.ts new file mode 100644 index 0000000000000..7c54a2f4c68b8 --- /dev/null +++ b/src/libs/API/parameters/EnableWorkspaceReportFieldListValueParams.ts @@ -0,0 +1,10 @@ +type EnableWorkspaceReportFieldListValueParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array + */ + reportFields: string; +}; + +export default EnableWorkspaceReportFieldListValueParams; diff --git a/src/libs/API/parameters/RemoveWorkspaceReportFieldListValueParams.ts b/src/libs/API/parameters/RemoveWorkspaceReportFieldListValueParams.ts new file mode 100644 index 0000000000000..94d90a8dbaaec --- /dev/null +++ b/src/libs/API/parameters/RemoveWorkspaceReportFieldListValueParams.ts @@ -0,0 +1,10 @@ +type RemoveWorkspaceReportFieldListValueParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array + */ + reportFields: string; +}; + +export default RemoveWorkspaceReportFieldListValueParams; diff --git a/src/libs/API/parameters/UpdateWorkspaceReportFieldInitialValueParams.ts b/src/libs/API/parameters/UpdateWorkspaceReportFieldInitialValueParams.ts new file mode 100644 index 0000000000000..a72781ff1c37c --- /dev/null +++ b/src/libs/API/parameters/UpdateWorkspaceReportFieldInitialValueParams.ts @@ -0,0 +1,10 @@ +type UpdateWorkspaceReportFieldInitialValueParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array + */ + reportFields: string; +}; + +export default UpdateWorkspaceReportFieldInitialValueParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 096e59f399f62..45b4d925c3f8f 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -245,6 +245,10 @@ export type {default as UpdateNetSuiteSubsidiaryParams} from './UpdateNetSuiteSu export type {default as PolicyReportFieldsReplace} from './PolicyReportFieldsReplace'; export type {default as ConnectPolicyToNetSuiteParams} from './ConnectPolicyToNetSuiteParams'; export type {default as CreateWorkspaceReportFieldParams} from './CreateWorkspaceReportFieldParams'; +export type {default as UpdateWorkspaceReportFieldInitialValueParams} from './UpdateWorkspaceReportFieldInitialValueParams'; +export type {default as EnableWorkspaceReportFieldListValueParams} from './EnableWorkspaceReportFieldListValueParams'; +export type {default as CreateWorkspaceReportFieldListValueParams} from './CreateWorkspaceReportFieldListValueParams'; +export type {default as RemoveWorkspaceReportFieldListValueParams} from './RemoveWorkspaceReportFieldListValueParams'; export type {default as OpenPolicyExpensifyCardsPageParams} from './OpenPolicyExpensifyCardsPageParams'; export type {default as RequestExpensifyCardLimitIncreaseParams} from './RequestExpensifyCardLimitIncreaseParams'; export type {default as UpdateNetSuiteGenericTypeParams} from './UpdateNetSuiteGenericTypeParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a1a5b91d7d99f..50c3dad8c3b51 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -238,6 +238,10 @@ const WRITE_COMMANDS = { REQUEST_REFUND: 'User_RefundPurchase', UPDATE_NETSUITE_SUBSIDIARY: 'UpdateNetSuiteSubsidiary', CREATE_WORKSPACE_REPORT_FIELD: 'CreatePolicyReportField', + UPDATE_WORKSPACE_REPORT_FIELD_INITIAL_VALUE: 'SetPolicyReportFieldDefault', + ENABLE_WORKSPACE_REPORT_FIELD_LIST_VALUE: 'EnablePolicyReportFieldOption', + CREATE_WORKSPACE_REPORT_FIELD_LIST_VALUE: 'CreatePolicyReportFieldOption', + REMOVE_WORKSPACE_REPORT_FIELD_LIST_VALUE: 'RemovePolicyReportFieldOption', UPDATE_NETSUITE_SYNC_TAX_CONFIGURATION: 'UpdateNetSuiteSyncTaxConfiguration', UPDATE_NETSUITE_CROSS_SUBSIDIARY_CUSTOMER_CONFIGURATION: 'UpdateNetSuiteCrossSubsidiaryCustomerConfiguration', UPDATE_NETSUITE_DEPARTMENTS_MAPPING: 'UpdateNetSuiteDepartmentsMapping', @@ -517,6 +521,10 @@ type WriteCommandParameters = { // Workspace report field parameters [WRITE_COMMANDS.CREATE_WORKSPACE_REPORT_FIELD]: Parameters.CreateWorkspaceReportFieldParams; + [WRITE_COMMANDS.UPDATE_WORKSPACE_REPORT_FIELD_INITIAL_VALUE]: Parameters.UpdateWorkspaceReportFieldInitialValueParams; + [WRITE_COMMANDS.ENABLE_WORKSPACE_REPORT_FIELD_LIST_VALUE]: Parameters.EnableWorkspaceReportFieldListValueParams; + [WRITE_COMMANDS.CREATE_WORKSPACE_REPORT_FIELD_LIST_VALUE]: Parameters.CreateWorkspaceReportFieldListValueParams; + [WRITE_COMMANDS.REMOVE_WORKSPACE_REPORT_FIELD_LIST_VALUE]: Parameters.RemoveWorkspaceReportFieldListValueParams; [WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_TAX_CONFIGURATION]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_CROSS_SUBSIDIARY_CUSTOMER_CONFIGURATION]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 0b001d747e5f8..d13fd35fb585b 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -250,7 +250,6 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/tags/WorkspaceEditTagsPage').default, [SCREENS.WORKSPACE.TAG_CREATE]: () => require('../../../../pages/workspace/tags/WorkspaceCreateTagPage').default, [SCREENS.WORKSPACE.TAG_EDIT]: () => require('../../../../pages/workspace/tags/EditTagPage').default, - [SCREENS.WORKSPACE.REPORT_FIELD_SETTINGS]: () => require('../../../../pages/workspace/reportFields/WorkspaceReportFieldSettingsPage').default, [SCREENS.WORKSPACE.TAXES_SETTINGS]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesSettingsPage').default, [SCREENS.WORKSPACE.TAXES_SETTINGS_CUSTOM_TAX_NAME]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesSettingsCustomTaxName').default, [SCREENS.WORKSPACE.TAXES_SETTINGS_FOREIGN_CURRENCY_DEFAULT]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesSettingsForeignCurrency').default, @@ -371,10 +370,12 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Subscription/PaymentCard/ChangeBillingCurrency').default, [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD]: () => require('../../../../pages/settings/Subscription/PaymentCard').default, [SCREENS.SETTINGS.ADD_PAYMENT_CARD_CHANGE_CURRENCY]: () => require('../../../../pages/settings/PaymentCard/ChangeCurrency').default, - [SCREENS.WORKSPACE.REPORT_FIELDS_CREATE]: () => require('../../../../pages/workspace/reportFields/WorkspaceCreateReportFieldPage').default, + [SCREENS.WORKSPACE.REPORT_FIELDS_CREATE]: () => require('../../../../pages/workspace/reportFields/CreateReportFieldPage').default, + [SCREENS.WORKSPACE.REPORT_FIELD_SETTINGS]: () => require('../../../../pages/workspace/reportFields/ReportFieldSettingsPage').default, [SCREENS.WORKSPACE.REPORT_FIELDS_LIST_VALUES]: () => require('../../../../pages/workspace/reportFields/ReportFieldListValuesPage').default, [SCREENS.WORKSPACE.REPORT_FIELDS_ADD_VALUE]: () => require('../../../../pages/workspace/reportFields/ReportFieldAddListValuePage').default, - [SCREENS.WORKSPACE.REPORT_FIELDS_VALUE_SETTINGS]: () => require('../../../../pages/workspace/reportFields/ValueSettingsPage').default, + [SCREENS.WORKSPACE.REPORT_FIELDS_VALUE_SETTINGS]: () => require('../../../../pages/workspace/reportFields/ReportFieldValueSettingsPage').default, + [SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE]: () => require('../../../../pages/workspace/reportFields/ReportFieldInitialValuePage').default, [SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_VALUE]: () => require('../../../../pages/workspace/reportFields/ReportFieldEditValuePage').default, }); 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 920e25e7b0be7..d59be78c31d0f 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -115,12 +115,13 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS, ], [SCREENS.WORKSPACE.REPORT_FIELDS]: [ - SCREENS.WORKSPACE.REPORT_FIELD_SETTINGS, SCREENS.WORKSPACE.REPORT_FIELDS_CREATE, + SCREENS.WORKSPACE.REPORT_FIELD_SETTINGS, SCREENS.WORKSPACE.REPORT_FIELDS_LIST_VALUES, SCREENS.WORKSPACE.REPORT_FIELDS_ADD_VALUE, SCREENS.WORKSPACE.REPORT_FIELDS_VALUE_SETTINGS, SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_VALUE, + SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE, ], [SCREENS.WORKSPACE.EXPENSIFY_CARD]: [], }; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 6f24aaf82048f..c19788764c294 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -526,12 +526,6 @@ const config: LinkingOptions['config'] = { orderWeight: Number, }, }, - [SCREENS.WORKSPACE.REPORT_FIELD_SETTINGS]: { - path: ROUTES.WORKSPACE_REPORT_FIELD_SETTINGS.route, - parse: { - reportFieldName: (reportFieldKey: string) => decodeURIComponent(reportFieldKey), - }, - }, [SCREENS.WORKSPACE.TAXES_SETTINGS]: { path: ROUTES.WORKSPACE_TAXES_SETTINGS.route, }, @@ -549,16 +543,37 @@ const config: LinkingOptions['config'] = { }, [SCREENS.WORKSPACE.REPORT_FIELDS_LIST_VALUES]: { path: ROUTES.WORKSPACE_REPORT_FIELD_LIST_VALUES.route, + parse: { + reportFieldID: (reportFieldID: string) => decodeURIComponent(reportFieldID), + }, }, [SCREENS.WORKSPACE.REPORT_FIELDS_ADD_VALUE]: { path: ROUTES.WORKSPACE_REPORT_FIELD_ADD_VALUE.route, + parse: { + reportFieldID: (reportFieldID: string) => decodeURIComponent(reportFieldID), + }, }, [SCREENS.WORKSPACE.REPORT_FIELDS_VALUE_SETTINGS]: { path: ROUTES.WORKSPACE_REPORT_FIELD_VALUE_SETTINGS.route, + parse: { + reportFieldID: (reportFieldID: string) => decodeURIComponent(reportFieldID), + }, }, [SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_VALUE]: { path: ROUTES.WORKSPACE_REPORT_FIELD_EDIT_VALUE.route, }, + [SCREENS.WORKSPACE.REPORT_FIELD_SETTINGS]: { + path: ROUTES.WORKSPACE_REPORT_FIELD_SETTINGS.route, + parse: { + reportFieldID: (reportFieldID: string) => decodeURIComponent(reportFieldID), + }, + }, + [SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE]: { + path: ROUTES.WORKSPACE_EDIT_REPORT_FIELD_INITIAL_VALUE.route, + parse: { + reportFieldID: (reportFieldID: string) => decodeURIComponent(reportFieldID), + }, + }, [SCREENS.REIMBURSEMENT_ACCOUNT]: { path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index feb822e1e97c0..77e78adfa0cf5 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -241,10 +241,6 @@ type SettingsNavigatorParamList = { orderWeight: number; tagName: string; }; - [SCREENS.WORKSPACE.REPORT_FIELD_SETTINGS]: { - policyID: string; - reportFieldKey: string; - }; [SCREENS.WORKSPACE.TAG_LIST_VIEW]: { policyID: string; orderWeight: number; @@ -282,18 +278,29 @@ type SettingsNavigatorParamList = { }; [SCREENS.WORKSPACE.REPORT_FIELDS_LIST_VALUES]: { policyID: string; + reportFieldID?: string; }; [SCREENS.WORKSPACE.REPORT_FIELDS_ADD_VALUE]: { policyID: string; + reportFieldID?: string; }; [SCREENS.WORKSPACE.REPORT_FIELDS_VALUE_SETTINGS]: { policyID: string; valueIndex: number; + reportFieldID?: string; }; [SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_VALUE]: { policyID: string; valueIndex: number; }; + [SCREENS.WORKSPACE.REPORT_FIELD_SETTINGS]: { + policyID: string; + reportFieldID: string; + }; + [SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE]: { + policyID: string; + reportFieldID: string; + }; [SCREENS.WORKSPACE.MEMBER_DETAILS]: { policyID: string; accountID: string; diff --git a/src/libs/WorkspaceReportFieldsUtils.ts b/src/libs/WorkspaceReportFieldUtils.ts similarity index 83% rename from src/libs/WorkspaceReportFieldsUtils.ts rename to src/libs/WorkspaceReportFieldUtils.ts index 0cc3cae24a238..b7d93b8dee3a7 100644 --- a/src/libs/WorkspaceReportFieldsUtils.ts +++ b/src/libs/WorkspaceReportFieldUtils.ts @@ -2,8 +2,8 @@ import type {FormInputErrors} from '@components/Form/types'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type ONYXKEYS from '@src/ONYXKEYS'; -import type {InputID} from '@src/types/form/WorkspaceReportFieldsForm'; -import type {PolicyReportFieldType} from '@src/types/onyx/Policy'; +import type {InputID} from '@src/types/form/WorkspaceReportFieldForm'; +import type {PolicyReportField, PolicyReportFieldType} from '@src/types/onyx/Policy'; import * as ErrorUtils from './ErrorUtils'; import * as Localize from './Localize'; import * as ValidationUtils from './ValidationUtils'; @@ -67,4 +67,23 @@ function generateFieldID(name: string) { return `field_id_${name.replace(CONST.REGEX.ANY_SPACE, '_').toUpperCase()}`; } -export {getReportFieldTypeTranslationKey, getReportFieldAlternativeTextTranslationKey, validateReportFieldListValueName, generateFieldID}; +/** + * Gets the initial value for a report field. + */ +function getReportFieldInitialValue(reportField: PolicyReportField | null): string { + if (!reportField) { + return ''; + } + + if (reportField.type === CONST.REPORT_FIELD_TYPES.LIST) { + return reportField.defaultValue ?? ''; + } + + if (reportField.type === CONST.REPORT_FIELD_TYPES.DATE) { + return Localize.translateLocal('common.currentDate'); + } + + return reportField.value ?? reportField.defaultValue; +} + +export {getReportFieldTypeTranslationKey, getReportFieldAlternativeTextTranslationKey, validateReportFieldListValueName, generateFieldID, getReportFieldInitialValue}; diff --git a/src/libs/actions/Policy/ReportField.ts b/src/libs/actions/Policy/ReportField.ts index 4a6c5ed4fad56..31af3ff53c4ce 100644 --- a/src/libs/actions/Policy/ReportField.ts +++ b/src/libs/actions/Policy/ReportField.ts @@ -1,15 +1,23 @@ +import cloneDeep from 'lodash/cloneDeep'; import type {NullishDeep, OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {CreateWorkspaceReportFieldParams, PolicyReportFieldsReplace} from '@libs/API/parameters'; +import type { + CreateWorkspaceReportFieldListValueParams, + CreateWorkspaceReportFieldParams, + EnableWorkspaceReportFieldListValueParams, + PolicyReportFieldsReplace, + RemoveWorkspaceReportFieldListValueParams, + UpdateWorkspaceReportFieldInitialValueParams, +} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import {generateFieldID} from '@libs/WorkspaceReportFieldsUtils'; +import * as WorkspaceReportFieldUtils from '@libs/WorkspaceReportFieldUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {WorkspaceReportFieldsForm} from '@src/types/form/WorkspaceReportFieldsForm'; -import INPUT_IDS from '@src/types/form/WorkspaceReportFieldsForm'; +import type {WorkspaceReportFieldForm} from '@src/types/form/WorkspaceReportFieldForm'; +import INPUT_IDS from '@src/types/form/WorkspaceReportFieldForm'; import type {Policy, PolicyReportField, Report} from '@src/types/onyx'; import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon'; import type {OnyxData} from '@src/types/onyx/Request'; @@ -127,16 +135,16 @@ function deleteReportFieldsListValue(valueIndexes: number[]) { }); } -type CreateReportFieldArguments = Pick; +type CreateReportFieldArguments = Pick; /** * Creates a new report field. */ function createReportField(policyID: string, {name, type, initialValue}: CreateReportFieldArguments) { const previousFieldList = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.fieldList ?? {}; - const fieldID = generateFieldID(name); + const fieldID = WorkspaceReportFieldUtils.generateFieldID(name); const fieldKey = ReportUtils.getReportFieldKey(fieldID); - const newReportField: OnyxValueWithOfflineFeedback = { + const newReportField: PolicyReportField = { name, type, defaultValue: initialValue, @@ -149,7 +157,6 @@ function createReportField(policyID: string, {name, type, initialValue}: CreateR keys: [], externalIDs: [], isTax: false, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }; const onyxData: OnyxData = { optimisticData: [ @@ -158,7 +165,7 @@ function createReportField(policyID: string, {name, type, initialValue}: CreateR onyxMethod: Onyx.METHOD.MERGE, value: { fieldList: { - [fieldKey]: newReportField, + [fieldKey]: {...newReportField, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, }, errorFields: null, }, @@ -261,14 +268,278 @@ function deleteReportFields(policyID: string, reportFieldsToUpdate: string[]) { API.write(WRITE_COMMANDS.POLICY_REPORT_FIELDS_REPLACE, parameters, onyxData); } +/** + * Updates the initial value of a report field. + */ +function updateReportFieldInitialValue(policyID: string, reportFieldID: string, newInitialValue: string) { + const previousFieldList = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.fieldList ?? {}; + const fieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const updatedReportField: PolicyReportField = { + ...previousFieldList[fieldKey], + defaultValue: newInitialValue, + }; + const onyxData: OnyxData = { + optimisticData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [fieldKey]: {...updatedReportField, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + }, + errorFields: null, + }, + }, + ], + successData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [fieldKey]: {pendingAction: null}, + }, + errorFields: null, + }, + }, + ], + failureData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [fieldKey]: {...previousFieldList[fieldKey], pendingAction: null}, + }, + errorFields: { + [fieldKey]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.reportFields.genericFailureMessage'), + }, + }, + }, + ], + }; + const parameters: UpdateWorkspaceReportFieldInitialValueParams = { + policyID, + reportFields: JSON.stringify([updatedReportField]), + }; + + API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_REPORT_FIELD_INITIAL_VALUE, parameters, onyxData); +} + +function updateReportFieldListValueEnabled(policyID: string, reportFieldID: string, valueIndexes: number[], enabled: boolean) { + const previousFieldList = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.fieldList ?? {}; + const fieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField = previousFieldList[fieldKey]; + + const updatedReportField = cloneDeep(reportField); + + valueIndexes.forEach((valueIndex) => { + updatedReportField.disabledOptions[valueIndex] = !enabled; + const shouldResetDefaultValue = !enabled && reportField.defaultValue === reportField.values[valueIndex]; + + if (shouldResetDefaultValue) { + updatedReportField.defaultValue = ''; + } + }); + + const onyxData: OnyxData = { + optimisticData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [fieldKey]: {...updatedReportField, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + }, + errorFields: null, + }, + }, + ], + successData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [fieldKey]: {pendingAction: null}, + }, + errorFields: null, + }, + }, + ], + failureData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [fieldKey]: {...reportField, pendingAction: null}, + }, + errorFields: { + [fieldKey]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.reportFields.genericFailureMessage'), + }, + }, + }, + ], + }; + const parameters: EnableWorkspaceReportFieldListValueParams = { + policyID, + reportFields: JSON.stringify([updatedReportField]), + }; + + API.write(WRITE_COMMANDS.ENABLE_WORKSPACE_REPORT_FIELD_LIST_VALUE, parameters, onyxData); +} + +/** + * Adds a new option to the list type report field on a workspace. + */ +function addReportFieldListValue(policyID: string, reportFieldID: string, valueName: string) { + const previousFieldList = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.fieldList ?? {}; + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField = previousFieldList[reportFieldKey]; + const updatedReportField = cloneDeep(reportField); + + updatedReportField.values.push(valueName); + updatedReportField.disabledOptions.push(false); + + const onyxData: OnyxData = { + optimisticData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [reportFieldKey]: { + ...updatedReportField, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + errorFields: null, + }, + }, + ], + successData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [reportFieldKey]: {pendingAction: null}, + }, + errorFields: null, + }, + }, + ], + failureData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [reportFieldKey]: {...reportField, pendingAction: null}, + }, + errorFields: { + [reportFieldKey]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.reportFields.genericFailureMessage'), + }, + }, + }, + ], + }; + + const parameters: CreateWorkspaceReportFieldListValueParams = { + policyID, + reportFields: JSON.stringify([updatedReportField]), + }; + + API.write(WRITE_COMMANDS.CREATE_WORKSPACE_REPORT_FIELD_LIST_VALUE, parameters, onyxData); +} + +/** + * Removes a list value from the workspace report fields. + */ +function removeReportFieldListValue(policyID: string, reportFieldID: string, valueIndexes: number[]) { + const previousFieldList = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.fieldList ?? {}; + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField = previousFieldList[reportFieldKey]; + const updatedReportField = cloneDeep(reportField); + + valueIndexes + .sort((a, b) => b - a) + .forEach((valueIndex) => { + const shouldResetDefaultValue = reportField.defaultValue === reportField.values[valueIndex]; + + if (shouldResetDefaultValue) { + updatedReportField.defaultValue = ''; + } + + updatedReportField.values.splice(valueIndex, 1); + updatedReportField.disabledOptions.splice(valueIndex, 1); + }); + + const onyxData: OnyxData = { + optimisticData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [reportFieldKey]: { + ...updatedReportField, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + errorFields: null, + }, + }, + ], + successData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [reportFieldKey]: {pendingAction: null}, + }, + errorFields: null, + }, + }, + ], + failureData: [ + { + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + onyxMethod: Onyx.METHOD.MERGE, + value: { + fieldList: { + [reportFieldKey]: {...reportField, pendingAction: null}, + }, + errorFields: { + [reportFieldKey]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.reportFields.genericFailureMessage'), + }, + }, + }, + ], + }; + + const parameters: RemoveWorkspaceReportFieldListValueParams = { + policyID, + reportFields: JSON.stringify([updatedReportField]), + }; + + API.write(WRITE_COMMANDS.REMOVE_WORKSPACE_REPORT_FIELD_LIST_VALUE, parameters, onyxData); +} + export type {CreateReportFieldArguments}; export { - deleteReportFields, setInitialCreateReportFieldsForm, createReportFieldsListValue, renameReportFieldsListValue, setReportFieldsListValueEnabled, deleteReportFieldsListValue, createReportField, + deleteReportFields, + updateReportFieldInitialValue, + updateReportFieldListValueEnabled, + addReportFieldListValue, + removeReportFieldListValue, }; diff --git a/src/pages/workspace/reportFields/WorkspaceCreateReportFieldPage.tsx b/src/pages/workspace/reportFields/CreateReportFieldPage.tsx similarity index 94% rename from src/pages/workspace/reportFields/WorkspaceCreateReportFieldPage.tsx rename to src/pages/workspace/reportFields/CreateReportFieldPage.tsx index eebbc2cd7bdbf..aaf3877f94902 100644 --- a/src/pages/workspace/reportFields/WorkspaceCreateReportFieldPage.tsx +++ b/src/pages/workspace/reportFields/CreateReportFieldPage.tsx @@ -24,20 +24,20 @@ import * as ValidationUtils from '@src/libs/ValidationUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import INPUT_IDS from '@src/types/form/WorkspaceReportFieldsForm'; +import INPUT_IDS from '@src/types/form/WorkspaceReportFieldForm'; import InitialListValueSelector from './InitialListValueSelector'; import TypeSelector from './TypeSelector'; -type WorkspaceCreateReportFieldPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; +type CreateReportFieldPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; const defaultDate = DateUtils.extractDate(new Date().toString()); -function WorkspaceCreateReportFieldPage({ +function CreateReportFieldPage({ policy, route: { params: {policyID}, }, -}: WorkspaceCreateReportFieldPageProps) { +}: CreateReportFieldPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const formRef = useRef(null); @@ -101,7 +101,7 @@ function WorkspaceCreateReportFieldPage({ - Object.values(formDraft?.listValues ?? {}) - .filter((listValue, index) => !formDraft?.disabledListValues?.[index]) - .map((listValue) => ({ - keyForList: listValue, - value: listValue, - isSelected: currentValue === listValue, - text: listValue, - })), - [currentValue, formDraft?.disabledListValues, formDraft?.listValues], - ); - return ( {subtitle} - - onValueSelected(item.value)} - initiallyFocusedOptionKey={listValueOptions.find((listValue) => listValue.isSelected)?.keyForList} + diff --git a/src/pages/workspace/reportFields/InitialListValueSelector/ReportFieldsInitialListValuePicker.tsx b/src/pages/workspace/reportFields/InitialListValueSelector/ReportFieldsInitialListValuePicker.tsx new file mode 100644 index 0000000000000..18f9f9d652592 --- /dev/null +++ b/src/pages/workspace/reportFields/InitialListValueSelector/ReportFieldsInitialListValuePicker.tsx @@ -0,0 +1,48 @@ +import React, {useMemo} from 'react'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; + +type ReportFieldsInitialListValuePickerProps = { + /** Options to select from if field is of type list */ + listValues: string[]; + + /** Collection of flags that state whether list field options are disabled */ + disabledOptions: boolean[]; + + /** Selected value */ + value: string; + + /** Function to call when the user selects a value */ + onValueChange: (value: string) => void; +}; + +function ReportFieldsInitialListValuePicker({listValues, disabledOptions, value, onValueChange}: ReportFieldsInitialListValuePickerProps) { + const listValueSections = useMemo( + () => [ + { + data: Object.values(listValues ?? {}) + .filter((listValue, index) => !disabledOptions[index]) + .map((listValue) => ({ + keyForList: listValue, + value: listValue, + isSelected: value === listValue, + text: listValue, + })), + }, + ], + [value, listValues, disabledOptions], + ); + + return ( + onValueChange(item.value)} + initiallyFocusedOptionKey={listValueSections[0].data.find((listValue) => listValue.isSelected)?.keyForList} + /> + ); +} + +ReportFieldsInitialListValuePicker.displayName = 'ReportFieldsInitialListValuePicker'; + +export default ReportFieldsInitialListValuePicker; diff --git a/src/pages/workspace/reportFields/ReportFieldAddListValuePage.tsx b/src/pages/workspace/reportFields/ReportFieldAddListValuePage.tsx index d344975e1bbcc..576dd1a6f6b0e 100644 --- a/src/pages/workspace/reportFields/ReportFieldAddListValuePage.tsx +++ b/src/pages/workspace/reportFields/ReportFieldAddListValuePage.tsx @@ -13,17 +13,21 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportField from '@libs/actions/Policy/ReportField'; import Navigation from '@libs/Navigation/Navigation'; -import {validateReportFieldListValueName} from '@libs/WorkspaceReportFieldsUtils'; +import * as WorkspaceReportFieldUtils from '@libs/WorkspaceReportFieldUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import INPUT_IDS from '@src/types/form/WorkspaceReportFieldsForm'; +import INPUT_IDS from '@src/types/form/WorkspaceReportFieldForm'; type ReportFieldAddListValuePageProps = StackScreenProps; -function ReportFieldAddListValuePage({route}: ReportFieldAddListValuePageProps) { +function ReportFieldAddListValuePage({ + route: { + params: {policyID, reportFieldID}, + }, +}: ReportFieldAddListValuePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); @@ -31,20 +35,27 @@ function ReportFieldAddListValuePage({route}: ReportFieldAddListValuePageProps) const validate = useCallback( (values: FormOnyxValues) => - validateReportFieldListValueName(values[INPUT_IDS.VALUE_NAME].trim(), '', formDraft?.[INPUT_IDS.LIST_VALUES] ?? [], INPUT_IDS.VALUE_NAME), + WorkspaceReportFieldUtils.validateReportFieldListValueName(values[INPUT_IDS.VALUE_NAME].trim(), '', formDraft?.[INPUT_IDS.LIST_VALUES] ?? [], INPUT_IDS.VALUE_NAME), [formDraft], ); - const createValue = useCallback((values: FormOnyxValues) => { - ReportField.createReportFieldsListValue(values[INPUT_IDS.VALUE_NAME]); - Keyboard.dismiss(); - Navigation.goBack(); - }, []); + const createValue = useCallback( + (values: FormOnyxValues) => { + if (reportFieldID) { + ReportField.addReportFieldListValue(policyID, reportFieldID, values[INPUT_IDS.VALUE_NAME]); + } else { + ReportField.createReportFieldsListValue(values[INPUT_IDS.VALUE_NAME]); + } + Keyboard.dismiss(); + Navigation.goBack(); + }, + [policyID, reportFieldID], + ); return ( ; @@ -37,7 +37,12 @@ function ReportFieldEditValuePage({ const validate = useCallback( (values: FormOnyxValues) => - validateReportFieldListValueName(values[INPUT_IDS.NEW_VALUE_NAME].trim(), currentValueName, formDraft?.[INPUT_IDS.LIST_VALUES] ?? [], INPUT_IDS.NEW_VALUE_NAME), + WorkspaceReportFieldUtils.validateReportFieldListValueName( + values[INPUT_IDS.NEW_VALUE_NAME].trim(), + currentValueName, + formDraft?.[INPUT_IDS.LIST_VALUES] ?? [], + INPUT_IDS.NEW_VALUE_NAME, + ), [currentValueName, formDraft], ); diff --git a/src/pages/workspace/reportFields/ReportFieldInitialValuePage.tsx b/src/pages/workspace/reportFields/ReportFieldInitialValuePage.tsx new file mode 100644 index 0000000000000..1cf075d27f016 --- /dev/null +++ b/src/pages/workspace/reportFields/ReportFieldInitialValuePage.tsx @@ -0,0 +1,149 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback, useState} from 'react'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ReportField from '@libs/actions/Policy/ReportField'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as ReportUtils from '@libs/ReportUtils'; +import * as WorkspaceReportFieldUtils from '@libs/WorkspaceReportFieldUtils'; +import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import CONST from '@src/CONST'; +import * as ErrorUtils from '@src/libs/ErrorUtils'; +import * as ValidationUtils from '@src/libs/ValidationUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceReportFieldForm'; +import ReportFieldsInitialListValuePicker from './InitialListValueSelector/ReportFieldsInitialListValuePicker'; + +type ReportFieldInitialValuePagePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; +function ReportFieldInitialValuePage({ + policy, + route: { + params: {policyID, reportFieldID}, + }, +}: ReportFieldInitialValuePagePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {inputCallbackRef} = useAutoFocusInput(); + + const reportField = policy?.fieldList?.[ReportUtils.getReportFieldKey(reportFieldID)] ?? null; + const availableListValuesLength = (reportField?.disabledOptions ?? []).filter((disabledListValue) => !disabledListValue).length; + + const [initialValue, setInitialValue] = useState(WorkspaceReportFieldUtils.getReportFieldInitialValue(reportField)); + + const submitForm = useCallback( + (values: FormOnyxValues) => { + ReportField.updateReportFieldInitialValue(policyID, reportFieldID, values.initialValue); + Navigation.goBack(); + }, + [policyID, reportFieldID], + ); + + const submitListValueUpdate = (value: string) => { + ReportField.updateReportFieldInitialValue(policyID, reportFieldID, value); + Navigation.goBack(); + }; + + const validateForm = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const {name, type, initialValue: formInitialValue} = values; + const errors: FormInputErrors = {}; + + if (!ValidationUtils.isRequiredFulfilled(name)) { + errors[INPUT_IDS.NAME] = translate('workspace.reportFields.reportFieldNameRequiredError'); + } else if (Object.values(policy?.fieldList ?? {}).some((currentReportField) => currentReportField.name === name)) { + errors[INPUT_IDS.NAME] = translate('workspace.reportFields.existingReportFieldNameError'); + } else if ([...name].length > CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH) { + // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 code units. + ErrorUtils.addErrorMessage( + errors, + INPUT_IDS.NAME, + translate('common.error.characterLimitExceedCounter', {length: [...name].length, limit: CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH}), + ); + } + + if (type === CONST.REPORT_FIELD_TYPES.LIST && availableListValuesLength > 0 && !ValidationUtils.isRequiredFulfilled(formInitialValue)) { + errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.reportFieldInitialValueRequiredError'); + } + + return errors; + }, + [availableListValuesLength, policy?.fieldList, translate], + ); + + if (!reportField) { + return ; + } + + const isTextFieldType = reportField.type === CONST.REPORT_FIELD_TYPES.TEXT; + const isListFieldType = reportField.type === CONST.REPORT_FIELD_TYPES.LIST; + + return ( + + + + {isTextFieldType && ( + + + + )} + {isListFieldType && ( + + )} + + + ); +} + +ReportFieldInitialValuePage.displayName = 'ReportFieldInitialValuePage'; + +export default withPolicyAndFullscreenLoading(ReportFieldInitialValuePage); diff --git a/src/pages/workspace/reportFields/ReportFieldListValuesPage.tsx b/src/pages/workspace/reportFields/ReportFieldListValuesPage.tsx index 3df4661a59196..92cdaad08900c 100644 --- a/src/pages/workspace/reportFields/ReportFieldListValuesPage.tsx +++ b/src/pages/workspace/reportFields/ReportFieldListValuesPage.tsx @@ -22,9 +22,11 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as ReportField from '@libs/actions/Policy/ReportField'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -45,8 +47,9 @@ type ValueListItem = ListItem & { type ReportFieldListValuesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; function ReportFieldListValuesPage({ + policy, route: { - params: {policyID}, + params: {policyID, reportFieldID}, }, }: ReportFieldListValuesPageProps) { const styles = useThemeStyles(); @@ -57,26 +60,44 @@ function ReportFieldListValuesPage({ const [selectedValues, setSelectedValues] = useState>({}); const [deleteValuesConfirmModalVisible, setDeleteValuesConfirmModalVisible] = useState(false); + const [listValues, disabledListValues] = useMemo(() => { + let reportFieldValues: string[]; + let reportFieldDisabledValues: boolean[]; + + if (reportFieldID) { + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + + reportFieldValues = Object.values(policy?.fieldList?.[reportFieldKey]?.values ?? {}); + reportFieldDisabledValues = Object.values(policy?.fieldList?.[reportFieldKey]?.disabledOptions ?? {}); + } else { + reportFieldValues = formDraft?.listValues ?? []; + reportFieldDisabledValues = formDraft?.disabledListValues ?? []; + } + + return [reportFieldValues, reportFieldDisabledValues]; + }, [formDraft?.disabledListValues, formDraft?.listValues, policy?.fieldList, reportFieldID]); + const listValuesSections = useMemo(() => { - const data = Object.values(formDraft?.listValues ?? {}).map((value, index) => ({ + const data = listValues.map((value, index) => ({ value, index, text: value, keyForList: value, isSelected: selectedValues[value], - enabled: formDraft?.disabledListValues?.[index] ?? true, + enabled: !disabledListValues[index] ?? true, + pendingAction: reportFieldID ? policy?.fieldList?.[ReportUtils.getReportFieldKey(reportFieldID)]?.pendingAction : null, rightElement: ( ), })); return [{data, isDisabled: false}]; - }, [formDraft?.disabledListValues, formDraft?.listValues, selectedValues, translate]); + }, [disabledListValues, listValues, policy?.fieldList, reportFieldID, selectedValues, translate]); - const shouldShowEmptyState = Object.values(formDraft?.listValues ?? {}).length <= 0; + const shouldShowEmptyState = Object.values(listValues ?? {}).length <= 0; const selectedValuesArray = Object.keys(selectedValues).filter((key) => selectedValues[key]); const toggleValue = (valueItem: ValueListItem) => { @@ -87,7 +108,6 @@ function ReportFieldListValuesPage({ }; const toggleAllValues = () => { - const listValues = formDraft?.listValues ?? []; const isAllSelected = listValues.length === Object.keys(selectedValues).length; setSelectedValues(isAllSelected ? {} : Object.fromEntries(listValues.map((value) => [value, true]))); @@ -97,7 +117,7 @@ function ReportFieldListValuesPage({ setSelectedValues({}); const valuesToDelete = selectedValuesArray.reduce((acc, valueName) => { - const index = formDraft?.listValues?.indexOf(valueName) ?? -1; + const index = listValues?.indexOf(valueName) ?? -1; if (index !== -1) { acc.push(index); @@ -106,7 +126,12 @@ function ReportFieldListValuesPage({ return acc; }, []); - ReportField.deleteReportFieldsListValue(valuesToDelete); + if (reportFieldID) { + ReportField.removeReportFieldListValue(policyID, reportFieldID, valuesToDelete); + } else { + ReportField.deleteReportFieldsListValue(valuesToDelete); + } + setDeleteValuesConfirmModalVisible(false); }; @@ -115,7 +140,7 @@ function ReportFieldListValuesPage({ return; } - Navigation.navigate(ROUTES.WORKSPACE_REPORT_FIELD_VALUE_SETTINGS.getRoute(policyID, valueItem.index)); + Navigation.navigate(ROUTES.WORKSPACE_REPORT_FIELD_VALUE_SETTINGS.getRoute(policyID, valueItem.index, reportFieldID)); setSelectedValues({}); }; @@ -139,14 +164,14 @@ function ReportFieldListValuesPage({ }); const enabledValues = selectedValuesArray.filter((valueName) => { - const index = formDraft?.listValues?.indexOf(valueName) ?? -1; - return !formDraft?.disabledListValues?.[index]; + const index = listValues?.indexOf(valueName) ?? -1; + return !disabledListValues?.[index]; }); if (enabledValues.length > 0) { const valuesToDisable = selectedValuesArray.reduce((acc, valueName) => { - const index = formDraft?.listValues?.indexOf(valueName) ?? -1; - if (!formDraft?.disabledListValues?.[index] && index !== -1) { + const index = listValues?.indexOf(valueName) ?? -1; + if (!disabledListValues?.[index] && index !== -1) { acc.push(index); } @@ -159,20 +184,26 @@ function ReportFieldListValuesPage({ value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, onSelected: () => { setSelectedValues({}); + + if (reportFieldID) { + ReportField.updateReportFieldListValueEnabled(policyID, reportFieldID, valuesToDisable, false); + return; + } + ReportField.setReportFieldsListValueEnabled(valuesToDisable, false); }, }); } const disabledValues = selectedValuesArray.filter((valueName) => { - const index = formDraft?.listValues?.indexOf(valueName) ?? -1; - return formDraft?.disabledListValues?.[index]; + const index = listValues?.indexOf(valueName) ?? -1; + return disabledListValues?.[index]; }); if (disabledValues.length > 0) { const valuesToEnable = selectedValuesArray.reduce((acc, valueName) => { - const index = formDraft?.listValues?.indexOf(valueName) ?? -1; - if (formDraft?.disabledListValues?.[index] && index !== -1) { + const index = listValues?.indexOf(valueName) ?? -1; + if (disabledListValues?.[index] && index !== -1) { acc.push(index); } @@ -185,6 +216,12 @@ function ReportFieldListValuesPage({ value: CONST.POLICY.BULK_ACTION_TYPES.ENABLE, onSelected: () => { setSelectedValues({}); + + if (reportFieldID) { + ReportField.updateReportFieldListValueEnabled(policyID, reportFieldID, valuesToEnable, true); + return; + } + ReportField.setReportFieldsListValueEnabled(valuesToEnable, true); }, }); @@ -211,7 +248,7 @@ function ReportFieldListValuesPage({ success icon={Expensicons.Plus} text={translate('workspace.reportFields.addValue')} - onPress={() => Navigation.navigate(ROUTES.WORKSPACE_REPORT_FIELD_ADD_VALUE.getRoute(policyID))} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_REPORT_FIELD_ADD_VALUE.getRoute(policyID, reportFieldID))} /> ); }; @@ -278,4 +315,4 @@ function ReportFieldListValuesPage({ ReportFieldListValuesPage.displayName = 'ReportFieldListValuesPage'; -export default ReportFieldListValuesPage; +export default withPolicyAndFullscreenLoading(ReportFieldListValuesPage); diff --git a/src/pages/workspace/reportFields/ReportFieldSettingsPage.tsx b/src/pages/workspace/reportFields/ReportFieldSettingsPage.tsx new file mode 100644 index 0000000000000..342762cc446d3 --- /dev/null +++ b/src/pages/workspace/reportFields/ReportFieldSettingsPage.tsx @@ -0,0 +1,126 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import {Str} from 'expensify-common'; +import React, {useState} from 'react'; +import {View} from 'react-native'; +import ConfirmModal from '@components/ConfirmModal'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; +import * as WorkspaceReportFieldUtils from '@libs/WorkspaceReportFieldUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import * as ReportField from '@userActions/Policy/ReportField'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type ReportFieldSettingsPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; + +function ReportFieldSettingsPage({ + policy, + route: { + params: {policyID, reportFieldID}, + }, +}: ReportFieldSettingsPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField = policy?.fieldList?.[reportFieldKey] ?? null; + + if (!reportField) { + return ; + } + + const isDateFieldType = reportField.type === CONST.REPORT_FIELD_TYPES.DATE; + const isListFieldType = reportField.type === CONST.REPORT_FIELD_TYPES.LIST; + + const deleteReportFieldAndHideModal = () => { + ReportField.deleteReportFields(policyID, [reportFieldKey]); + setIsDeleteModalVisible(false); + Navigation.goBack(); + }; + + return ( + + + + + + Navigation.navigate(ROUTES.WORKSPACE_EDIT_REPORT_FIELD_INITIAL_VALUE.getRoute(policyID, reportFieldID))} + /> + {isListFieldType && ( + Navigation.navigate(ROUTES.WORKSPACE_REPORT_FIELD_LIST_VALUES.getRoute(policyID, reportFieldID))} + /> + )} + + setIsDeleteModalVisible(true)} + /> + + setIsDeleteModalVisible(false)} + shouldSetModalVisibility={false} + prompt={translate('workspace.reportFields.deleteConfirmation')} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> + + + ); +} + +ReportFieldSettingsPage.displayName = 'ReportFieldSettingsPage'; + +export default withPolicyAndFullscreenLoading(ReportFieldSettingsPage); diff --git a/src/pages/workspace/reportFields/ReportFieldTypePicker/index.tsx b/src/pages/workspace/reportFields/ReportFieldTypePicker/index.tsx index 88b21f0fc2d91..599d7ae0da997 100644 --- a/src/pages/workspace/reportFields/ReportFieldTypePicker/index.tsx +++ b/src/pages/workspace/reportFields/ReportFieldTypePicker/index.tsx @@ -2,7 +2,7 @@ import React, {useMemo} from 'react'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; -import {getReportFieldAlternativeTextTranslationKey, getReportFieldTypeTranslationKey} from '@libs/WorkspaceReportFieldsUtils'; +import * as WorkspaceReportFieldUtils from '@libs/WorkspaceReportFieldUtils'; import CONST from '@src/CONST'; import type {PolicyReportFieldType} from '@src/types/onyx/Policy'; @@ -36,8 +36,8 @@ function ReportFieldTypePicker({defaultValue, onOptionSelected}: ReportFieldType keyForList: reportFieldType, value: reportFieldType, isSelected: defaultValue === reportFieldType, - text: translate(getReportFieldTypeTranslationKey(reportFieldType)), - alternateText: translate(getReportFieldAlternativeTextTranslationKey(reportFieldType)), + text: translate(WorkspaceReportFieldUtils.getReportFieldTypeTranslationKey(reportFieldType)), + alternateText: translate(WorkspaceReportFieldUtils.getReportFieldAlternativeTextTranslationKey(reportFieldType)), })); return [{data}]; diff --git a/src/pages/workspace/reportFields/ValueSettingsPage.tsx b/src/pages/workspace/reportFields/ReportFieldValueSettingsPage.tsx similarity index 65% rename from src/pages/workspace/reportFields/ValueSettingsPage.tsx rename to src/pages/workspace/reportFields/ReportFieldValueSettingsPage.tsx index 0e1a21e2d9b24..f99842fdf5a7c 100644 --- a/src/pages/workspace/reportFields/ValueSettingsPage.tsx +++ b/src/pages/workspace/reportFields/ReportFieldValueSettingsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useState} from 'react'; +import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; @@ -14,41 +14,69 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportField from '@libs/actions/Policy/ReportField'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -type ValueSettingsPageProps = StackScreenProps; +type ReportFieldValueSettingsPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; -function ValueSettingsPage({ +function ReportFieldValueSettingsPage({ + policy, route: { - params: {policyID, valueIndex}, + params: {policyID, valueIndex, reportFieldID}, }, -}: ValueSettingsPageProps) { +}: ReportFieldValueSettingsPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [formDraft] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT); const [isDeleteTagModalOpen, setIsDeleteTagModalOpen] = useState(false); - const currentValueName = formDraft?.listValues?.[valueIndex] ?? ''; - const currentValueDisabled = formDraft?.disabledListValues?.[valueIndex] ?? false; + const [currentValueName, currentValueDisabled] = useMemo(() => { + let reportFieldValue: string; + let reportFieldDisabledValue: boolean; + + if (reportFieldID) { + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + + reportFieldValue = Object.values(policy?.fieldList?.[reportFieldKey]?.values ?? {})?.[valueIndex] ?? ''; + reportFieldDisabledValue = Object.values(policy?.fieldList?.[reportFieldKey]?.disabledOptions ?? {})?.[valueIndex] ?? false; + } else { + reportFieldValue = formDraft?.listValues?.[valueIndex] ?? ''; + reportFieldDisabledValue = formDraft?.disabledListValues?.[valueIndex] ?? false; + } + + return [reportFieldValue, reportFieldDisabledValue]; + }, [formDraft?.disabledListValues, formDraft?.listValues, policy?.fieldList, reportFieldID, valueIndex]); if (!currentValueName) { return ; } const deleteListValueAndHideModal = () => { - ReportField.deleteReportFieldsListValue([valueIndex]); + if (reportFieldID) { + ReportField.removeReportFieldListValue(policyID, reportFieldID, [valueIndex]); + } else { + ReportField.deleteReportFieldsListValue([valueIndex]); + } + setIsDeleteTagModalOpen(false); Navigation.goBack(); }; const updateListValueEnabled = (value: boolean) => { + if (reportFieldID) { + ReportField.updateReportFieldListValueEnabled(policyID, reportFieldID, [Number(valueIndex)], value); + return; + } + ReportField.setReportFieldsListValueEnabled([valueIndex], value); }; @@ -65,7 +93,7 @@ function ValueSettingsPage({ ; - -function WorkspaceReportFieldSettings({route}: WorkspaceReportFieldSettingsPageProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`); - const [isDeleteModalVisible, setIsDeleteModalVisible] = React.useState(false); - const reportFieldKey = ReportUtils.getReportFieldKey(route.params.reportFieldKey); - const currentPolicyReportField = policy?.fieldList?.[reportFieldKey]; - if (!currentPolicyReportField) { - return ; - } - - const deleteReportFieldAndHideModal = () => { - ReportField.deleteReportFields(route.params.policyID, [reportFieldKey]); - setIsDeleteModalVisible(false); - Navigation.goBack(); - }; - - return ( - - - - setIsDeleteModalVisible(false)} - shouldSetModalVisibility={false} - prompt={translate('workspace.reportFields.deleteConfirmation')} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - /> - - setIsDeleteModalVisible(true)} - /> - - - - ); -} - -WorkspaceReportFieldSettings.displayName = 'WorkspaceReportFieldSettings'; - -export default WorkspaceReportFieldSettings; diff --git a/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx b/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx index 34a7719774e06..4ab6e48811c59 100644 --- a/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx +++ b/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx @@ -65,25 +65,31 @@ function WorkspaceReportFieldsPage({ setSelectedReportFields([]); }, [isFocused]); - const reportFieldsList = useMemo(() => { + const reportFieldsSections = useMemo(() => { if (!policy) { - return []; + return [{data: [], isDisabled: true}]; } - return Object.values(filteredPolicyFieldList).map((reportField) => ({ - value: reportField.name, - fieldID: reportField.fieldID, - keyForList: String(reportField.orderWeight), - orderWeight: reportField.orderWeight, - pendingAction: reportField.pendingAction, - isSelected: selectedReportFields.find((selectedReportField) => selectedReportField.name === reportField.name) !== undefined, - text: reportField.name, - rightElement: ( - - ), - })); + + return [ + { + data: Object.values(filteredPolicyFieldList).map((reportField) => ({ + value: reportField.name, + fieldID: reportField.fieldID, + keyForList: String(reportField.fieldID), + orderWeight: reportField.orderWeight, + pendingAction: reportField.pendingAction, + isSelected: selectedReportFields.find((selectedReportField) => selectedReportField.name === reportField.name) !== undefined, + text: reportField.name, + rightElement: ( + + ), + })), + isDisabled: false, + }, + ]; }, [filteredPolicyFieldList, policy, selectedReportFields]); const updateSelectedReportFields = (item: ReportFieldForList) => { @@ -98,7 +104,7 @@ function WorkspaceReportFieldsPage({ Navigation.navigate(ROUTES.WORKSPACE_REPORT_FIELD_SETTINGS.getRoute(policyID, reportField.fieldID)); }; - const isLoading = reportFieldsList === undefined; + const isLoading = policy === undefined; const shouldShowEmptyState = Object.values(filteredPolicyFieldList).length <= 0 && !isLoading; const getHeaderButtons = () => ( @@ -148,7 +154,7 @@ function WorkspaceReportFieldsPage({ {!isSmallScreenWidth && getHeaderButtons()} {isSmallScreenWidth && {getHeaderButtons()}} - {(!isSmallScreenWidth || reportFieldsList.length === 0 || isLoading) && getHeaderText()} + {(!isSmallScreenWidth || reportFieldsSections[0].data.length === 0 || isLoading) && getHeaderText()} {isLoading && ( {}} diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 14b070d3d781d..5dea1664589dc 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -39,6 +39,8 @@ type PolicyRoute = RouteProp< | typeof SCREENS.WORKSPACE.DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT | typeof SCREENS.WORKSPACE.REPORT_FIELDS_CREATE | typeof SCREENS.WORKSPACE.REPORT_FIELDS_LIST_VALUES + | typeof SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE + | typeof SCREENS.WORKSPACE.REPORT_FIELDS_VALUE_SETTINGS >; function getPolicyIDFromRoute(route: PolicyRoute): string { diff --git a/src/types/form/WorkspaceReportFieldsForm.ts b/src/types/form/WorkspaceReportFieldForm.ts similarity index 89% rename from src/types/form/WorkspaceReportFieldsForm.ts rename to src/types/form/WorkspaceReportFieldForm.ts index a33437c0b070f..69651cf7de208 100644 --- a/src/types/form/WorkspaceReportFieldsForm.ts +++ b/src/types/form/WorkspaceReportFieldForm.ts @@ -14,7 +14,7 @@ const INPUT_IDS = { type InputID = ValueOf; -type WorkspaceReportFieldsForm = Form< +type WorkspaceReportFieldForm = Form< InputID, { [INPUT_IDS.NAME]: string; @@ -27,5 +27,5 @@ type WorkspaceReportFieldsForm = Form< } >; -export type {WorkspaceReportFieldsForm, InputID}; +export type {WorkspaceReportFieldForm, InputID}; export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index b3503099b4b31..52ceb719d8fff 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -54,7 +54,7 @@ export type {WalletAdditionalDetailsForm} from './WalletAdditionalDetailsForm'; export type {NewChatNameForm} from './NewChatNameForm'; export type {WorkForm} from './WorkForm'; export type {SubscriptionSizeForm} from './SubscriptionSizeForm'; -export type {WorkspaceReportFieldsForm} from './WorkspaceReportFieldsForm'; +export type {WorkspaceReportFieldForm} from './WorkspaceReportFieldForm'; export type {SageIntactCredentialsForm} from './SageIntactCredentialsForm'; export type {NetSuiteTokenInputForm} from './NetSuiteTokenInputForm'; export type {default as Form} from './Form'; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 395de399bc9d3..9e50c5c77bf01 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1062,7 +1062,7 @@ type PolicyReportField = { deletable: boolean; /** Value of the field */ - value: string | null; + value?: string | null; /** Options to select from if field is of type dropdown */ values: string[]; diff --git a/tests/actions/ReportFieldTest.ts b/tests/actions/ReportFieldTest.ts new file mode 100644 index 0000000000000..4676d187a211b --- /dev/null +++ b/tests/actions/ReportFieldTest.ts @@ -0,0 +1,737 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import DateUtils from '@libs/DateUtils'; +import {generateFieldID} from '@libs/WorkspaceReportFieldUtils'; +import CONST from '@src/CONST'; +import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; +import * as Policy from '@src/libs/actions/Policy/Policy'; +import * as ReportField from '@src/libs/actions/Policy/ReportField'; +import type {CreateReportFieldArguments} from '@src/libs/actions/Policy/ReportField'; +import * as ReportUtils from '@src/libs/ReportUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/WorkspaceReportFieldForm'; +import type {PolicyReportField, Policy as PolicyType} from '@src/types/onyx'; +import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon'; +import createRandomPolicy from '../utils/collections/policies'; +import * as TestHelper from '../utils/TestHelper'; +import type {MockFetch} from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +OnyxUpdateManager(); +describe('actions/ReportField', () => { + type PolicyReportFieldWithOfflineFeedback = Record>; + + function connectToFetchPolicy(policyID: string): Promise> { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + callback: (workspace) => { + Onyx.disconnect(connectionID); + resolve(workspace); + }, + }); + }); + } + + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + }); + }); + + let mockFetch: MockFetch; + beforeEach(() => { + global.fetch = TestHelper.getGlobalFetchMock(); + mockFetch = fetch as MockFetch; + return Onyx.clear().then(waitForBatchedUpdates); + }); + + describe('createReportField', () => { + it('creates a new text report field of a workspace', async () => { + mockFetch.pause(); + Onyx.set(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT, {}); + await waitForBatchedUpdates(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const newReportField: OnyxValueWithOfflineFeedback = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.TEXT, + defaultValue: 'Default Value', + values: [], + disabledOptions: [], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + externalIDs: [], + isTax: false, + }; + const createReportFieldArguments: CreateReportFieldArguments = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.TEXT, + initialValue: 'Default Value', + }; + + ReportField.createReportField(policyID, createReportFieldArguments); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the new report field was added to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: {...newReportField, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + }); + + // Check for success data + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // Check if the policy pending action was cleared + expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); + }); + + it('creates a new date report field of a workspace', async () => { + mockFetch.pause(); + Onyx.set(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT, {}); + await waitForBatchedUpdates(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field 2'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const defaultDate = DateUtils.extractDate(new Date().toString()); + const newReportField: OnyxValueWithOfflineFeedback = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.DATE, + defaultValue: defaultDate, + values: [], + disabledOptions: [], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + externalIDs: [], + isTax: false, + }; + const createReportFieldArguments: CreateReportFieldArguments = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.DATE, + initialValue: defaultDate, + }; + + ReportField.createReportField(policyID, createReportFieldArguments); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the new report field was added to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: {...newReportField, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + }); + + // Check for success data + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // Check if the policy pending action was cleared + expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); + }); + + it('creates a new list report field of a workspace', async () => { + mockFetch.pause(); + Onyx.set(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT, { + [INPUT_IDS.LIST_VALUES]: ['Value 1', 'Value 2'], + [INPUT_IDS.DISABLED_LIST_VALUES]: [false, true], + }); + await waitForBatchedUpdates(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field 3'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const newReportField: OnyxValueWithOfflineFeedback = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.LIST, + defaultValue: '', + values: ['Value 1', 'Value 2'], + disabledOptions: [false, true], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + value: CONST.REPORT_FIELD_TYPES.LIST, + }; + const createReportFieldArguments: CreateReportFieldArguments = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.LIST, + initialValue: '', + }; + + ReportField.createReportField(policyID, createReportFieldArguments); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the new report field was added to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: {...newReportField, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + }); + + // Check for success data + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // Check if the policy pending action was cleared + expect(policy?.fieldList?.[reportFieldKey].pendingAction).toBeFalsy(); + }); + }); + + describe('deleteReportField', () => { + it('Deleted a report field from a workspace', async () => { + const fakePolicy = createRandomPolicy(0); + const reportFieldName = 'Test Field'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const fakeReportField: OnyxValueWithOfflineFeedback = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.TEXT, + defaultValue: 'Default Value', + values: [], + disabledOptions: [], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + value: 'default', + externalIDs: [], + isTax: false, + }; + fakePolicy.fieldList = { + [reportFieldKey]: fakeReportField, + }; + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(fakePolicy.id); + + // check if the report field exists in the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: fakeReportField, + }); + + ReportField.deleteReportFields(fakePolicy.id, [reportFieldKey]); + await waitForBatchedUpdates(); + + // Check for success data + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(fakePolicy.id); + + // Check if the policy report field was removed + expect(policy?.fieldList?.[reportFieldKey]).toBeFalsy(); + }); + + it('Deleted a report field from a workspace when API fails', async () => { + const policyID = Policy.generatePolicyID(); + const fakePolicy = createRandomPolicy(Number(policyID)); + const reportFieldName = 'Test Field'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const fakeReportField: OnyxValueWithOfflineFeedback = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.TEXT, + defaultValue: 'Default Value', + values: [], + disabledOptions: [], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + value: 'default', + externalIDs: [], + isTax: false, + }; + fakePolicy.fieldList = { + [reportFieldKey]: fakeReportField, + }; + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, fakePolicy); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the report field exists in the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: fakeReportField, + }); + + // Check for failure data + mockFetch.fail(); + ReportField.deleteReportFields(policyID, [reportFieldKey]); + await waitForBatchedUpdates(); + + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // check if the deleted report field was reset in the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: fakeReportField, + }); + }); + }); + + describe('updateReportFieldInitialValue', () => { + it('updates the initial value of a text report field', async () => { + mockFetch.pause(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const oldInitialValue = 'Old initial value'; + const newInitialValue = 'New initial value'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField: PolicyReportField = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.TEXT, + defaultValue: oldInitialValue, + values: [], + disabledOptions: [], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + }; + const fakePolicy = createRandomPolicy(Number(policyID)); + + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {...fakePolicy, fieldList: {[reportFieldKey]: reportField}}); + await waitForBatchedUpdates(); + + ReportField.updateReportFieldInitialValue(policyID, reportFieldID, newInitialValue); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the updated report field was set to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: { + ...reportField, + defaultValue: newInitialValue, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }); + + // Check for success data + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // Check if the policy pending action was cleared + expect(policy?.fieldList?.[reportFieldKey].pendingAction).toBeFalsy(); + }); + + it('updates the initial value of a text report field when api returns an error', async () => { + mockFetch.pause(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const oldInitialValue = 'Old initial value'; + const newInitialValue = 'New initial value'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField: PolicyReportField = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.TEXT, + defaultValue: oldInitialValue, + values: [], + disabledOptions: [], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + }; + const fakePolicy = createRandomPolicy(Number(policyID)); + + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {...fakePolicy, fieldList: {[reportFieldKey]: reportField}}); + await waitForBatchedUpdates(); + + ReportField.updateReportFieldInitialValue(policyID, reportFieldID, newInitialValue); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the updated report field was set to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: { + ...reportField, + defaultValue: newInitialValue, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }); + + // Check for failure data + mockFetch.fail(); + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // check if the updated report field was reset in the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: reportField, + }); + // Check if the policy errors was set + expect(policy?.errorFields?.[reportFieldKey]).toBeTruthy(); + }); + }); + + describe('updateReportFieldListValueEnabled', () => { + it('updates the enabled flag of report field list values', async () => { + mockFetch.pause(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const valueIndexesTpUpdate = [1, 2]; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField: PolicyReportField = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.LIST, + defaultValue: 'Value 2', + values: ['Value 1', 'Value 2', 'Value 3'], + disabledOptions: [false, false, true], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + value: CONST.REPORT_FIELD_TYPES.LIST, + }; + const fakePolicy = createRandomPolicy(Number(policyID)); + + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {...fakePolicy, fieldList: {[reportFieldKey]: reportField}}); + await waitForBatchedUpdates(); + + ReportField.updateReportFieldListValueEnabled(policyID, reportFieldID, valueIndexesTpUpdate, false); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the new report field was added to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: { + ...reportField, + defaultValue: '', + disabledOptions: [false, true, true], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }); + + // Check for success data + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // Check if the policy pending action was cleared + expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); + }); + + it('updates the enabled flag of a report field list value when api returns an error', async () => { + mockFetch.pause(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const valueIndexesToUpdate = [1]; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField: PolicyReportField = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.LIST, + defaultValue: 'Value 2', + values: ['Value 1', 'Value 2', 'Value 3'], + disabledOptions: [false, false, true], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + value: CONST.REPORT_FIELD_TYPES.LIST, + }; + const fakePolicy = createRandomPolicy(Number(policyID)); + + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {...fakePolicy, fieldList: {[reportFieldKey]: reportField}}); + await waitForBatchedUpdates(); + + ReportField.updateReportFieldListValueEnabled(policyID, reportFieldID, valueIndexesToUpdate, false); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the new report field was added to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: { + ...reportField, + defaultValue: '', + disabledOptions: [false, true, true], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }); + + // Check for failure data + mockFetch.fail(); + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // check if the updated report field was reset in the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: reportField, + }); + // Check if the policy errors was set + expect(policy?.errorFields?.[reportFieldKey]).toBeTruthy(); + }); + }); + + describe('addReportFieldListValue', () => { + it('adds a new value to a report field list', async () => { + mockFetch.pause(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField: PolicyReportField = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.LIST, + defaultValue: 'Value 2', + values: ['Value 1', 'Value 2', 'Value 3'], + disabledOptions: [false, false, true], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + value: CONST.REPORT_FIELD_TYPES.LIST, + }; + const fakePolicy = createRandomPolicy(Number(policyID)); + const newListValueName = 'Value 4'; + + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {...fakePolicy, fieldList: {[reportFieldKey]: reportField}}); + await waitForBatchedUpdates(); + + ReportField.addReportFieldListValue(policyID, reportFieldID, newListValueName); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + // check if the new report field was added to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: { + ...reportField, + values: [...reportField.values, newListValueName], + disabledOptions: [...reportField.disabledOptions, false], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }); + + // Check for success data + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // Check if the policy pending action was cleared + expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); + }); + + it('adds a new value to a report field list when api returns an error', async () => { + mockFetch.pause(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField: PolicyReportField = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.LIST, + defaultValue: 'Value 2', + values: ['Value 1', 'Value 2', 'Value 3'], + disabledOptions: [false, false, true], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + value: CONST.REPORT_FIELD_TYPES.LIST, + }; + const fakePolicy = createRandomPolicy(Number(policyID)); + const newListValueName = 'Value 4'; + + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {...fakePolicy, fieldList: {[reportFieldKey]: reportField}}); + await waitForBatchedUpdates(); + + ReportField.addReportFieldListValue(policyID, reportFieldID, newListValueName); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // check if the new report field was added to the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: { + ...reportField, + values: [...reportField.values, newListValueName], + disabledOptions: [...reportField.disabledOptions, false], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }); + + // Check for failure data + mockFetch.fail(); + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // check if the updated report field was reset in the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: reportField, + }); + }); + }); + + describe('removeReportFieldListValue', () => { + it('removes list values from a report field list', async () => { + mockFetch.pause(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField: PolicyReportField = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.LIST, + defaultValue: 'Value 2', + values: ['Value 1', 'Value 2', 'Value 3'], + disabledOptions: [false, false, true], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + value: CONST.REPORT_FIELD_TYPES.LIST, + }; + const fakePolicy = createRandomPolicy(Number(policyID)); + + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {...fakePolicy, fieldList: {[reportFieldKey]: reportField}}); + await waitForBatchedUpdates(); + + ReportField.removeReportFieldListValue(policyID, reportFieldID, [1, 2]); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // Check if the values were removed from the report field + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: { + ...reportField, + defaultValue: '', + values: ['Value 1'], + disabledOptions: [false], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }); + + // Check for success data + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + // Check if the policy pending action was cleared + expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); + }); + + it('removes list values from a report field list when api returns an error', async () => { + mockFetch.pause(); + + const policyID = Policy.generatePolicyID(); + const reportFieldName = 'Test Field'; + const reportFieldID = generateFieldID(reportFieldName); + const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); + const reportField: PolicyReportField = { + name: reportFieldName, + type: CONST.REPORT_FIELD_TYPES.LIST, + defaultValue: 'Value 2', + values: ['Value 1', 'Value 2', 'Value 3'], + disabledOptions: [false, false, true], + fieldID: reportFieldID, + orderWeight: 1, + deletable: false, + keys: [], + externalIDs: [], + isTax: false, + value: CONST.REPORT_FIELD_TYPES.LIST, + }; + const fakePolicy = createRandomPolicy(Number(policyID)); + + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {...fakePolicy, fieldList: {[reportFieldKey]: reportField}}); + await waitForBatchedUpdates(); + + ReportField.removeReportFieldListValue(policyID, reportFieldID, [1, 2]); + await waitForBatchedUpdates(); + + let policy: OnyxEntry = await connectToFetchPolicy(policyID); + + // Check if the values were removed from the report field + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: { + ...reportField, + defaultValue: '', + values: ['Value 1'], + disabledOptions: [false], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }); + + // Check for failure data + mockFetch.fail(); + mockFetch.resume(); + await waitForBatchedUpdates(); + + policy = await connectToFetchPolicy(policyID); + + // check if the updated report field was reset in the policy + expect(policy?.fieldList).toStrictEqual({ + [reportFieldKey]: reportField, + }); + // Check if the policy errors was set + expect(policy?.errorFields?.[reportFieldKey]).toBeTruthy(); + }); + }); +}); diff --git a/tests/actions/ReportFieldsTest.ts b/tests/actions/ReportFieldsTest.ts deleted file mode 100644 index 46af89407eed1..0000000000000 --- a/tests/actions/ReportFieldsTest.ts +++ /dev/null @@ -1,386 +0,0 @@ -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; -import DateUtils from '@libs/DateUtils'; -import {generateFieldID} from '@libs/WorkspaceReportFieldsUtils'; -import CONST from '@src/CONST'; -import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; -import * as Policy from '@src/libs/actions/Policy/Policy'; -import * as ReportField from '@src/libs/actions/Policy/ReportField'; -import type {CreateReportFieldArguments} from '@src/libs/actions/Policy/ReportField'; -import * as ReportUtils from '@src/libs/ReportUtils'; -import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/WorkspaceReportFieldsForm'; -import type {PolicyReportField, Policy as PolicyType} from '@src/types/onyx'; -import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon'; -import createRandomPolicy from '../utils/collections/policies'; -import * as TestHelper from '../utils/TestHelper'; -import type {MockFetch} from '../utils/TestHelper'; -import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; - -OnyxUpdateManager(); -describe('actions/ReportField', () => { - beforeAll(() => { - Onyx.init({ - keys: ONYXKEYS, - }); - }); - - let mockFetch: MockFetch; - beforeEach(() => { - global.fetch = TestHelper.getGlobalFetchMock(); - mockFetch = fetch as MockFetch; - return Onyx.clear().then(waitForBatchedUpdates); - }); - - describe('createReportField', () => { - it('creates a new text report field of a workspace', async () => { - mockFetch?.pause?.(); - Onyx.set(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT, {}); - await waitForBatchedUpdates(); - - const policyID = Policy.generatePolicyID(); - const reportFieldName = 'Test Field'; - const reportFieldID = generateFieldID(reportFieldName); - const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); - const newReportField: OnyxValueWithOfflineFeedback> = { - name: reportFieldName, - type: CONST.REPORT_FIELD_TYPES.TEXT, - defaultValue: 'Default Value', - values: [], - disabledOptions: [], - fieldID: reportFieldID, - orderWeight: 1, - deletable: false, - keys: [], - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - externalIDs: [], - isTax: false, - }; - const createReportFieldArguments: CreateReportFieldArguments = { - name: reportFieldName, - type: CONST.REPORT_FIELD_TYPES.TEXT, - initialValue: 'Default Value', - }; - - ReportField.createReportField(policyID, createReportFieldArguments); - await waitForBatchedUpdates(); - - let policy: OnyxEntry | OnyxCollection = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // check if the new report field was added to the policy - expect(policy?.fieldList).toStrictEqual({ - [reportFieldKey]: newReportField, - }); - - // Check for success data - mockFetch?.resume?.(); - await waitForBatchedUpdates(); - - policy = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY, - waitForCollectionCallback: true, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // Check if the policy pending action was cleared - // @ts-expect-error pendingFields is not null - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); - }); - - it('creates a new date report field of a workspace', async () => { - mockFetch?.pause?.(); - Onyx.set(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT, {}); - await waitForBatchedUpdates(); - - const policyID = Policy.generatePolicyID(); - const reportFieldName = 'Test Field 2'; - const reportFieldID = generateFieldID(reportFieldName); - const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); - const defaultDate = DateUtils.extractDate(new Date().toString()); - const newReportField: OnyxValueWithOfflineFeedback> = { - name: reportFieldName, - type: CONST.REPORT_FIELD_TYPES.DATE, - defaultValue: defaultDate, - values: [], - disabledOptions: [], - fieldID: reportFieldID, - orderWeight: 1, - deletable: false, - keys: [], - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - externalIDs: [], - isTax: false, - }; - const createReportFieldArguments: CreateReportFieldArguments = { - name: reportFieldName, - type: CONST.REPORT_FIELD_TYPES.DATE, - initialValue: defaultDate, - }; - - ReportField.createReportField(policyID, createReportFieldArguments); - await waitForBatchedUpdates(); - - let policy: OnyxEntry | OnyxCollection = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // check if the new report field was added to the policy - expect(policy?.fieldList).toStrictEqual({ - [reportFieldKey]: newReportField, - }); - - // Check for success data - mockFetch?.resume?.(); - await waitForBatchedUpdates(); - - policy = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // Check if the policy pending action was cleared - // @ts-expect-error pendingFields is not null - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); - }); - - it('creates a new list report field of a workspace', async () => { - mockFetch?.pause?.(); - Onyx.set(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT, { - [INPUT_IDS.LIST_VALUES]: ['Value 1', 'Value 2'], - [INPUT_IDS.DISABLED_LIST_VALUES]: [false, true], - }); - await waitForBatchedUpdates(); - - const policyID = Policy.generatePolicyID(); - const reportFieldName = 'Test Field 3'; - const reportFieldID = generateFieldID(reportFieldName); - const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); - const newReportField: OnyxValueWithOfflineFeedback = { - name: reportFieldName, - type: CONST.REPORT_FIELD_TYPES.LIST, - defaultValue: '', - values: ['Value 1', 'Value 2'], - disabledOptions: [false, true], - fieldID: reportFieldID, - orderWeight: 1, - deletable: false, - keys: [], - externalIDs: [], - isTax: false, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - value: CONST.REPORT_FIELD_TYPES.LIST, - }; - const createReportFieldArguments: CreateReportFieldArguments = { - name: reportFieldName, - type: CONST.REPORT_FIELD_TYPES.LIST, - initialValue: '', - }; - - ReportField.createReportField(policyID, createReportFieldArguments); - await waitForBatchedUpdates(); - - let policy: OnyxEntry | OnyxCollection = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // check if the new report field was added to the policy - expect(policy?.fieldList).toStrictEqual({ - [reportFieldKey]: newReportField, - }); - - // Check for success data - mockFetch?.resume?.(); - await waitForBatchedUpdates(); - - policy = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // Check if the policy pending action was cleared - // @ts-expect-error pendingFields is not null - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); - }); - }); - - describe('deleteReportField', () => { - it('Deleted a report field from a workspace', async () => { - const fakePolicy = createRandomPolicy(0); - const reportFieldName = 'Test Field'; - const reportFieldID = generateFieldID(reportFieldName); - const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); - const fakeReportField: OnyxValueWithOfflineFeedback = { - name: reportFieldName, - type: CONST.REPORT_FIELD_TYPES.TEXT, - defaultValue: 'Default Value', - values: [], - disabledOptions: [], - fieldID: reportFieldID, - orderWeight: 1, - deletable: false, - keys: [], - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - value: 'default', - externalIDs: [], - isTax: false, - }; - fakePolicy.fieldList = { - [reportFieldKey]: fakeReportField, - }; - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); - await waitForBatchedUpdates(); - - let policy: OnyxEntry | OnyxCollection = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // check if the report field exists in the policy - expect(policy?.fieldList).toStrictEqual({ - [reportFieldKey]: fakeReportField, - }); - - ReportField.deleteReportFields(fakePolicy.id, [reportFieldKey]); - await waitForBatchedUpdates(); - - // Check for success data - mockFetch?.resume?.(); - await waitForBatchedUpdates(); - - policy = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // Check if the policy report field was removed - // @ts-expect-error fieldList is not null - expect(policy?.fieldList?.[reportFieldKey]).toBeFalsy(); - - // Check if the policy pending action was cleared - // @ts-expect-error pendingFields is not null - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); - }); - - it('Deleted a report field from a workspace when API fails', async () => { - const policyID = Policy.generatePolicyID(); - const fakePolicy = createRandomPolicy(Number(policyID)); - const reportFieldName = 'Test Field'; - const reportFieldID = generateFieldID(reportFieldName); - const reportFieldKey = ReportUtils.getReportFieldKey(reportFieldID); - const fakeReportField: OnyxValueWithOfflineFeedback = { - name: reportFieldName, - type: CONST.REPORT_FIELD_TYPES.TEXT, - defaultValue: 'Default Value', - values: [], - disabledOptions: [], - fieldID: reportFieldID, - orderWeight: 1, - deletable: false, - keys: [], - value: 'default', - externalIDs: [], - isTax: false, - }; - fakePolicy.fieldList = { - [reportFieldKey]: fakeReportField, - }; - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, fakePolicy); - await waitForBatchedUpdates(); - - let policy: OnyxEntry | OnyxCollection = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // check if the report field exists in the policy - expect(policy?.fieldList).toStrictEqual({ - [reportFieldKey]: fakeReportField, - }); - - // Check for failure data - mockFetch?.fail?.(); - ReportField.deleteReportFields(policyID, [reportFieldKey]); - await waitForBatchedUpdates(); - - mockFetch?.resume?.(); - await waitForBatchedUpdates(); - - policy = await new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - callback: (workspace) => { - Onyx.disconnect(connectionID); - resolve(workspace); - }, - }); - }); - - // check if the deleted report field was reset in the policy - expect(policy?.fieldList).toStrictEqual({ - [reportFieldKey]: fakeReportField, - }); - // Check if the policy pending action was cleared - // @ts-expect-error pendingFields is not null - expect(policy?.pendingFields?.[reportFieldKey]).toBeFalsy(); - - // Check if the policy pending action was cleared - // @ts-expect-error pendingFields is not null - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(policy?.fieldList?.[reportFieldKey]?.pendingAction).toBeFalsy(); - }); - }); -});