diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 98e3856f4544..1857911b920c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -350,6 +350,8 @@ const ONYXKEYS = { REPORT_VIRTUAL_CARD_FRAUD_DRAFT: 'reportVirtualCardFraudFormDraft', GET_PHYSICAL_CARD_FORM: 'getPhysicalCardForm', GET_PHYSICAL_CARD_FORM_DRAFT: 'getPhysicalCardFormDraft', + POLICY_REPORT_FIELD_EDIT_FORM: 'policyReportFieldEditForm', + POLICY_REPORT_FIELD_EDIT_FORM_DRAFT: 'policyReportFieldEditFormDraft', }, } as const; @@ -442,7 +444,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; - [ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS]: OnyxTypes.PolicyReportField; + [ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS]: OnyxTypes.PolicyReportFields; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; @@ -527,6 +529,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form | undefined; + [ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM_DRAFT]: OnyxTypes.Form | undefined; }; type OnyxKeyValue = OnyxEntry; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 37003a09a0cd..5ebe05eb93e2 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -163,6 +163,10 @@ const ROUTES = { route: 'r/:threadReportID/edit/currency', getRoute: (threadReportID: string, currency: string, backTo: string) => `r/${threadReportID}/edit/currency?currency=${currency}&backTo=${backTo}` as const, }, + EDIT_REPORT_FIELD_REQUEST: { + route: 'r/:reportID/edit/policyField/:policyID/:fieldID', + getRoute: (reportID: string, policyID: string, fieldID: string) => `r/${reportID}/edit/policyField/${policyID}/${fieldID}` as const, + }, REPORT_WITH_ID_DETAILS_SHARE_CODE: { route: 'r/:reportID/details/shareCode', getRoute: (reportID: string) => `r/${reportID}/details/shareCode` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 703cb309d641..e2f4a849d4aa 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -208,6 +208,7 @@ const SCREENS = { EDIT_REQUEST: { ROOT: 'EditRequest_Root', CURRENCY: 'EditRequest_Currency', + REPORT_FIELD: 'EditRequest_ReportField', }, NEW_CHAT: { diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 4fcca3e518a5..8c6e818d2411 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -14,9 +14,11 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import variables from '@styles/variables'; +import ROUTES from '@src/ROUTES'; import type {PolicyReportField, Report} from '@src/types/onyx'; type MoneyReportViewProps = { @@ -73,7 +75,7 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: {}} + onPress={() => Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))} shouldShowRightIcon disabled={false} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 9d4be56ba08f..9080281204de 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -267,6 +267,7 @@ const FlagCommentStackNavigator = createModalStackNavigator({ [SCREENS.EDIT_REQUEST.ROOT]: () => require('../../../pages/EditRequestPage').default as React.ComponentType, [SCREENS.EDIT_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType, + [SCREENS.EDIT_REQUEST.REPORT_FIELD]: () => require('../../../pages/EditReportFieldPage').default as React.ComponentType, }); const PrivateNotesModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index 1a495e92eb80..16d4ef0e350f 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -478,6 +478,7 @@ const linkingConfig: LinkingOptions = { screens: { [SCREENS.EDIT_REQUEST.ROOT]: ROUTES.EDIT_REQUEST.route, [SCREENS.EDIT_REQUEST.CURRENCY]: ROUTES.EDIT_CURRENCY_REQUEST.route, + [SCREENS.EDIT_REQUEST.REPORT_FIELD]: ROUTES.EDIT_REPORT_FIELD_REQUEST.route, }, }, [SCREENS.RIGHT_MODAL.SIGN_IN]: { diff --git a/src/pages/EditReportFieldDatePage.tsx b/src/pages/EditReportFieldDatePage.tsx new file mode 100644 index 000000000000..5ee86b2bf8e6 --- /dev/null +++ b/src/pages/EditReportFieldDatePage.tsx @@ -0,0 +1,82 @@ +import React, {useCallback, useRef} from 'react'; +import {View} from 'react-native'; +import DatePicker from '@components/DatePicker'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +type EditReportFieldDatePageProps = { + /** Value of the policy report field */ + fieldValue: string; + + /** Name of the policy report field */ + fieldName: string; + + /** ID of the policy report field */ + fieldID: string; + + /** Callback to fire when the Save button is pressed */ + onSubmit: () => void; +}; + +function EditReportFieldDatePage({fieldName, onSubmit, fieldValue, fieldID}: EditReportFieldDatePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const inputRef = useRef(null); + + const validate = useCallback( + (value: Record) => { + const errors: Record = {}; + if (value[fieldID].trim() === '') { + errors[fieldID] = 'common.error.fieldRequired'; + } + return errors; + }, + [fieldID], + ); + + return ( + inputRef.current?.focus()} + testID={EditReportFieldDatePage.displayName} + > + + {/* @ts-expect-error TODO: TS migration */} + + + + + + + ); +} + +EditReportFieldDatePage.displayName = 'EditReportFieldDatePage'; + +export default EditReportFieldDatePage; diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx new file mode 100644 index 000000000000..a1d76e13f261 --- /dev/null +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -0,0 +1,82 @@ +import React, {useMemo, useState} from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import OptionsSelector from '@components/OptionsSelector'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type EditReportFieldDropdownPageProps = { + /** Value of the policy report field */ + fieldValue: string; + + /** Name of the policy report field */ + fieldName: string; + + /** Options of the policy report field */ + fieldOptions: string[]; + + /** Callback to fire when the Save button is pressed */ + onSubmit: () => void; +}; + +function EditReportFieldDropdownPage({fieldName, onSubmit, fieldValue, fieldOptions}: EditReportFieldDropdownPageProps) { + const [searchValue, setSearchValue] = useState(''); + const styles = useThemeStyles(); + const {getSafeAreaMargins} = useStyleUtils(); + const {translate} = useLocalize(); + + const sections = useMemo(() => { + const filteredOptions = fieldOptions.filter((option) => option.toLowerCase().includes(searchValue.toLowerCase())); + return [ + { + title: translate('common.recents'), + shouldShow: true, + data: [], + }, + { + title: translate('common.all'), + shouldShow: true, + data: filteredOptions.map((option) => ({ + text: option, + keyForList: option, + searchText: option, + tooltipText: option, + })), + }, + ]; + }, [fieldOptions, searchValue, translate]); + + return ( + + {({insets}) => ( + <> + + + + )} + + ); +} + +EditReportFieldDropdownPage.displayName = 'EditReportFieldDropdownPage'; + +export default EditReportFieldDropdownPage; diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx new file mode 100644 index 000000000000..d74582708995 --- /dev/null +++ b/src/pages/EditReportFieldPage.tsx @@ -0,0 +1,103 @@ +import React, {useEffect} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {PolicyReportFields, Report} from '@src/types/onyx'; +import EditReportFieldDatePage from './EditReportFieldDatePage'; +import EditReportFieldDropdownPage from './EditReportFieldDropdownPage'; +import EditReportFieldTextPage from './EditReportFieldTextPage'; + +type EditReportFieldPageOnyxProps = { + /** The report object for the expense report */ + report: OnyxEntry; + + /** Policy report fields */ + policyReportFields: OnyxEntry; +}; + +type EditReportFieldPageProps = EditReportFieldPageOnyxProps & { + /** Route from navigation */ + route: { + /** Params from the route */ + params: { + /** Which field we are editing */ + fieldID: string; + + /** reportID for the expense report */ + reportID: string; + + /** policyID for the expense report */ + policyID: string; + }; + }; +}; + +function EditReportFieldPage({route, report, policyReportFields}: EditReportFieldPageProps) { + const policyReportField = policyReportFields?.[route.params.fieldID]; + const reportFieldValue = report?.reportFields?.[policyReportField?.fieldID ?? '']; + + // Decides whether to allow or disallow editing a money request + useEffect(() => {}, []); + + if (policyReportField) { + if (policyReportField.type === 'text' || policyReportField.type === 'formula') { + return ( + {}} + /> + ); + } + + if (policyReportField.type === 'date') { + return ( + {}} + /> + ); + } + + if (policyReportField.type === 'dropdown') { + return ( + {}} + /> + ); + } + } + + return ( + + {}} + onLinkPress={() => {}} + /> + + ); +} + +EditReportFieldPage.displayName = 'EditReportFieldPage'; + +export default withOnyx({ + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + }, + policyReportFields: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${route.params.policyID}`, + }, +})(EditReportFieldPage); diff --git a/src/pages/EditReportFieldTextPage.tsx b/src/pages/EditReportFieldTextPage.tsx new file mode 100644 index 000000000000..b468861e9a27 --- /dev/null +++ b/src/pages/EditReportFieldTextPage.tsx @@ -0,0 +1,80 @@ +import React, {useCallback, useRef} from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +type EditReportFieldTextPageProps = { + /** Value of the policy report field */ + fieldValue: string; + + /** Name of the policy report field */ + fieldName: string; + + /** ID of the policy report field */ + fieldID: string; + + /** Callback to fire when the Save button is pressed */ + onSubmit: () => void; +}; + +function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, fieldID}: EditReportFieldTextPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const inputRef = useRef(null); + + const validate = useCallback( + (value: Record) => { + const errors: Record = {}; + if (value[fieldID].trim() === '') { + errors[fieldID] = 'common.error.fieldRequired'; + } + return errors; + }, + [fieldID], + ); + + return ( + inputRef.current?.focus()} + testID={EditReportFieldTextPage.displayName} + > + + {/* @ts-expect-error TODO: TS migration */} + + + + + + + ); +} + +EditReportFieldTextPage.displayName = 'EditReportFieldTextPage'; + +export default EditReportFieldTextPage; diff --git a/src/types/onyx/PolicyReportField.ts b/src/types/onyx/PolicyReportField.ts index 6a3be23c0a0b..a1724a9ff52f 100644 --- a/src/types/onyx/PolicyReportField.ts +++ b/src/types/onyx/PolicyReportField.ts @@ -24,5 +24,4 @@ type PolicyReportField = { }; type PolicyReportFields = Record; -export default PolicyReportField; -export type {PolicyReportFields}; +export type {PolicyReportField, PolicyReportFields}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 8cba351d0f45..abf52fc08d71 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -30,7 +30,7 @@ import type Policy from './Policy'; import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; -import type PolicyReportField from './PolicyReportField'; +import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; import type {PolicyTag, PolicyTags} from './PolicyTag'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; @@ -138,5 +138,6 @@ export type { WalletTransfer, ReportUserIsTyping, PolicyReportField, + PolicyReportFields, RecentlyUsedReportFields, };