From cef767ab13aad2c2c04fee5db681915e70c59976 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 9 Jan 2024 13:49:16 +0100 Subject: [PATCH 01/24] ref: started migratiing MoneyRequestConfirmationList --- ...st.js => MoneyRequestConfirmationList.tsx} | 195 ++++++++---------- 1 file changed, 91 insertions(+), 104 deletions(-) rename src/components/{MoneyRequestConfirmationList.js => MoneyRequestConfirmationList.tsx} (88%) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.tsx similarity index 88% rename from src/components/MoneyRequestConfirmationList.js rename to src/components/MoneyRequestConfirmationList.tsx index 13dce9337673..5d0c12944d78 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -4,8 +4,8 @@ import {isEmpty} from 'lodash'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useReducer, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; @@ -28,6 +28,7 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import * as OnyxTypes from '@src/types/onyx'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import categoryPropTypes from './categoryPropTypes'; import ConfirmedRoute from './ConfirmedRoute'; @@ -43,135 +44,115 @@ import tagPropTypes from './tagPropTypes'; import taxPropTypes from './taxPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; +import type {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; -const propTypes = { - /** Callback to inform parent modal of success */ - onConfirm: PropTypes.func, - - /** Callback to parent modal to send money */ - onSendMoney: PropTypes.func, - - /** Callback to inform a participant is selected */ - onSelectParticipant: PropTypes.func, - - /** Should we request a single or multiple participant selection from user */ - hasMultipleParticipants: PropTypes.bool.isRequired, - - /** IOU amount */ - iouAmount: PropTypes.number.isRequired, - - /** IOU comment */ - iouComment: PropTypes.string, - - /** IOU currency */ - iouCurrencyCode: PropTypes.string, - - /** IOU type */ - iouType: PropTypes.string, - - /** IOU date */ - iouCreated: PropTypes.string, - - /** IOU merchant */ - iouMerchant: PropTypes.string, - - /** IOU Category */ - iouCategory: PropTypes.string, +type MoneyRequestConfirmationListOnyxProps = { + iou?: OnyxTypes.IOU; + policyTaxRates?: any; + session: OnyxTypes.Session; + transaction?: OnyxTypes.Transaction; + mileageRate?: { + unit?: typeof CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES | typeof CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS; + rate?: number; + currency?: string; + }; + policyCategories?: any; + policyTags?: any; + policy?: OnyxEntry; +}; +type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & + WithCurrentUserPersonalDetailsProps & { + /** Callback to inform parent modal of success */ + onConfirm?: () => void; - /** IOU Tag */ - iouTag: PropTypes.string, + /** Callback to parent modal to send money */ + onSendMoney?: () => void; - /** IOU isBillable */ - iouIsBillable: PropTypes.bool, + /** Callback to inform a participant is selected */ + onSelectParticipant?: () => void; - /** Callback to toggle the billable state */ - onToggleBillable: PropTypes.func, + /** Should we request a single or multiple participant selection from user */ + hasMultipleParticipants: boolean; - /** Selected participants from MoneyRequestModal with login / accountID */ - selectedParticipants: PropTypes.arrayOf(optionPropTypes).isRequired, + /** IOU amount */ + iouAmount: number; - /** Payee of the money request with login */ - payeePersonalDetails: optionPropTypes, + /** IOU comment */ + iouComment?: string; - /** Can the participants be modified or not */ - canModifyParticipants: PropTypes.bool, + /** IOU currency */ + iouCurrencyCode?: string; - /** Should the list be read only, and not editable? */ - isReadOnly: PropTypes.bool, + /** IOU type */ + iouType?: string; - /** Depending on expense report or personal IOU report, respective bank account route */ - bankAccountRoute: PropTypes.string, + /** IOU date */ + iouCreated?: string; - ...withCurrentUserPersonalDetailsPropTypes, + /** IOU merchant */ + iouMerchant?: string; - /** Current user session */ - session: PropTypes.shape({ - email: PropTypes.string.isRequired, - }), + /** IOU Category */ + iouCategory?: string; - /** The policyID of the request */ - policyID: PropTypes.string, + /** IOU Tag */ + iouTag?: string; - /** The reportID of the request */ - reportID: PropTypes.string, + /** IOU isBillable */ + iouIsBillable?: boolean; - /** File path of the receipt */ - receiptPath: PropTypes.string, + /** Callback to toggle the billable state */ + onToggleBillable?: () => void; - /** File name of the receipt */ - receiptFilename: PropTypes.string, + /** Selected participants from MoneyRequestModal with login / accountID */ + selectedParticipants: OptionType[]; - /** List styles for OptionsSelector */ - listStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + /** Payee of the money request with login */ + payeePersonalDetails?: OptionType; - /** ID of the transaction that represents the money request */ - transactionID: PropTypes.string, + /** Can the participants be modified or not */ + canModifyParticipants?: boolean; - /** Transaction that represents the money request */ - transaction: transactionPropTypes, + /** Should the list be read only, and not editable? */ + isReadOnly?: boolean; - /** Unit and rate used for if the money request is a distance request */ - mileageRate: PropTypes.shape({ - /** Unit used to represent distance */ - unit: PropTypes.oneOf([CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]), + /** Depending on expense report or personal IOU report, respective bank account route */ + bankAccountRoute?: string; - /** Rate used to calculate the distance request amount */ - rate: PropTypes.number, + /** The policyID of the request */ + policyID?: string; - /** The currency of the rate */ - currency: PropTypes.string, - }), + /** The reportID of the request */ + reportID?: string; - /** Whether the money request is a distance request */ - isDistanceRequest: PropTypes.bool, + /** File path of the receipt */ + receiptPath?: string; - /** Whether the money request is a scan request */ - isScanRequest: PropTypes.bool, + /** File name of the receipt */ + receiptFilename?: string; - /** Whether we're editing a split bill */ - isEditingSplitBill: PropTypes.bool, + /** List styles for OptionsSelector */ + listStyles?: StyleProp; - /** Whether we should show the amount, date, and merchant fields. */ - shouldShowSmartScanFields: PropTypes.bool, + /** ID of the transaction that represents the money request */ + transactionID?: string; - /** A flag for verifying that the current report is a sub-report of a workspace chat */ - isPolicyExpenseChat: PropTypes.bool, + /** Whether the money request is a distance request */ + isDistanceRequest?: boolean; - /* Onyx Props */ - /** Collection of categories attached to a policy */ - policyCategories: PropTypes.objectOf(categoryPropTypes), + /** Whether the money request is a scan request */ + isScanRequest?: boolean; - /** Collection of tags attached to a policy */ - policyTags: tagPropTypes, + /** Whether we're editing a split bill */ + isEditingSplitBill?: boolean; - /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, + /** Whether we should show the amount, date, and merchant fields. */ + shouldShowSmartScanFields?: boolean; - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, -}; + /** A flag for verifying that the current report is a sub-report of a workspace chat */ + isPolicyExpenseChat?: boolean; + }; const defaultProps = { onConfirm: () => {}, @@ -208,12 +189,18 @@ const defaultProps = { policyTaxRates: {}, }; -function MoneyRequestConfirmationList(props) { +function MoneyRequestConfirmationList({ + onConfirm = () => {}, + onSendMoney = () => {}, + onSelectParticipant = () => {}, + iouType = CONST.IOU.TYPE.REQUEST, + iouCategory = '', + iouTag = '', + iouIsBillable = false, + onToggleBillable = () => {}, +}: MoneyRequestConfirmationListProps) { const theme = useTheme(); const styles = useThemeStyles(); - // Destructure functions from props to pass it as a dependecy to useCallback/useMemo hooks. - // Prop functions pass props itself as a "this" value to the function which means they change every time props change. - const {onSendMoney, onConfirm, onSelectParticipant} = props; const {translate, toLocaleDigit} = useLocalize(); const transaction = props.transaction; const {canUseViolations} = usePermissions(); From d76e0dfcb8c726158f817641b432d93c4e53d330 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 9 Jan 2024 18:49:30 +0100 Subject: [PATCH 02/24] ref: keep migrating --- .../MoneyRequestConfirmationList.tsx | 522 +++++++++--------- .../TextInput/BaseTextInput/types.ts | 2 +- src/libs/ReportUtils.ts | 2 +- src/types/onyx/Policy.ts | 13 +- 4 files changed, 270 insertions(+), 269 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 5d0c12944d78..87c6db42c3b5 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1,12 +1,12 @@ import {useIsFocused} from '@react-navigation/native'; import {format} from 'date-fns'; import {isEmpty} from 'lodash'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useReducer, useState} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useTheme from '@hooks/useTheme'; @@ -22,55 +22,49 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import type {Participant} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import * as OnyxTypes from '@src/types/onyx'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {MileageRate} from '@src/types/onyx/Policy'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; -import categoryPropTypes from './categoryPropTypes'; import ConfirmedRoute from './ConfirmedRoute'; import FormHelpMessage from './FormHelpMessage'; import Image from './Image'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; -import optionPropTypes from './optionPropTypes'; import OptionsSelector from './OptionsSelector'; import SettlementButton from './SettlementButton'; import ShowMoreButton from './ShowMoreButton'; import Switch from './Switch'; -import tagPropTypes from './tagPropTypes'; -import taxPropTypes from './taxPropTypes'; import Text from './Text'; -import transactionPropTypes from './transactionPropTypes'; import type {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersonalDetails'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; type MoneyRequestConfirmationListOnyxProps = { - iou?: OnyxTypes.IOU; - policyTaxRates?: any; + iou: OnyxEntry; + policyTaxRates: any; session: OnyxTypes.Session; - transaction?: OnyxTypes.Transaction; - mileageRate?: { - unit?: typeof CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES | typeof CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS; - rate?: number; - currency?: string; - }; - policyCategories?: any; - policyTags?: any; + transaction: OnyxTypes.Transaction; + mileageRate: OnyxEntry; + policyCategories: OnyxEntry; + policyTags: OnyxEntry; policy?: OnyxEntry; }; + type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & WithCurrentUserPersonalDetailsProps & { /** Callback to inform parent modal of success */ - onConfirm?: () => void; + onConfirm?: (selectedParticipants: Participant[]) => void; /** Callback to parent modal to send money */ - onSendMoney?: () => void; + onSendMoney?: (paymentMethod: ValueOf) => void; /** Callback to inform a participant is selected */ - onSelectParticipant?: () => void; + onSelectParticipant?: (option: Participant) => void; /** Should we request a single or multiple participant selection from user */ hasMultipleParticipants: boolean; @@ -85,7 +79,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & iouCurrencyCode?: string; /** IOU type */ - iouType?: string; + iouType?: ValueOf; /** IOU date */ iouCreated?: string; @@ -106,10 +100,10 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & onToggleBillable?: () => void; /** Selected participants from MoneyRequestModal with login / accountID */ - selectedParticipants: OptionType[]; + selectedParticipants: Participant[]; /** Payee of the money request with login */ - payeePersonalDetails?: OptionType; + payeePersonalDetails?: OnyxEntry; /** Can the participants be modified or not */ canModifyParticipants?: boolean; @@ -152,42 +146,11 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & /** A flag for verifying that the current report is a sub-report of a workspace chat */ isPolicyExpenseChat?: boolean; - }; -const defaultProps = { - onConfirm: () => {}, - onSendMoney: () => {}, - onSelectParticipant: () => {}, - iouType: CONST.IOU.TYPE.REQUEST, - iouCategory: '', - iouTag: '', - iouIsBillable: false, - onToggleBillable: () => {}, - payeePersonalDetails: null, - canModifyParticipants: false, - isReadOnly: false, - bankAccountRoute: '', - session: { - email: null, - }, - policyID: '', - reportID: '', - ...withCurrentUserPersonalDetailsDefaultProps, - receiptPath: '', - receiptFilename: '', - listStyles: [], - policyCategories: {}, - policyTags: {}, - transactionID: '', - transaction: {}, - mileageRate: {unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, currency: 'USD'}, - isDistanceRequest: false, - isScanRequest: false, - shouldShowSmartScanFields: true, - isPolicyExpenseChat: false, - iou: iouDefaultProps, - policyTaxRates: {}, -}; + hasSmartScanFailed?: boolean; + + reportActionID?: string; + }; function MoneyRequestConfirmationList({ onConfirm = () => {}, @@ -198,81 +161,114 @@ function MoneyRequestConfirmationList({ iouTag = '', iouIsBillable = false, onToggleBillable = () => {}, + payeePersonalDetails = null, + canModifyParticipants = false, + isReadOnly = false, + bankAccountRoute = '', + policyID = '', + reportID = '', + receiptPath = '', + receiptFilename = '', + transactionID = '', + mileageRate = {unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, currency: 'USD'}, + isDistanceRequest = false, + isScanRequest = false, + shouldShowSmartScanFields = true, + isPolicyExpenseChat = false, + transaction, + iouAmount, + policyTags, + policyCategories, + policy, + policyTaxRates, + iouCurrencyCode, + isEditingSplitBill, + hasSmartScanFailed, + iouMerchant, + currentUserPersonalDetails, + hasMultipleParticipants, + selectedParticipants, + session, + iou, + reportActionID, + iouCreated, + listStyles, + iouComment, }: MoneyRequestConfirmationListProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const transaction = props.transaction; const {canUseViolations} = usePermissions(); - const isTypeRequest = props.iouType === CONST.IOU.TYPE.REQUEST; - const isSplitBill = props.iouType === CONST.IOU.TYPE.SPLIT; - const isTypeSend = props.iouType === CONST.IOU.TYPE.SEND; + const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST; + const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; + const isTypeSend = iouType === CONST.IOU.TYPE.SEND; - const isSplitWithScan = isSplitBill && props.isScanRequest; + const isSplitWithScan = isSplitBill && isScanRequest; - const {unit, rate, currency} = props.mileageRate; - const distance = lodashGet(transaction, 'routes.route0.distance', 0); - const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0; + const distance = transaction?.routes?.route0.distance ?? 0; + const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; // A flag for showing the categories field - const shouldShowCategories = props.isPolicyExpenseChat && (props.iouCategory || OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories))); + const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); // Do not hide fields in case of send money request - const shouldShowAllFields = props.isDistanceRequest || shouldExpandFields || !props.shouldShowSmartScanFields || isTypeSend || props.isEditingSplitBill; + const shouldShowAllFields = isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || isEditingSplitBill; // In Send Money and Split Bill with Scan flow, we don't allow the Merchant or Date to be edited. For distance requests, don't show the merchant as there's already another "Distance" menu item const shouldShowDate = shouldShowAllFields && !isTypeSend && !isSplitWithScan; - const shouldShowMerchant = shouldShowAllFields && !isTypeSend && !props.isDistanceRequest && !isSplitWithScan; + const shouldShowMerchant = shouldShowAllFields && !isTypeSend && !isDistanceRequest && !isSplitWithScan; // Fetches the first tag list of the policy - const policyTag = PolicyUtils.getTag(props.policyTags); - const policyTagList = lodashGet(policyTag, 'tags', {}); - const policyTagListName = lodashGet(policyTag, 'name', translate('common.tag')); + const policyTag = PolicyUtils.getTag(policyTags); + const policyTagList = policyTag?.tags ?? {}; + const policyTagListName = policyTag?.name ?? translate('common.tag'); // A flag for showing the tags field - const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledOptions(_.values(policyTagList))); + const shouldShowTags = isPolicyExpenseChat && (iouTag || OptionsListUtils.hasEnabledOptions(Object.values(policyTagList))); // A flag for showing tax fields - tax rate and tax amount - const shouldShowTax = props.isPolicyExpenseChat && props.policy.isTaxTrackingEnabled; + const shouldShowTax = isPolicyExpenseChat && policy?.isTaxTrackingEnabled; // A flag for showing the billable field - const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true); + const shouldShowBillable = !policy?.disabledFields?.defaultBillable ?? true; const hasRoute = TransactionUtils.hasRoute(transaction); - const isDistanceRequestWithoutRoute = props.isDistanceRequest && !hasRoute; + const isDistanceRequestWithoutRoute = isDistanceRequest && !hasRoute; const formattedAmount = isDistanceRequestWithoutRoute ? translate('common.tbd') : CurrencyUtils.convertToDisplayString( - shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : props.iouAmount, - props.isDistanceRequest ? currency : props.iouCurrencyCode, + shouldCalculateDistanceAmount + ? DistanceRequestUtils.getDistanceRequestAmount(distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate?.rate) + : iouAmount, + isDistanceRequest ? mileageRate?.currency : iouCurrencyCode, ); - const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode); + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode); - const defaultTaxKey = props.policyTaxRates.defaultExternalID; - const defaultTaxName = (defaultTaxKey && `${props.policyTaxRates.taxes[defaultTaxKey].name} (${props.policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; - const taxRateTitle = (props.transaction.taxRate && props.transaction.taxRate.text) || defaultTaxName; + const defaultTaxKey = policyTaxRates.defaultExternalID; + const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; + const taxRateTitle = transaction.taxRate?.text || defaultTaxName; const isFocused = useIsFocused(); - const [formError, setFormError] = useState(''); + const [formError, setFormError] = useState(null); const [didConfirm, setDidConfirm] = useState(false); const [didConfirmSplit, setDidConfirmSplit] = useState(false); const shouldDisplayFieldError = useMemo(() => { - if (!props.isEditingSplitBill) { + if (!isEditingSplitBill) { return false; } - return (props.hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction)); - }, [props.isEditingSplitBill, props.hasSmartScanFailed, transaction, didConfirmSplit]); + return (!!hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction)); + }, [isEditingSplitBill, hasSmartScanFailed, transaction, didConfirmSplit]); - const isMerchantEmpty = !props.iouMerchant || props.iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - const shouldDisplayMerchantError = props.isPolicyExpenseChat && !props.isScanRequest && isMerchantEmpty; + const isMerchantEmpty = !iouMerchant || iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; + const shouldDisplayMerchantError = isPolicyExpenseChat && !isScanRequest && isMerchantEmpty; useEffect(() => { - if (shouldDisplayFieldError && props.hasSmartScanFailed) { + if (shouldDisplayFieldError && hasSmartScanFailed) { setFormError('iou.receiptScanningFailed'); return; } @@ -281,83 +277,80 @@ function MoneyRequestConfirmationList({ return; } // reset the form error whenever the screen gains or loses focus - setFormError(''); - }, [isFocused, transaction, shouldDisplayFieldError, props.hasSmartScanFailed, didConfirmSplit]); + setFormError(null); + }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit]); useEffect(() => { if (!shouldCalculateDistanceAmount) { return; } - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate); + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate.rate); IOU.setMoneyRequestAmount(amount); - }, [shouldCalculateDistanceAmount, distance, rate, unit]); + }, [shouldCalculateDistanceAmount, distance, mileageRate?.rate, mileageRate?.unit]); /** * Returns the participants with amount - * @param {Array} participants - * @returns {Array} */ const getParticipantsWithAmount = useCallback( - (participantsList) => { - const iouAmount = IOUUtils.calculateAmount(participantsList.length, props.iouAmount, props.iouCurrencyCode); + (participantsList: Participant[]) => { + const calculatedIouAmount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode ?? ''); return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, - props.iouAmount > 0 ? CurrencyUtils.convertToDisplayString(iouAmount, props.iouCurrencyCode) : '', + calculatedIouAmount > 0 ? CurrencyUtils.convertToDisplayString(calculatedIouAmount, iouCurrencyCode) : '', ); }, - [props.iouAmount, props.iouCurrencyCode], + [iouAmount, iouCurrencyCode], ); // If completing a split bill fails, set didConfirm to false to allow the user to edit the fields again - if (props.isEditingSplitBill && didConfirm) { + if (isEditingSplitBill && didConfirm) { setDidConfirm(false); } const splitOrRequestOptions = useMemo(() => { let text; - if (isSplitBill && props.iouAmount === 0) { + if (isSplitBill && iouAmount === 0) { text = translate('iou.split'); - } else if ((props.receiptPath && isTypeRequest) || isDistanceRequestWithoutRoute) { + } else if ((receiptPath && isTypeRequest) || isDistanceRequestWithoutRoute) { text = translate('iou.request'); - if (props.iouAmount !== 0) { - text = translate('iou.requestAmount', {amount: formattedAmount}); + if (iouAmount !== 0) { + text = translate('iou.requestAmount', {amount: Number(formattedAmount)}); } } else { const translationKey = isSplitBill ? 'iou.splitAmount' : 'iou.requestAmount'; - text = translate(translationKey, {amount: formattedAmount}); + text = translate(translationKey, {amount: Number(formattedAmount)}); } return [ { text: text[0].toUpperCase() + text.slice(1), - value: props.iouType, + value: iouType, }, ]; - }, [isSplitBill, isTypeRequest, props.iouType, props.iouAmount, props.receiptPath, formattedAmount, isDistanceRequestWithoutRoute, translate]); + }, [isSplitBill, isTypeRequest, iouType, iouAmount, receiptPath, formattedAmount, isDistanceRequestWithoutRoute, translate]); - const selectedParticipants = useMemo(() => _.filter(props.selectedParticipants, (participant) => participant.selected), [props.selectedParticipants]); - const payeePersonalDetails = useMemo(() => props.payeePersonalDetails || props.currentUserPersonalDetails, [props.payeePersonalDetails, props.currentUserPersonalDetails]); - const canModifyParticipants = !props.isReadOnly && props.canModifyParticipants && props.hasMultipleParticipants; - const shouldDisablePaidBySection = canModifyParticipants; + const selectedParticipantsMemo = useMemo(() => selectedParticipants.filter((participant) => participant.selected), [selectedParticipants]); + const payeePersonalDetailsMemo = useMemo(() => payeePersonalDetails ?? currentUserPersonalDetails, [payeePersonalDetails, currentUserPersonalDetails]); + const canModifyParticipantsValue = !isReadOnly && canModifyParticipants && hasMultipleParticipants; const optionSelectorSections = useMemo(() => { const sections = []; - const unselectedParticipants = _.filter(props.selectedParticipants, (participant) => !participant.selected); - if (props.hasMultipleParticipants) { - const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants); - let formattedParticipantsList = _.union(formattedSelectedParticipants, unselectedParticipants); + const unselectedParticipants = selectedParticipants.filter((participant) => !participant.selected); + if (hasMultipleParticipants) { + const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipantsMemo); + let formattedParticipantsList = [...new Set([...formattedSelectedParticipants, ...unselectedParticipants])]; - if (!canModifyParticipants) { - formattedParticipantsList = _.map(formattedParticipantsList, (participant) => ({ + if (!canModifyParticipantsValue) { + formattedParticipantsList = formattedParticipantsList.map((participant) => ({ ...participant, isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID), })); } - const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, props.iouAmount, props.iouCurrencyCode, true); + const myIOUAmount = IOUUtils.calculateAmount(selectedParticipantsMemo.length, iouAmount, iouCurrencyCode, true); const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( - payeePersonalDetails, - props.iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, props.iouCurrencyCode) : '', + payeePersonalDetailsMemo, + iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode) : '', ); sections.push( @@ -366,7 +359,7 @@ function MoneyRequestConfirmationList({ data: [formattedPayeeOption], shouldShow: true, indexOffset: 0, - isDisabled: shouldDisablePaidBySection, + isDisabled: canModifyParticipantsValue, }, { title: translate('moneyRequestConfirmationList.splitWith'), @@ -376,7 +369,7 @@ function MoneyRequestConfirmationList({ }, ); } else { - const formattedSelectedParticipants = _.map(props.selectedParticipants, (participant) => ({ + const formattedSelectedParticipants = selectedParticipants.map((participant) => ({ ...participant, isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID), })); @@ -389,50 +382,56 @@ function MoneyRequestConfirmationList({ } return sections; }, [ - props.selectedParticipants, - props.hasMultipleParticipants, - props.iouAmount, - props.iouCurrencyCode, - getParticipantsWithAmount, selectedParticipants, - payeePersonalDetails, + hasMultipleParticipants, + iouAmount, + iouCurrencyCode, + getParticipantsWithAmount, + payeePersonalDetailsMemo, translate, - shouldDisablePaidBySection, - canModifyParticipants, + canModifyParticipantsValue, + selectedParticipantsMemo, ]); const selectedOptions = useMemo(() => { - if (!props.hasMultipleParticipants) { + if (!hasMultipleParticipants) { return []; } - return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetails)]; - }, [selectedParticipants, props.hasMultipleParticipants, payeePersonalDetails]); + return [...selectedParticipantsMemo, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetailsMemo)]; + }, [selectedParticipantsMemo, hasMultipleParticipants, payeePersonalDetailsMemo]); useEffect(() => { - if (!props.isDistanceRequest) { + if (!isDistanceRequest) { return; } - const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate, currency, translate, toLocaleDigit); - IOU.setMoneyRequestMerchant_temporaryForRefactor(props.transactionID, distanceMerchant); - }, [hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, props.isDistanceRequest, props.transactionID]); + const distanceMerchant = DistanceRequestUtils.getDistanceMerchant( + hasRoute, + distance, + mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + mileageRate?.rate ?? 0, + mileageRate?.currency ?? 'USD', + translate, + toLocaleDigit, + ); + IOU.setMoneyRequestMerchant_temporaryForRefactor(transactionID, distanceMerchant); + }, [hasRoute, distance, mileageRate?.unit, mileageRate?.rate, mileageRate?.currency, translate, toLocaleDigit, isDistanceRequest, transactionID]); /** * @param {Object} option */ const selectParticipant = useCallback( - (option) => { + (option: Participant) => { // Return early if selected option is currently logged in user. - if (option.accountID === props.session.accountID) { + if (option.accountID === session?.accountID) { return; } onSelectParticipant(option); }, - [props.session.accountID, onSelectParticipant], + [session.accountID, onSelectParticipant], ); /** * Navigate to report details or profile of selected user - * @param {Object} option */ const navigateToReportOrUserDetail = (option) => { if (option.accountID) { @@ -444,19 +443,16 @@ function MoneyRequestConfirmationList({ } }; - /** - * @param {String} paymentMethod - */ const confirm = useCallback( - (paymentMethod) => { - if (_.isEmpty(selectedParticipants)) { + (paymentMethod: ValueOf) => { + if (selectedParticipantsMemo.length === 0) { return; } - if (props.iouCategory && props.iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { + if (iouCategory && iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { setFormError('iou.error.invalidCategoryLength'); return; } - if (props.iouType === CONST.IOU.TYPE.SEND) { + if (iouType === CONST.IOU.TYPE.SEND) { if (!paymentMethod) { return; } @@ -467,44 +463,44 @@ function MoneyRequestConfirmationList({ onSendMoney(paymentMethod); } else { // validate the amount for distance requests - const decimals = CurrencyUtils.getCurrencyDecimals(props.iouCurrencyCode); - if (props.isDistanceRequest && !isDistanceRequestWithoutRoute && !MoneyRequestUtils.validateAmount(String(props.iouAmount), decimals)) { + const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode); + if (isDistanceRequest && !isDistanceRequestWithoutRoute && !MoneyRequestUtils.validateAmount(String(iouAmount), decimals)) { setFormError('common.error.invalidAmount'); return; } - if (props.isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { + if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { setDidConfirmSplit(true); setFormError('iou.error.genericSmartscanFailureMessage'); return; } setDidConfirm(true); - onConfirm(selectedParticipants); + onConfirm(selectedParticipantsMemo); } }, [ - selectedParticipants, + selectedParticipantsMemo, + iouCategory, + iouType, onSendMoney, - onConfirm, - props.isEditingSplitBill, - props.iouType, - props.isDistanceRequest, - props.iouCategory, + iouCurrencyCode, + isDistanceRequest, isDistanceRequestWithoutRoute, - props.iouCurrencyCode, - props.iouAmount, + iouAmount, + isEditingSplitBill, transaction, + onConfirm, ], ); const footerContent = useMemo(() => { - if (props.isReadOnly) { + if (isReadOnly) { return; } - const shouldShowSettlementButton = props.iouType === CONST.IOU.TYPE.SEND; - const shouldDisableButton = selectedParticipants.length === 0 || shouldDisplayMerchantError; + const shouldShowSettlementButton = iouType === CONST.IOU.TYPE.SEND; + const shouldDisableButton = selectedParticipantsMemo.length === 0 || shouldDisplayMerchantError; const button = shouldShowSettlementButton ? ( confirm(value)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} @@ -539,7 +536,7 @@ function MoneyRequestConfirmationList({ return ( <> - {!_.isEmpty(formError) && ( + {formError && ( ); }, [ - props.isReadOnly, - props.iouType, - props.bankAccountRoute, - props.iouCurrencyCode, - props.policyID, - selectedParticipants.length, + isReadOnly, + iouType, + selectedParticipantsMemo.length, shouldDisplayMerchantError, confirm, + bankAccountRoute, + iouCurrencyCode, + policyID, splitOrRequestOptions, formError, styles.ph1, @@ -565,81 +562,80 @@ function MoneyRequestConfirmationList({ translate, ]); - const {image: receiptImage, thumbnail: receiptThumbnail} = - props.receiptPath && props.receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, props.receiptPath, props.receiptFilename) : {}; + const receiptData = ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename); return ( - {props.isDistanceRequest && ( + {isDistanceRequest && ( - + )} - {(receiptImage || receiptThumbnail) && ( + {(receiptData.image || receiptData.thumbnail) && ( )} - {props.shouldShowSmartScanFields && ( + {shouldShowSmartScanFields && ( { - if (props.isDistanceRequest) { + if (isDistanceRequest) { return; } - if (props.isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.AMOUNT)); + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.AMOUNT)); return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_AMOUNT.getRoute(props.iouType, props.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_AMOUNT.getRoute(iouType, reportID)); }} style={[styles.moneyRequestMenuItem, styles.mt2]} titleStyle={styles.moneyRequestConfirmationAmount} disabled={didConfirm} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} error={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? translate('common.error.enterAmount') : ''} /> )} { - if (props.isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION)); + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.DESCRIPTION)); return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_DESCRIPTION.getRoute(props.iouType, props.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_DESCRIPTION.getRoute(iouType, reportID)); }} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} disabled={didConfirm} - interactive={!props.isReadOnly} + interactive={!isReadOnly} numberOfLinesTitle={2} /> {!shouldShowAllFields && ( @@ -652,137 +648,133 @@ function MoneyRequestConfirmationList({ <> {shouldShowDate && ( { - if (props.isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.DATE)); + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.DATE)); return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_DATE.getRoute(props.iouType, props.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_DATE.getRoute(iouType, reportID)); }} disabled={didConfirm} - interactive={!props.isReadOnly} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + interactive={!isReadOnly} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} error={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? translate('common.error.enterDate') : ''} /> )} - {props.isDistanceRequest && ( + {isDistanceRequest && ( Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))} + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(iouType, reportID))} disabled={didConfirm || !isTypeRequest} - interactive={!props.isReadOnly} + interactive={!isReadOnly} /> )} {shouldShowMerchant && ( { - if (props.isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.MERCHANT)); + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.MERCHANT)); return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_MERCHANT.getRoute(props.iouType, props.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_MERCHANT.getRoute(iouType, reportID)); }} disabled={didConfirm} - interactive={!props.isReadOnly} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + interactive={!isReadOnly} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} error={shouldDisplayMerchantError || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction)) ? translate('common.error.enterMerchant') : ''} /> )} {shouldShowCategories && ( { - if (props.isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.CATEGORY)); + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.CATEGORY)); return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_CATEGORY.getRoute(props.iouType, props.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_CATEGORY.getRoute(iouType, reportID)); }} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} disabled={didConfirm} - interactive={!props.isReadOnly} - rightLabel={canUseViolations && Boolean(props.policy.requiresCategory) ? translate('common.required') : ''} + interactive={!isReadOnly} + rightLabel={canUseViolations && Boolean(policy?.requiresCategory) ? translate('common.required') : ''} /> )} {shouldShowTags && ( { - if (props.isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.TAG)); + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.TAG)); return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(props.iouType, props.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(iouType, reportID)); }} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} disabled={didConfirm} - interactive={!props.isReadOnly} - rightLabel={canUseViolations && Boolean(props.policy.requiresTag) ? translate('common.required') : ''} + interactive={!isReadOnly} + rightLabel={canUseViolations && Boolean(policy?.requiresTag) ? translate('common.required') : ''} /> )} {shouldShowTax && ( - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(props.iouType, props.transaction.transactionID, props.reportID, Navigation.getActiveRouteWithoutParams()), - ) + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) } disabled={didConfirm} - interactive={!props.isReadOnly} + interactive={!isReadOnly} /> )} {shouldShowTax && ( - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(props.iouType, props.transaction.transactionID, props.reportID, Navigation.getActiveRouteWithoutParams()), - ) + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) } disabled={didConfirm} - interactive={!props.isReadOnly} + interactive={!isReadOnly} /> )} {shouldShowBillable && ( - {translate('common.billable')} + {translate('common.billable')} )} @@ -792,13 +784,10 @@ function MoneyRequestConfirmationList({ ); } -MoneyRequestConfirmationList.propTypes = propTypes; -MoneyRequestConfirmationList.defaultProps = defaultProps; MoneyRequestConfirmationList.displayName = 'MoneyRequestConfirmationList'; export default compose( - withCurrentUserPersonalDetails, - withOnyx({ + withOnyx({ session: { key: ONYXKEYS.SESSION, }, @@ -825,4 +814,5 @@ export default compose( key: ONYXKEYS.IOU, }, }), + withCurrentUserPersonalDetails, )(MoneyRequestConfirmationList); diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 21875d4dcc64..5564f3919542 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -111,7 +111,7 @@ type CustomBaseTextInputProps = { autoCompleteType?: string; }; -type BaseTextInputRef = ForwardedRef>>; +type BaseTextInputRef = ForwardedRef>>; type BaseTextInputProps = CustomBaseTextInputProps & TextInputProps; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0d7658adf180..a90a1f10a2f4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4534,4 +4534,4 @@ export { shouldDisableThread, }; -export type {ExpenseOriginalMessage, OptionData, OptimisticChatReport}; +export type {ExpenseOriginalMessage, OptionData, OptimisticChatReport, Participant}; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 2cd686c115b4..63091d983840 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -10,6 +10,12 @@ type Rate = { currency: string; }; +type MileageRate = { + unit: Unit; + rate: number; + currency: string; +}; + type CustomUnit = { customUnitID?: string; name?: string; @@ -79,8 +85,13 @@ type Policy = { /** The employee list of the policy */ employeeList?: []; + + /** Whether tax tracking enabled for policy */ + isTaxTrackingEnabled?: boolean; + + disabledFields?: Record; }; export default Policy; -export type {Unit}; +export type {Unit, MileageRate}; From e0dc2e94a3912c965421e7304a8ec483373106ca Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 10 Jan 2024 08:07:25 +0100 Subject: [PATCH 03/24] ref: wip --- src/components/MoneyRequestConfirmationList.tsx | 5 +++-- src/types/onyx/PolicyTag.ts | 1 + src/types/onyx/Transaction.ts | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 87c6db42c3b5..741118167a26 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -240,7 +240,7 @@ function MoneyRequestConfirmationList({ ? translate('common.tbd') : CurrencyUtils.convertToDisplayString( shouldCalculateDistanceAmount - ? DistanceRequestUtils.getDistanceRequestAmount(distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate?.rate) + ? DistanceRequestUtils.getDistanceRequestAmount(distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate?.rate ?? 0) : iouAmount, isDistanceRequest ? mileageRate?.currency : iouCurrencyCode, ); @@ -248,6 +248,7 @@ function MoneyRequestConfirmationList({ const defaultTaxKey = policyTaxRates.defaultExternalID; const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const taxRateTitle = transaction.taxRate?.text || defaultTaxName; const isFocused = useIsFocused(); @@ -347,7 +348,7 @@ function MoneyRequestConfirmationList({ })); } - const myIOUAmount = IOUUtils.calculateAmount(selectedParticipantsMemo.length, iouAmount, iouCurrencyCode, true); + const myIOUAmount = IOUUtils.calculateAmount(selectedParticipantsMemo.length, iouAmount, iouCurrencyCode ?? '', true); const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( payeePersonalDetailsMemo, iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode) : '', diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 58a21dcf4df5..e9b909b1748a 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -8,6 +8,7 @@ type PolicyTag = { /** "General Ledger code" that corresponds to this tag in an accounting system. Similar to an ID. */ // eslint-disable-next-line @typescript-eslint/naming-convention 'GL Code': string; + tags: PolicyTags; }; type PolicyTags = Record; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 8b7e26280305..179f473dc71f 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -3,6 +3,15 @@ import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; import type RecentWaypoint from './RecentWaypoint'; +type TaxRate = { + text: string; + keyForList: string; + data: { + value: string; + }; + modifiedName?: string; +}; + type Waypoint = { /** The name associated with the address of the waypoint */ name?: string; @@ -18,6 +27,7 @@ type Waypoint = { }; type WaypointCollection = Record; + type Comment = { comment?: string; waypoints?: WaypointCollection; @@ -94,6 +104,8 @@ type Transaction = { /** If the transaction was made in a foreign currency, we send the original amount and currency */ originalAmount?: number; originalCurrency?: string; + taxAmount?: number; + taxRate?: TaxRate; }; export default Transaction; From 8946496c86a6ad6950c8e666b38400a262f737b0 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 10 Jan 2024 16:29:28 +0100 Subject: [PATCH 04/24] fix: wip --- src/ONYXKEYS.ts | 1 + src/components/MenuItem.tsx | 2 +- .../MoneyRequestConfirmationList.tsx | 108 +++++++++--------- src/libs/ReceiptUtils.ts | 5 +- src/libs/TransactionUtils.ts | 19 +-- src/types/onyx/Policy.ts | 4 + src/types/onyx/PolicyTaxRates.ts | 4 + src/types/onyx/index.ts | 4 + 8 files changed, 83 insertions(+), 64 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 89ddbdc06883..7fb29f348a44 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -439,6 +439,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; + [ONYXKEYS.COLLECTION.POLICY_TAX_RATE]: OnyxTypes.PolicyTaxRate; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index ce44db72598a..9b35a0963b14 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -204,7 +204,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { shouldBlockSelection?: boolean; /** Whether should render title as HTML or as Text */ - shouldParseTitle?: false; + shouldParseTitle?: boolean; /** Should check anonymous user in onPress function */ shouldCheckActionAllowedOnPress?: boolean; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 741118167a26..b0a9ce1d7a91 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -11,7 +11,6 @@ import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; @@ -46,13 +45,12 @@ import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; type MoneyRequestConfirmationListOnyxProps = { iou: OnyxEntry; - policyTaxRates: any; - session: OnyxTypes.Session; - transaction: OnyxTypes.Transaction; + policyTaxRates: OnyxEntry; + session: OnyxEntry; mileageRate: OnyxEntry; - policyCategories: OnyxEntry; + policyCategories: OnyxEntry; policyTags: OnyxEntry; - policy?: OnyxEntry; + policy: OnyxEntry; }; type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & @@ -150,6 +148,8 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & hasSmartScanFailed?: boolean; reportActionID?: string; + + transaction: OnyxTypes.Transaction; }; function MoneyRequestConfirmationList({ @@ -210,7 +210,7 @@ function MoneyRequestConfirmationList({ const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; // A flag for showing the categories field - const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories))); // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); @@ -244,12 +244,12 @@ function MoneyRequestConfirmationList({ : iouAmount, isDistanceRequest ? mileageRate?.currency : iouCurrencyCode, ); - const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode); + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); - const defaultTaxKey = policyTaxRates.defaultExternalID; - const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; + const defaultTaxKey = policyTaxRates?.defaultExternalID; + const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates?.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const taxRateTitle = transaction.taxRate?.text || defaultTaxName; + const taxRateTitle = transaction?.taxRate?.text || defaultTaxName; const isFocused = useIsFocused(); const [formError, setFormError] = useState(null); @@ -286,7 +286,7 @@ function MoneyRequestConfirmationList({ return; } - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate.rate); + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate?.rate); IOU.setMoneyRequestAmount(amount); }, [shouldCalculateDistanceAmount, distance, mileageRate?.rate, mileageRate?.unit]); @@ -398,8 +398,10 @@ function MoneyRequestConfirmationList({ if (!hasMultipleParticipants) { return []; } - return [...selectedParticipantsMemo, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetailsMemo)]; - }, [selectedParticipantsMemo, hasMultipleParticipants, payeePersonalDetailsMemo]); + // TODO: check if this is needed + const myIOUAmount = IOUUtils.calculateAmount(selectedParticipantsMemo.length, iouAmount, iouCurrencyCode ?? '', true); + return [...selectedParticipantsMemo, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetailsMemo, String(myIOUAmount))]; + }, [hasMultipleParticipants, selectedParticipantsMemo, iouAmount, iouCurrencyCode, payeePersonalDetailsMemo]); useEffect(() => { if (!isDistanceRequest) { @@ -428,14 +430,14 @@ function MoneyRequestConfirmationList({ } onSelectParticipant(option); }, - [session.accountID, onSelectParticipant], + [session?.accountID, onSelectParticipant], ); /** * Navigate to report details or profile of selected user */ - const navigateToReportOrUserDetail = (option) => { - if (option.accountID) { + const navigateToReportOrUserDetail = (option: Participant | OnyxTypes.Report) => { + if ('accountID' in option) { const activeRoute = Navigation.getActiveRouteWithoutParams(); Navigation.navigate(ROUTES.PROFILE.getRoute(option.accountID, activeRoute)); @@ -505,6 +507,7 @@ function MoneyRequestConfirmationList({ const button = shouldShowSettlementButton ? ( - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()), + ) } disabled={didConfirm} interactive={!isReadOnly} @@ -758,11 +764,13 @@ function MoneyRequestConfirmationList({ - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()), + ) } disabled={didConfirm} interactive={!isReadOnly} @@ -787,33 +795,29 @@ function MoneyRequestConfirmationList({ MoneyRequestConfirmationList.displayName = 'MoneyRequestConfirmationList'; -export default compose( - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - policyCategories: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - }, - policyTags: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - }, - mileageRate: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - selector: DistanceRequestUtils.getDefaultMileageRate, - }, - splitTransactionDraft: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, - }, - policy: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - }, - policyTaxRates: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, - }, - iou: { - key: ONYXKEYS.IOU, - }, - }), - withCurrentUserPersonalDetails, -)(MoneyRequestConfirmationList); +const MoneyRequestConfirmationListWithCurrentUserPersonalDetails = withCurrentUserPersonalDetails(MoneyRequestConfirmationList); + +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + policyCategories: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + }, + policyTags: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + }, + mileageRate: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + selector: DistanceRequestUtils.getDefaultMileageRate, + }, + policy: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + }, + policyTaxRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, + }, + iou: { + key: ONYXKEYS.IOU, + }, +})(MoneyRequestConfirmationListWithCurrentUserPersonalDetails); diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index bcba68a3a0bd..7858c3d7277d 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -1,5 +1,6 @@ import Str from 'expensify-common/lib/str'; import type {ImageSourcePropType} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import ReceiptDoc from '@assets/images/receipt-doc.png'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; import ReceiptHTML from '@assets/images/receipt-html.png'; @@ -28,7 +29,7 @@ type FileNameAndExtension = { * @param receiptPath * @param receiptFileName */ -function getThumbnailAndImageURIs(transaction: Transaction, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { +function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { // URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg const path = transaction?.receipt?.source ?? receiptPath ?? ''; // filename of uploaded image or last part of remote URI @@ -39,7 +40,7 @@ function getThumbnailAndImageURIs(transaction: Transaction, receiptPath: string if (!Object.hasOwn(transaction?.pendingFields ?? {}, 'waypoints')) { if (hasEReceipt) { - return {thumbnail: null, image: ROUTES.ERECEIPT.getRoute(transaction.transactionID), transaction}; + return {thumbnail: null, image: ROUTES.ERECEIPT.getRoute(transaction?.transactionID), transaction}; } // For local files, we won't have a thumbnail yet diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index c34a6753c1d5..48a7d4065700 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -139,23 +139,24 @@ function hasReceipt(transaction: Transaction | undefined | null): boolean { return !!transaction?.receipt?.state || hasEReceipt(transaction); } -function isMerchantMissing(transaction: Transaction) { - const isMerchantEmpty = transaction.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction.merchant === ''; +function isMerchantMissing(transaction: OnyxEntry) { + const isMerchantEmpty = transaction?.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction?.merchant === ''; - const isModifiedMerchantEmpty = !transaction.modifiedMerchant || transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction.modifiedMerchant === ''; + const isModifiedMerchantEmpty = + !transaction?.modifiedMerchant || transaction?.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction?.modifiedMerchant === ''; return isMerchantEmpty && isModifiedMerchantEmpty; } -function isAmountMissing(transaction: Transaction) { - return transaction.amount === 0 && (!transaction.modifiedAmount || transaction.modifiedAmount === 0); +function isAmountMissing(transaction: OnyxEntry) { + return transaction?.amount === 0 && (!transaction?.modifiedAmount || transaction?.modifiedAmount === 0); } -function isCreatedMissing(transaction: Transaction) { - return transaction.created === '' && (!transaction.created || transaction.modifiedCreated === ''); +function isCreatedMissing(transaction: OnyxEntry) { + return transaction?.created === '' && (!transaction?.created || transaction?.modifiedCreated === ''); } -function areRequiredFieldsEmpty(transaction: Transaction): boolean { +function areRequiredFieldsEmpty(transaction: OnyxEntry): boolean { const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`] ?? null; const isFromExpenseReport = parentReport?.type === CONST.REPORT.TYPE.EXPENSE; return (isFromExpenseReport && isMerchantMissing(transaction)) || isAmountMissing(transaction) || isCreatedMissing(transaction); @@ -434,7 +435,7 @@ function hasMissingSmartscanFields(transaction: OnyxEntry): boolean /** * Check if the transaction has a defined route */ -function hasRoute(transaction: Transaction): boolean { +function hasRoute(transaction: OnyxEntry): boolean { return !!transaction?.routes?.route0?.geometry?.coordinates; } diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 63091d983840..af65dd57607f 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -90,6 +90,10 @@ type Policy = { isTaxTrackingEnabled?: boolean; disabledFields?: Record; + + requiresCategory?: boolean; + + requiresTag?: boolean; }; export default Policy; diff --git a/src/types/onyx/PolicyTaxRates.ts b/src/types/onyx/PolicyTaxRates.ts index d549b620f51f..492cf37a3181 100644 --- a/src/types/onyx/PolicyTaxRates.ts +++ b/src/types/onyx/PolicyTaxRates.ts @@ -7,6 +7,10 @@ type PolicyTaxRate = { /** Whether the tax is disabled */ isDisabled?: boolean; + + defaultExternalID: string; + + taxes: PolicyTaxRates; }; type PolicyTaxRates = Record; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 7bd9c321be5e..2ef702226384 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -32,6 +32,8 @@ import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type PolicyReportField from './PolicyReportField'; import type {PolicyTag, PolicyTags} from './PolicyTag'; +import type {PolicyTaxRates} from './PolicyTaxRates'; +import type PolicyTaxRate from './PolicyTaxRates'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -137,4 +139,6 @@ export type { ReportUserIsTyping, PolicyReportField, RecentlyUsedReportFields, + PolicyTaxRate, + PolicyTaxRates, }; From ef6ae5f2735ca7cce0441454828f70b3be5210a3 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 11 Jan 2024 15:30:18 +0100 Subject: [PATCH 05/24] Update policyCategories type in MoneyRequestConfirmationList --- src/ONYXKEYS.ts | 2 +- .../MoneyRequestConfirmationList.tsx | 60 +++++++++---------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 45f70d1864bc..f5e8bba63361 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -437,7 +437,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; [ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; - [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; + [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; [ONYXKEYS.COLLECTION.POLICY_TAX_RATE]: OnyxTypes.PolicyTaxRate; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b0a9ce1d7a91..9c46144d3a56 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -46,9 +46,8 @@ import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; type MoneyRequestConfirmationListOnyxProps = { iou: OnyxEntry; policyTaxRates: OnyxEntry; - session: OnyxEntry; mileageRate: OnyxEntry; - policyCategories: OnyxEntry; + policyCategories: OnyxEntry; policyTags: OnyxEntry; policy: OnyxEntry; }; @@ -210,7 +209,8 @@ function MoneyRequestConfirmationList({ const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; // A flag for showing the categories field - const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories))); + const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); @@ -286,7 +286,7 @@ function MoneyRequestConfirmationList({ return; } - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate?.rate); + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate?.rate ?? 0); IOU.setMoneyRequestAmount(amount); }, [shouldCalculateDistanceAmount, distance, mileageRate?.rate, mileageRate?.unit]); @@ -299,7 +299,8 @@ function MoneyRequestConfirmationList({ return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, calculatedIouAmount > 0 ? CurrencyUtils.convertToDisplayString(calculatedIouAmount, iouCurrencyCode) : '', - ); + // TODO: Remove assertion after OptionsListUtils will be migrated + ) as Participant[]; }, [iouAmount, iouCurrencyCode], ); @@ -795,29 +796,26 @@ function MoneyRequestConfirmationList({ MoneyRequestConfirmationList.displayName = 'MoneyRequestConfirmationList'; -const MoneyRequestConfirmationListWithCurrentUserPersonalDetails = withCurrentUserPersonalDetails(MoneyRequestConfirmationList); - -export default withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - policyCategories: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - }, - policyTags: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - }, - mileageRate: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - selector: DistanceRequestUtils.getDefaultMileageRate, - }, - policy: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - }, - policyTaxRates: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, - }, - iou: { - key: ONYXKEYS.IOU, - }, -})(MoneyRequestConfirmationListWithCurrentUserPersonalDetails); +export default withCurrentUserPersonalDetails( + withOnyx({ + policyCategories: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + }, + policyTags: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + }, + mileageRate: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + selector: DistanceRequestUtils.getDefaultMileageRate, + }, + policy: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + }, + policyTaxRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, + }, + iou: { + key: ONYXKEYS.IOU, + }, + })(MoneyRequestConfirmationList), +); From 441e437de6ab31f4b80a4ccf5f4b8ddddfcf47c7 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 15 Jan 2024 08:40:17 +0100 Subject: [PATCH 06/24] fix: wip --- src/components/MoneyRequestConfirmationList.tsx | 2 +- src/components/TextInput/BaseTextInput/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 9c46144d3a56..5e292e310313 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -646,7 +646,7 @@ function MoneyRequestConfirmationList({ /> {!shouldShowAllFields && ( )} diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 5564f3919542..21875d4dcc64 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -111,7 +111,7 @@ type CustomBaseTextInputProps = { autoCompleteType?: string; }; -type BaseTextInputRef = ForwardedRef>>; +type BaseTextInputRef = ForwardedRef>>; type BaseTextInputProps = CustomBaseTextInputProps & TextInputProps; From 814715b85a10ba75bc517946404639a7b96d4939 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 15 Jan 2024 09:02:53 +0100 Subject: [PATCH 07/24] fix: wip --- src/components/MoneyRequestConfirmationList.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 5e292e310313..c66a58625300 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -44,11 +44,22 @@ import type {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersona import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; type MoneyRequestConfirmationListOnyxProps = { + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: OnyxEntry; + + /** Collection of tax rates attached to a policy */ policyTaxRates: OnyxEntry; + + /** Unit and rate used for if the money request is a distance request */ mileageRate: OnyxEntry; + + /** Collection of categories attached to a policy */ policyCategories: OnyxEntry; + + /** Collection of tags attached to a policy */ policyTags: OnyxEntry; + + /** The policy of root parent report */ policy: OnyxEntry; }; @@ -169,7 +180,7 @@ function MoneyRequestConfirmationList({ receiptPath = '', receiptFilename = '', transactionID = '', - mileageRate = {unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, currency: 'USD'}, + mileageRate, isDistanceRequest = false, isScanRequest = false, shouldShowSmartScanFields = true, @@ -399,7 +410,6 @@ function MoneyRequestConfirmationList({ if (!hasMultipleParticipants) { return []; } - // TODO: check if this is needed const myIOUAmount = IOUUtils.calculateAmount(selectedParticipantsMemo.length, iouAmount, iouCurrencyCode ?? '', true); return [...selectedParticipantsMemo, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetailsMemo, String(myIOUAmount))]; }, [hasMultipleParticipants, selectedParticipantsMemo, iouAmount, iouCurrencyCode, payeePersonalDetailsMemo]); @@ -420,9 +430,6 @@ function MoneyRequestConfirmationList({ IOU.setMoneyRequestMerchant_temporaryForRefactor(transactionID, distanceMerchant); }, [hasRoute, distance, mileageRate?.unit, mileageRate?.rate, mileageRate?.currency, translate, toLocaleDigit, isDistanceRequest, transactionID]); - /** - * @param {Object} option - */ const selectParticipant = useCallback( (option: Participant) => { // Return early if selected option is currently logged in user. From e5921cee015660fa3a5b59b453a9617dbeefa53a Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 17 Jan 2024 10:39:47 +0100 Subject: [PATCH 08/24] fix: resolve comments --- src/components/ConfirmedRoute.tsx | 6 +- .../MoneyRequestConfirmationList.tsx | 65 ++++++++++++------- src/libs/ReportUtils.ts | 5 +- src/types/onyx/OriginalMessage.ts | 4 +- src/types/onyx/Policy.ts | 3 + src/types/onyx/PolicyTaxRates.ts | 2 + 6 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx index c01f7c6250f4..8a3b39305dba 100644 --- a/src/components/ConfirmedRoute.tsx +++ b/src/components/ConfirmedRoute.tsx @@ -30,13 +30,13 @@ type ConfirmedRoutePropsOnyxProps = { type ConfirmedRouteProps = ConfirmedRoutePropsOnyxProps & { /** Transaction that stores the distance request data */ - transaction: Transaction; + transaction: Transaction | undefined; }; function ConfirmedRoute({mapboxAccessToken, transaction}: ConfirmedRouteProps) { const {isOffline} = useNetwork(); - const {route0: route} = transaction.routes ?? {}; - const waypoints = transaction.comment?.waypoints ?? {}; + const {route0: route} = transaction?.routes ?? {}; + const waypoints = transaction?.comment?.waypoints ?? {}; const coordinates = route?.geometry?.coordinates ?? []; const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index c66a58625300..f1d042b29cad 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1,6 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; import {format} from 'date-fns'; -import {isEmpty} from 'lodash'; import React, {useCallback, useEffect, useMemo, useReducer, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; @@ -29,6 +28,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; +import type {PaymentType} from '@src/types/onyx/OriginalMessage'; import type {MileageRate} from '@src/types/onyx/Policy'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import ConfirmedRoute from './ConfirmedRoute'; @@ -43,6 +43,15 @@ import Text from './Text'; import type {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; +type Option = Partial; + +type CategorySection = { + title: string | undefined; + shouldShow: boolean; + indexOffset: number; + data: Option[]; +}; + type MoneyRequestConfirmationListOnyxProps = { /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: OnyxEntry; @@ -69,7 +78,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & onConfirm?: (selectedParticipants: Participant[]) => void; /** Callback to parent modal to send money */ - onSendMoney?: (paymentMethod: ValueOf) => void; + onSendMoney?: (paymentMethod: PaymentType) => void; /** Callback to inform a participant is selected */ onSelectParticipant?: (option: Participant) => void; @@ -155,11 +164,14 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & /** A flag for verifying that the current report is a sub-report of a workspace chat */ isPolicyExpenseChat?: boolean; + /** Whether there is smartscan failed */ hasSmartScanFailed?: boolean; + /** ID of the report action */ reportActionID?: string; - transaction: OnyxTypes.Transaction; + /** Transaction object */ + transaction?: OnyxTypes.Transaction; }; function MoneyRequestConfirmationList({ @@ -171,7 +183,7 @@ function MoneyRequestConfirmationList({ iouTag = '', iouIsBillable = false, onToggleBillable = () => {}, - payeePersonalDetails = null, + payeePersonalDetails, canModifyParticipants = false, isReadOnly = false, bankAccountRoute = '', @@ -245,7 +257,7 @@ function MoneyRequestConfirmationList({ // A flag for showing the billable field const shouldShowBillable = !policy?.disabledFields?.defaultBillable ?? true; - const hasRoute = TransactionUtils.hasRoute(transaction); + const hasRoute = TransactionUtils.hasRoute(transaction ?? null); const isDistanceRequestWithoutRoute = isDistanceRequest && !hasRoute; const formattedAmount = isDistanceRequestWithoutRoute ? translate('common.tbd') @@ -273,7 +285,7 @@ function MoneyRequestConfirmationList({ return false; } - return (!!hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction)); + return (!!hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction ?? null)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)); }, [isEditingSplitBill, hasSmartScanFailed, transaction, didConfirmSplit]); const isMerchantEmpty = !iouMerchant || iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; @@ -310,7 +322,7 @@ function MoneyRequestConfirmationList({ return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, calculatedIouAmount > 0 ? CurrencyUtils.convertToDisplayString(calculatedIouAmount, iouCurrencyCode) : '', - // TODO: Remove assertion after OptionsListUtils will be migrated + // TODO: Remove the assertion once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. ) as Participant[]; }, [iouAmount, iouCurrencyCode], @@ -346,7 +358,7 @@ function MoneyRequestConfirmationList({ const payeePersonalDetailsMemo = useMemo(() => payeePersonalDetails ?? currentUserPersonalDetails, [payeePersonalDetails, currentUserPersonalDetails]); const canModifyParticipantsValue = !isReadOnly && canModifyParticipants && hasMultipleParticipants; - const optionSelectorSections = useMemo(() => { + const optionSelectorSections: CategorySection[] = useMemo(() => { const sections = []; const unselectedParticipants = selectedParticipants.filter((participant) => !participant.selected); if (hasMultipleParticipants) { @@ -445,17 +457,17 @@ function MoneyRequestConfirmationList({ * Navigate to report details or profile of selected user */ const navigateToReportOrUserDetail = (option: Participant | OnyxTypes.Report) => { - if ('accountID' in option) { + if ('accountID' in option && option.accountID) { const activeRoute = Navigation.getActiveRouteWithoutParams(); Navigation.navigate(ROUTES.PROFILE.getRoute(option.accountID, activeRoute)); - } else if (option.reportID) { + } else if ('reportID' in option && option.reportID) { Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(option.reportID)); } }; const confirm = useCallback( - (paymentMethod: ValueOf) => { + (paymentMethod: PaymentType) => { if (selectedParticipantsMemo.length === 0) { return; } @@ -480,7 +492,7 @@ function MoneyRequestConfirmationList({ return; } - if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { + if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)) { setDidConfirmSplit(true); setFormError('iou.error.genericSmartscanFailureMessage'); return; @@ -540,7 +552,7 @@ function MoneyRequestConfirmationList({ pressOnEnter isDisabled={shouldDisableButton} // eslint-disable-next-line @typescript-eslint/naming-convention - onPress={(_event, value) => confirm(value)} + onPress={(_, value) => confirm(value)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} /> @@ -574,7 +586,7 @@ function MoneyRequestConfirmationList({ translate, ]); - const receiptData = ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename); + const receiptData = ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename); return ( // @ts-expect-error TODO: Remove this once OptionsSelector (https://github.com/Expensify/App/issues/25125) is migrated to TypeScript. @@ -626,11 +638,11 @@ function MoneyRequestConfirmationList({ } Navigation.navigate(ROUTES.MONEY_REQUEST_AMOUNT.getRoute(iouType, reportID)); }} - style={{...styles.moneyRequestMenuItem, ...styles.mt2}} + style={[styles.moneyRequestMenuItem, styles.mt2]} titleStyle={styles.moneyRequestConfirmationAmount} disabled={didConfirm} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - error={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? translate('common.error.enterAmount') : ''} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction ?? null) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction ?? null) ? translate('common.error.enterAmount') : ''} /> )} )} {isDistanceRequest && ( @@ -707,8 +720,12 @@ function MoneyRequestConfirmationList({ }} disabled={didConfirm} interactive={!isReadOnly} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - error={shouldDisplayMerchantError || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction)) ? translate('common.error.enterMerchant') : ''} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction ?? null) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={ + shouldDisplayMerchantError || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction ?? null)) + ? translate('common.error.enterMerchant') + : '' + } /> )} {shouldShowCategories && ( @@ -747,7 +764,7 @@ function MoneyRequestConfirmationList({ style={styles.moneyRequestMenuItem} disabled={didConfirm} interactive={!isReadOnly} - rightLabel={canUseViolations && Boolean(policy?.requiresTag) ? translate('common.required') : ''} + rightLabel={canUseViolations && !!policy?.requiresTag ? translate('common.required') : ''} /> )} diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5b3347dbfa3e..ecccb451b17f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -16,12 +16,11 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, Report, ReportAction, ReportMetadata, Session, Transaction} from '@src/types/onyx'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type {IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage'; +import type {IOUMessage, OriginalMessageActionName, OriginalMessageCreated, PaymentType} from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {NotificationPreference} from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; import type {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; -import type DeepValueOf from '@src/types/utils/DeepValueOf'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject, isNotEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -2698,7 +2697,7 @@ function buildOptimisticIOUReportAction( comment: string, participants: Participant[], transactionID: string, - paymentType: DeepValueOf, + paymentType: PaymentType, iouReportID = '', isSettlingUp = false, isSendMoneyFlow = false, diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index f10696ced00f..5549b96acdb2 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -2,6 +2,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; +type PaymentType = DeepValueOf; type ActionName = DeepValueOf; type OriginalMessageActionName = | 'ADDCOMMENT' @@ -43,7 +44,7 @@ type IOUMessage = { lastModified?: string; participantAccountIDs?: number[]; type: ValueOf; - paymentType?: DeepValueOf; + paymentType?: PaymentType; /** Only exists when we are sending money */ IOUDetails?: IOUDetails; }; @@ -280,4 +281,5 @@ export type { OriginalMessageIOU, OriginalMessageCreated, OriginalMessageAddComment, + PaymentType, }; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index a2182298acdf..19cecf3b9e6d 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -92,10 +92,13 @@ type Policy = { /** Whether tax tracking enabled for policy */ isTaxTrackingEnabled?: boolean; + /** List of fields which should disabled for the policy */ disabledFields?: Record; + /** Whether or not the policy requires categories */ requiresCategory?: boolean; + /** Whether or not the policy requires tags */ requiresTag?: boolean; }; diff --git a/src/types/onyx/PolicyTaxRates.ts b/src/types/onyx/PolicyTaxRates.ts index 492cf37a3181..5709ce6338ab 100644 --- a/src/types/onyx/PolicyTaxRates.ts +++ b/src/types/onyx/PolicyTaxRates.ts @@ -8,8 +8,10 @@ type PolicyTaxRate = { /** Whether the tax is disabled */ isDisabled?: boolean; + /** Default policy tax ID */ defaultExternalID: string; + /** List of tax names and values */ taxes: PolicyTaxRates; }; From d851908c62970b61180eefafd74bd43c3a6bdc5d Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 18 Jan 2024 13:40:22 +0100 Subject: [PATCH 09/24] fix: typecheck --- src/components/ButtonWithDropdownMenu.tsx | 4 ++-- src/components/MoneyRequestConfirmationList.tsx | 4 ++-- src/components/PopoverMenu.tsx | 2 +- src/libs/DistanceRequestUtils.ts | 12 +++--------- src/types/onyx/IOU.ts | 2 ++ src/types/onyx/Policy.ts | 2 +- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/components/ButtonWithDropdownMenu.tsx b/src/components/ButtonWithDropdownMenu.tsx index 466c68229a32..fc33c373ad45 100644 --- a/src/components/ButtonWithDropdownMenu.tsx +++ b/src/components/ButtonWithDropdownMenu.tsx @@ -19,7 +19,7 @@ import PopoverMenu from './PopoverMenu'; type DropdownOption = { value: string; text: string; - icon: IconAsset; + icon?: IconAsset; iconWidth?: number; iconHeight?: number; iconDescription?: string; @@ -55,7 +55,7 @@ type ButtonWithDropdownMenuProps = { anchorAlignment?: AnchorAlignment; /* ref for the button */ - buttonRef: RefObject; + buttonRef?: RefObject; }; function ButtonWithDropdownMenu({ diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index f1d042b29cad..3712e928a5e1 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -20,7 +20,6 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import type {Participant} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; @@ -28,6 +27,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; +import type {Participant} from '@src/types/onyx/IOU'; import type {PaymentType} from '@src/types/onyx/OriginalMessage'; import type {MileageRate} from '@src/types/onyx/Policy'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; @@ -552,7 +552,7 @@ function MoneyRequestConfirmationList({ pressOnEnter isDisabled={shouldDisableButton} // eslint-disable-next-line @typescript-eslint/naming-convention - onPress={(_, value) => confirm(value)} + onPress={(_, value) => confirm(value as PaymentType)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} /> diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 17b1a119671a..ae995c899521 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -17,7 +17,7 @@ import Text from './Text'; type PopoverMenuItem = { /** An icon element displayed on the left side */ - icon: IconAsset; + icon?: IconAsset; /** Text label */ text: string; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index c92e9bfd3f67..6c7444ea12c7 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,17 +1,11 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; -import type {Unit} from '@src/types/onyx/Policy'; +import type {MileageRate, Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import * as CurrencyUtils from './CurrencyUtils'; import * as PolicyUtils from './PolicyUtils'; -type DefaultMileageRate = { - rate?: number; - currency?: string; - unit: Unit; -}; - /** * Retrieves the default mileage rate based on a given policy. * @@ -22,7 +16,7 @@ type DefaultMileageRate = { * @returns [currency] - The currency associated with the rate. * @returns [unit] - The unit of measurement for the distance. */ -function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate | null { +function getDefaultMileageRate(policy: OnyxEntry): MileageRate | null { if (!policy?.customUnits) { return null; } @@ -39,7 +33,7 @@ function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate | return { rate: distanceRate.rate, - currency: distanceRate.currency, + currency: distanceRate.currency ?? 'USD', unit: distanceUnit.attributes.unit, }; } diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts index a89b0d4530ef..220af7005c45 100644 --- a/src/types/onyx/IOU.ts +++ b/src/types/onyx/IOU.ts @@ -23,3 +23,5 @@ type IOU = { }; export default IOU; + +export type {Participant}; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index ee8c69fa3f21..e5681e53dc15 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -19,7 +19,7 @@ type Attributes = { type MileageRate = { unit: Unit; - rate: number; + rate?: number; currency: string; }; From d4365b1b94081a9a98524fa36b6c21b6e5ce82b1 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 30 Jan 2024 10:57:10 +0100 Subject: [PATCH 10/24] fix: typecheck --- .../MoneyRequestConfirmationList.tsx | 39 ++++++++++++++----- src/libs/OptionsListUtils.ts | 19 +++++++-- src/types/onyx/index.ts | 3 +- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 3712e928a5e1..3a6dd5964741 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -28,7 +28,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; -import type {PaymentType} from '@src/types/onyx/OriginalMessage'; +import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {MileageRate} from '@src/types/onyx/Policy'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import ConfirmedRoute from './ConfirmedRoute'; @@ -36,6 +36,7 @@ import FormHelpMessage from './FormHelpMessage'; import Image from './Image'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import OptionsSelector from './OptionsSelector'; +import ReceiptEmptyState from './ReceiptEmptyState'; import SettlementButton from './SettlementButton'; import ShowMoreButton from './ShowMoreButton'; import Switch from './Switch'; @@ -78,7 +79,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & onConfirm?: (selectedParticipants: Participant[]) => void; /** Callback to parent modal to send money */ - onSendMoney?: (paymentMethod: PaymentType) => void; + onSendMoney?: (paymentMethod: PaymentMethodType) => void; /** Callback to inform a participant is selected */ onSelectParticipant?: (option: Participant) => void; @@ -232,7 +233,7 @@ function MoneyRequestConfirmationList({ const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; // A flag for showing the categories field - const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(policyCategories ?? {})); // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); @@ -249,7 +250,7 @@ function MoneyRequestConfirmationList({ const policyTagList = policyTag?.tags ?? {}; const policyTagListName = policyTag?.name ?? translate('common.tag'); // A flag for showing the tags field - const shouldShowTags = isPolicyExpenseChat && (iouTag || OptionsListUtils.hasEnabledOptions(Object.values(policyTagList))); + const shouldShowTags = isPolicyExpenseChat && (iouTag || OptionsListUtils.hasEnabledOptions(policyTagList)); // A flag for showing tax fields - tax rate and tax amount const shouldShowTax = isPolicyExpenseChat && policy?.isTaxTrackingEnabled; @@ -322,8 +323,7 @@ function MoneyRequestConfirmationList({ return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, calculatedIouAmount > 0 ? CurrencyUtils.convertToDisplayString(calculatedIouAmount, iouCurrencyCode) : '', - // TODO: Remove the assertion once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. - ) as Participant[]; + ); }, [iouAmount, iouCurrencyCode], ); @@ -467,7 +467,7 @@ function MoneyRequestConfirmationList({ }; const confirm = useCallback( - (paymentMethod: PaymentType) => { + (paymentMethod: PaymentMethodType) => { if (selectedParticipantsMemo.length === 0) { return; } @@ -552,7 +552,7 @@ function MoneyRequestConfirmationList({ pressOnEnter isDisabled={shouldDisableButton} // eslint-disable-next-line @typescript-eslint/naming-convention - onPress={(_, value) => confirm(value as PaymentType)} + onPress={(_, value) => confirm(value as PaymentMethodType)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} /> @@ -612,7 +612,7 @@ function MoneyRequestConfirmationList({ )} - {(receiptData.image || receiptData.thumbnail) && ( + {receiptData.image || receiptData.thumbnail ? ( + ) : ( + // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") + PolicyUtils.isPaidGroupPolicy(policy) && + !isDistanceRequest && + iouType === CONST.IOU.TYPE.REQUEST && ( + + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( + CONST.IOU.ACTION.CREATE, + iouType, + transaction?.transactionID ?? '', + reportID, + Navigation.getActiveRouteWithoutParams(), + ), + ) + } + /> + ) )} {shouldShowSmartScanFields && ( { diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 812ebb051624..a1714629c21c 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -10,7 +10,20 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyCategories, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; +import type { + Beta, + Login, + PersonalDetails, + PersonalDetailsList, + Policy, + PolicyCategories, + PolicyTags, + Report, + ReportAction, + ReportActions, + Transaction, + TransactionViolation, +} from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {PolicyTaxRate, PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates'; @@ -768,7 +781,7 @@ function getEnabledCategoriesCount(options: PolicyCategories): number { /** * Verifies that there is at least one enabled option */ -function hasEnabledOptions(options: PolicyCategories): boolean { +function hasEnabledOptions(options: PolicyCategories | PolicyTags): boolean { return Object.values(options).some((option) => option.enabled); } @@ -1699,7 +1712,7 @@ function getSearchOptions(reports: Record, personalDetails: Onyx /** * Build the IOUConfirmation options for showing the payee personalDetail */ -function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails, amountText: string): PayeePersonalDetails { +function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | EmptyObject, amountText: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 011242b2ba38..81bca425f78e 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -33,8 +33,7 @@ import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; import type {PolicyTag, PolicyTags} from './PolicyTag'; -import type {PolicyTaxRates} from './PolicyTaxRates'; -import type PolicyTaxRate from './PolicyTaxRates'; +import type {PolicyTaxRate, PolicyTaxRates} from './PolicyTaxRates'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; From 2c030a3d49b2fe430e61eeb4461567db56353575 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 30 Jan 2024 11:02:40 +0100 Subject: [PATCH 11/24] fix: remove unnecessary default values --- src/components/MoneyRequestConfirmationList.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 3a6dd5964741..74ac5e878395 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -187,12 +187,12 @@ function MoneyRequestConfirmationList({ payeePersonalDetails, canModifyParticipants = false, isReadOnly = false, - bankAccountRoute = '', - policyID = '', - reportID = '', - receiptPath = '', - receiptFilename = '', - transactionID = '', + bankAccountRoute, + policyID, + reportID, + receiptPath, + receiptFilename, + transactionID, mileageRate, isDistanceRequest = false, isScanRequest = false, From b037ab91daadf1b1dcb610f942f7ab385d4f499e Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 30 Jan 2024 13:38:04 +0100 Subject: [PATCH 12/24] fix: typecheck --- .../MoneyRequestConfirmationList.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 74ac5e878395..933d604fd3cb 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -337,7 +337,7 @@ function MoneyRequestConfirmationList({ let text; if (isSplitBill && iouAmount === 0) { text = translate('iou.split'); - } else if ((receiptPath && isTypeRequest) || isDistanceRequestWithoutRoute) { + } else if (!!(receiptPath && isTypeRequest) || isDistanceRequestWithoutRoute) { text = translate('iou.request'); if (iouAmount !== 0) { text = translate('iou.requestAmount', {amount: Number(formattedAmount)}); @@ -439,7 +439,7 @@ function MoneyRequestConfirmationList({ translate, toLocaleDigit, ); - IOU.setMoneyRequestMerchant_temporaryForRefactor(transactionID, distanceMerchant); + IOU.setMoneyRequestMerchant_temporaryForRefactor(transactionID ?? '', distanceMerchant); }, [hasRoute, distance, mileageRate?.unit, mileageRate?.rate, mileageRate?.currency, translate, toLocaleDigit, isDistanceRequest, transactionID]); const selectParticipant = useCallback( @@ -633,7 +633,7 @@ function MoneyRequestConfirmationList({ CONST.IOU.ACTION.CREATE, iouType, transaction?.transactionID ?? '', - reportID, + reportID ?? '', Navigation.getActiveRouteWithoutParams(), ), ) @@ -652,7 +652,7 @@ function MoneyRequestConfirmationList({ return; } if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.AMOUNT)); + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID ?? '', reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.AMOUNT)); return; } Navigation.navigate(ROUTES.MONEY_REQUEST_AMOUNT.getRoute(iouType, reportID)); @@ -671,7 +671,7 @@ function MoneyRequestConfirmationList({ description={translate('common.description')} onPress={() => { if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.DESCRIPTION)); + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID ?? '', reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.DESCRIPTION)); return; } Navigation.navigate(ROUTES.MONEY_REQUEST_DESCRIPTION.getRoute(iouType, reportID)); @@ -700,7 +700,7 @@ function MoneyRequestConfirmationList({ titleStyle={styles.flex1} onPress={() => { if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.DATE)); + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID ?? '', reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.DATE)); return; } Navigation.navigate(ROUTES.MONEY_REQUEST_DATE.getRoute(iouType, reportID)); @@ -732,7 +732,7 @@ function MoneyRequestConfirmationList({ titleStyle={styles.flex1} onPress={() => { if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.MERCHANT)); + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID ?? '', reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.MERCHANT)); return; } Navigation.navigate(ROUTES.MONEY_REQUEST_MERCHANT.getRoute(iouType, reportID)); @@ -755,7 +755,7 @@ function MoneyRequestConfirmationList({ numberOfLinesTitle={2} onPress={() => { if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.CATEGORY)); + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID ?? '', reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.CATEGORY)); return; } Navigation.navigate(ROUTES.MONEY_REQUEST_CATEGORY.getRoute(iouType, reportID)); @@ -775,7 +775,7 @@ function MoneyRequestConfirmationList({ numberOfLinesTitle={2} onPress={() => { if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.TAG)); + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID ?? '', reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.TAG)); return; } Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(iouType, reportID)); @@ -796,7 +796,7 @@ function MoneyRequestConfirmationList({ titleStyle={styles.flex1} onPress={() => Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()), + ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(iouType, transaction?.transactionID ?? '', reportID ?? '', Navigation.getActiveRouteWithoutParams()), ) } disabled={didConfirm} @@ -813,7 +813,7 @@ function MoneyRequestConfirmationList({ titleStyle={styles.flex1} onPress={() => Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()), + ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction?.transactionID ?? '', reportID ?? '', Navigation.getActiveRouteWithoutParams()), ) } disabled={didConfirm} From 523302c7047da4e50cb4c5a1e362487f34e459d9 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 2 Feb 2024 14:35:33 +0100 Subject: [PATCH 13/24] fix: sync with main --- src/components/MoneyRequestConfirmationList.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 933d604fd3cb..30cd28541181 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -660,8 +660,14 @@ function MoneyRequestConfirmationList({ style={[styles.moneyRequestMenuItem, styles.mt2]} titleStyle={styles.moneyRequestConfirmationAmount} disabled={didConfirm} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction ?? null) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - error={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction ?? null) ? translate('common.error.enterAmount') : ''} + brickRoadIndicator={ + isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction ?? null) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined + } + error={ + shouldDisplayMerchantError || (isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction ?? null)) + ? translate('common.error.enterMerchant') + : '' + } /> )} Date: Fri, 2 Feb 2024 15:00:47 +0100 Subject: [PATCH 14/24] fix: resolve comments --- src/components/MoneyRequestConfirmationList.tsx | 7 +++++-- src/libs/ReceiptUtils.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 30cd28541181..685559835897 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -423,7 +423,10 @@ function MoneyRequestConfirmationList({ return []; } const myIOUAmount = IOUUtils.calculateAmount(selectedParticipantsMemo.length, iouAmount, iouCurrencyCode ?? '', true); - return [...selectedParticipantsMemo, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetailsMemo, String(myIOUAmount))]; + return [ + ...selectedParticipantsMemo, + OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetailsMemo, CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode)), + ]; }, [hasMultipleParticipants, selectedParticipantsMemo, iouAmount, iouCurrencyCode, payeePersonalDetailsMemo]); useEffect(() => { @@ -560,7 +563,7 @@ function MoneyRequestConfirmationList({ return ( <> - {formError && ( + {!!formError && ( , receiptPa // URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg // If there're errors, we need to display them in preview. We can store many files in errors, but we just need to get the last one - const errors = _.findLast(transaction.errors) as ReceiptError | undefined; + const errors = _.findLast(transaction?.errors) as ReceiptError | undefined; const path = errors?.source ?? transaction?.receipt?.source ?? receiptPath ?? ''; // filename of uploaded image or last part of remote URI const filename = errors?.filename ?? transaction?.filename ?? receiptFileName ?? ''; From 5f3be3716fb5b2629a49f09191d479532eee1680 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 6 Feb 2024 14:34:37 +0100 Subject: [PATCH 15/24] fix: typecheck --- src/components/ButtonWithDropdownMenu.tsx | 21 +++++++-------- .../MoneyRequestConfirmationList.tsx | 27 ++++++++++++------- .../API/parameters/PayMoneyRequestParams.ts | 5 ++-- src/libs/API/parameters/SendMoneyParams.ts | 5 ++-- src/libs/actions/IOU.ts | 26 +++++++++--------- src/types/onyx/OriginalMessage.ts | 3 +-- 6 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/components/ButtonWithDropdownMenu.tsx b/src/components/ButtonWithDropdownMenu.tsx index 0e41da57b409..8aa3a5f0b9f0 100644 --- a/src/components/ButtonWithDropdownMenu.tsx +++ b/src/components/ButtonWithDropdownMenu.tsx @@ -10,17 +10,14 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; import type AnchorAlignment from '@src/types/utils/AnchorAlignment'; -import type DeepValueOf from '@src/types/utils/DeepValueOf'; import type IconAsset from '@src/types/utils/IconAsset'; import Button from './Button'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import PopoverMenu from './PopoverMenu'; -type PaymentType = DeepValueOf; - -type DropdownOption = { - value: PaymentType; +type DropdownOption = { + value: T; text: string; icon?: IconAsset; iconWidth?: number; @@ -28,15 +25,15 @@ type DropdownOption = { iconDescription?: string; }; -type ButtonWithDropdownMenuProps = { +type ButtonWithDropdownMenuProps = { /** Text to display for the menu header */ menuHeaderText?: string; /** Callback to execute when the main button is pressed */ - onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: PaymentType) => void; + onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: T) => void; /** Callback to execute when a dropdown option is selected */ - onOptionSelected?: (option: DropdownOption) => void; + onOptionSelected?: (option: DropdownOption) => void; /** Call the onPress function on main button when Enter key is pressed */ pressOnEnter?: boolean; @@ -55,19 +52,19 @@ type ButtonWithDropdownMenuProps = { /** Menu options to display */ /** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */ - options: DropdownOption[]; + options: Array>; /** The anchor alignment of the popover menu */ anchorAlignment?: AnchorAlignment; /* ref for the button */ - buttonRef: RefObject; + buttonRef?: RefObject; /** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */ enterKeyEventListenerPriority?: number; }; -function ButtonWithDropdownMenu({ +function ButtonWithDropdownMenu({ isLoading = false, isDisabled = false, pressOnEnter = false, @@ -83,7 +80,7 @@ function ButtonWithDropdownMenu({ options, onOptionSelected, enterKeyEventListenerPriority = 0, -}: ButtonWithDropdownMenuProps) { +}: ButtonWithDropdownMenuProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index d26bdc139f69..1fd94d4a5f4b 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -25,11 +25,12 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; -import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {MileageRate} from '@src/types/onyx/Policy'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import ConfirmedRoute from './ConfirmedRoute'; import FormHelpMessage from './FormHelpMessage'; @@ -44,6 +45,11 @@ import Text from './Text'; import type {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; +type DropdownOption = { + text: string; + value: DeepValueOf; +}; + type Option = Partial; type CategorySection = { @@ -79,7 +85,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & onConfirm?: (selectedParticipants: Participant[]) => void; /** Callback to parent modal to send money */ - onSendMoney?: (paymentMethod: PaymentMethodType) => void; + onSendMoney?: (paymentMethod: IOU.PaymentMethodType) => void; /** Callback to inform a participant is selected */ onSelectParticipant?: (option: Participant) => void; @@ -130,7 +136,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & isReadOnly?: boolean; /** Depending on expense report or personal IOU report, respective bank account route */ - bankAccountRoute?: string; + bankAccountRoute?: Route; /** The policyID of the request */ policyID?: string; @@ -233,7 +239,7 @@ function MoneyRequestConfirmationList({ const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; // A flag for showing the categories field - const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(policyCategories ?? {})); + const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); @@ -250,7 +256,7 @@ function MoneyRequestConfirmationList({ const policyTagList = policyTag?.tags ?? {}; const policyTagListName = policyTag?.name ?? translate('common.tag'); // A flag for showing the tags field - const shouldShowTags = isPolicyExpenseChat && (iouTag || OptionsListUtils.hasEnabledOptions(policyTagList)); + const shouldShowTags = isPolicyExpenseChat && (iouTag || OptionsListUtils.hasEnabledOptions(Object.values(policyTagList ?? {}))); // A flag for showing tax fields - tax rate and tax amount const shouldShowTax = isPolicyExpenseChat && policy?.isTaxTrackingEnabled; @@ -333,7 +339,7 @@ function MoneyRequestConfirmationList({ setDidConfirm(false); } - const splitOrRequestOptions = useMemo(() => { + const splitOrRequestOptions: DropdownOption[] = useMemo(() => { let text; if (isSplitBill && iouAmount === 0) { text = translate('iou.split'); @@ -368,7 +374,7 @@ function MoneyRequestConfirmationList({ if (!canModifyParticipantsValue) { formattedParticipantsList = formattedParticipantsList.map((participant) => ({ ...participant, - isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID), + isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), })); } @@ -396,7 +402,7 @@ function MoneyRequestConfirmationList({ } else { const formattedSelectedParticipants = selectedParticipants.map((participant) => ({ ...participant, - isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID), + isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), })); sections.push({ title: translate('common.to'), @@ -470,7 +476,7 @@ function MoneyRequestConfirmationList({ }; const confirm = useCallback( - (paymentMethod: PaymentMethodType) => { + (paymentMethod: IOU.PaymentMethodType | undefined) => { if (selectedParticipantsMemo.length === 0) { return; } @@ -555,7 +561,7 @@ function MoneyRequestConfirmationList({ pressOnEnter isDisabled={shouldDisableButton} // eslint-disable-next-line @typescript-eslint/naming-convention - onPress={(_, value) => confirm(value as PaymentMethodType)} + onPress={(_, value) => confirm(value as IOU.PaymentMethodType)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} enterKeyEventListenerPriority={1} @@ -796,6 +802,7 @@ function MoneyRequestConfirmationList({ ); return; } + Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(iouType, reportID)); }} style={styles.moneyRequestMenuItem} diff --git a/src/libs/API/parameters/PayMoneyRequestParams.ts b/src/libs/API/parameters/PayMoneyRequestParams.ts index 94f62bcca065..93117467f630 100644 --- a/src/libs/API/parameters/PayMoneyRequestParams.ts +++ b/src/libs/API/parameters/PayMoneyRequestParams.ts @@ -1,11 +1,10 @@ -import type CONST from '@src/CONST'; -import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import type {PaymentMethodType} from '@libs/actions/IOU'; type PayMoneyRequestParams = { iouReportID: string; chatReportID: string; reportActionID: string; - paymentMethodType: DeepValueOf; + paymentMethodType: PaymentMethodType; }; export default PayMoneyRequestParams; diff --git a/src/libs/API/parameters/SendMoneyParams.ts b/src/libs/API/parameters/SendMoneyParams.ts index c32287d5b4ec..e4faa8da5b8f 100644 --- a/src/libs/API/parameters/SendMoneyParams.ts +++ b/src/libs/API/parameters/SendMoneyParams.ts @@ -1,11 +1,10 @@ -import type CONST from '@src/CONST'; -import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import type {PaymentMethodType} from '@libs/actions/IOU'; type SendMoneyParams = { iouReportID: string; chatReportID: string; reportActionID: string; - paymentMethodType: DeepValueOf; + paymentMethodType: PaymentMethodType; transactionID: string; newIOUReportDetails: string; createdReportActionID: string; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 569b6b26b728..3444030240d2 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -63,7 +63,7 @@ type MoneyRequestRoute = StackScreenProps; -type PaymentMethodType = DeepValueOf; +type PaymentMethodType = DeepValueOf | typeof CONST.IOU.REPORT_ACTION_TYPE.APPROVE; type OneOnOneIOUReport = OnyxTypes.Report | undefined | null; @@ -3255,14 +3255,14 @@ function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency: Report.notifyNewAction(params.chatReportID, managerID); } -function approveMoneyRequest(expenseReport: OnyxTypes.Report) { - const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; +function approveMoneyRequest(expenseReport: OnyxEntry | EmptyObject) { + const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport?.reportID}`] ?? null; - const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID); + const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport?.total ?? 0, expenseReport?.currency ?? '', expenseReport?.reportID ?? ''); const optimisticReportActionsData: OnyxUpdate = { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`, value: { [optimisticApprovedReportAction.reportActionID]: { ...(optimisticApprovedReportAction as OnyxTypes.ReportAction), @@ -3272,7 +3272,7 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report) { }; const optimisticIOUReportData: OnyxUpdate = { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport?.reportID}`, value: { ...expenseReport, lastMessageText: optimisticApprovedReportAction.message?.[0].text, @@ -3286,7 +3286,7 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report) { const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`, value: { [optimisticApprovedReportAction.reportActionID]: { pendingAction: null, @@ -3298,9 +3298,9 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report) { const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`, value: { - [expenseReport.reportActionID ?? '']: { + [expenseReport?.reportActionID ?? '']: { errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), }, }, @@ -3310,18 +3310,18 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report) { if (currentNextStep) { optimisticData.push({ onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport?.reportID}`, value: null, }); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport?.reportID}`, value: currentNextStep, }); } const parameters: ApproveMoneyRequestParams = { - reportID: expenseReport.reportID, + reportID: expenseReport?.reportID ?? '', approvedReportActionID: optimisticApprovedReportAction.reportActionID, }; @@ -3730,3 +3730,5 @@ export { navigateToStartStepIfScanFileCannotBeRead, savePreferredPaymentMethod, }; + +export type {PaymentMethodType}; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index fd28699e7b4c..34ac84989632 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -1,9 +1,8 @@ import type {ValueOf} from 'type-fest'; +import type {PaymentMethodType} from '@libs/actions/IOU'; import type CONST from '@src/CONST'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; -type PaymentMethodType = DeepValueOf; - type ActionName = DeepValueOf; type OriginalMessageActionName = | 'ADDCOMMENT' From 1da3d098f240ba878e7d737a7e32597ca04b1ae9 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 8 Feb 2024 14:30:53 +0100 Subject: [PATCH 16/24] fix: typecheck --- src/components/MoneyRequestConfirmationList.tsx | 4 ++-- src/components/SettlementButton.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 98d177a19e67..dbfce22030a2 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -85,7 +85,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & onConfirm?: (selectedParticipants: Participant[]) => void; /** Callback to parent modal to send money */ - onSendMoney?: (paymentMethod: IOU.PaymentMethodType) => void; + onSendMoney?: (paymentMethod: OnyxTypes.PaymentMethodType) => void; /** Callback to inform a participant is selected */ onSelectParticipant?: (option: Participant) => void; @@ -567,7 +567,7 @@ function MoneyRequestConfirmationList({ pressOnEnter isDisabled={shouldDisableButton} // eslint-disable-next-line @typescript-eslint/naming-convention - onPress={(_, value) => confirm(value as IOU.PaymentMethodType)} + onPress={(_, value) => confirm(value as OnyxTypes.PaymentMethodType)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} enterKeyEventListenerPriority={1} diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index 058def7a34ad..4e96684a65ec 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -226,10 +226,10 @@ function SettlementButton({ buttonRef={buttonRef} isDisabled={isDisabled} isLoading={isLoading} - onPress={(event, iouPaymentType) => selectPaymentType(event, iouPaymentType, triggerKYCFlow)} + onPress={(event, iouPaymentType) => selectPaymentType(event, iouPaymentType as PaymentMethodType, triggerKYCFlow)} pressOnEnter={pressOnEnter} options={paymentButtonOptions} - onOptionSelected={(option) => savePreferredPaymentMethod(policyID, option.value)} + onOptionSelected={(option) => savePreferredPaymentMethod(policyID, option.value as PaymentMethodType)} style={style} buttonSize={buttonSize} anchorAlignment={paymentMethodDropdownAnchorAlignment} From bee66eb714051217464d30bedc61f9a77439c75c Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 9 Feb 2024 16:32:51 +0100 Subject: [PATCH 17/24] fix: conflicts --- src/libs/NextStepUtils.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 3b42382b10f9..e99f2c2ef8a1 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -1,5 +1,6 @@ import {format, lastDayOfMonth, setDate} from 'date-fns'; import Str from 'expensify-common/lib/str'; +import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; @@ -62,18 +63,21 @@ type BuildNextStepParameters = { * @param parameters.isPaidWithWallet - Whether a report has been paid with the wallet or outside of Expensify * @returns nextStep */ -function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { +function buildNextStep( + report: OnyxEntry | EmptyObject, + predictedNextStatus: ValueOf, + {isPaidWithWallet}: BuildNextStepParameters = {}, +): ReportNextStep | null { if (!ReportUtils.isExpenseReport(report)) { return null; } - const {policyID = '', ownerAccountID = -1, managerID = -1} = report; - const policy = ReportUtils.getPolicy(policyID); + const policy = ReportUtils.getPolicy(report?.policyID); const {submitsTo, harvesting, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; - const isOwner = currentUserAccountID === ownerAccountID; - const isManager = currentUserAccountID === managerID; + const isOwner = currentUserAccountID === report?.ownerAccountID; + const isManager = currentUserAccountID === report?.managerID; const isSelfApproval = currentUserAccountID === submitsTo; - const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? ''; + const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([report?.ownerAccountID ?? -1])[0] ?? ''; const managerDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(submitsTo) ?? ''; const type: ReportNextStep['type'] = 'neutral'; let optimisticNextStep: ReportNextStep | null; From f69fce5038eb5366ec3ada2f96050edc13382306 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 12 Feb 2024 09:52:40 +0100 Subject: [PATCH 18/24] fix: typecheck --- src/libs/OptionsListUtils.ts | 1 + src/types/onyx/index.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 8a96c2a8705c..863970dbda30 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -30,6 +30,7 @@ import type {Participant} from '@src/types/onyx/IOU'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {PolicyTaxRate, PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import times from '@src/utils/times'; import Timing from './actions/Timing'; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 6343b9a1b2ec..51e772c0d173 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -50,6 +50,7 @@ import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; +import type {PolicyTaxRate, PolicyTaxRates} from './PolicyTaxRates'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -169,8 +170,6 @@ export type { PolicyReportField, PolicyReportFields, RecentlyUsedReportFields, - PolicyTaxRate, - PolicyTaxRates, LastPaymentMethod, NewRoomForm, IKnowATeacherForm, @@ -180,4 +179,6 @@ export type { ReportFieldEditForm, RoomNameForm, PaymentMethodType, + PolicyTaxRate, + PolicyTaxRates, }; From 68f4cf96945713268d8f1d34b9c4c81dd2ca2bba Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 14 Feb 2024 15:03:53 +0100 Subject: [PATCH 19/24] fix: typecheck --- src/ONYXKEYS.ts | 2 +- src/components/MoneyRequestConfirmationList.tsx | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0735bc53e56c..944788d99cb8 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -453,7 +453,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; [ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep; - [ONYXKEYS.COLLECTION.POLICY_TAX_RATE]: string[]; + [ONYXKEYS.COLLECTION.POLICY_TAX_RATE]: OnyxTypes.PolicyTaxRate; }; type OnyxValuesMapping = { diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 1dd6e9b9f302..de289f1fba3b 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -77,6 +77,9 @@ type MoneyRequestConfirmationListOnyxProps = { /** The policy of root parent report */ policy: OnyxEntry; + + /** The session of the logged in user */ + session: OnyxEntry; }; type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & @@ -818,7 +821,7 @@ function MoneyRequestConfirmationList({ Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( CONST.IOU.ACTION.CREATE, - CONST.IOU.TYPE.SEND, + iouType, transaction?.transactionID ?? '', reportID ?? '', Navigation.getActiveRouteWithoutParams(), @@ -905,5 +908,8 @@ export default withCurrentUserPersonalDetails( iou: { key: ONYXKEYS.IOU, }, + session: { + key: ONYXKEYS.SESSION, + }, })(MoneyRequestConfirmationList), ); From d7708c104d60b0295cb382c617646e3820d9b4e7 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 15 Feb 2024 14:33:50 +0100 Subject: [PATCH 20/24] fix: removed uncessesary condition --- .../MoneyRequestConfirmationList.tsx | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index de289f1fba3b..8430dd66d3c8 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -262,7 +262,7 @@ function MoneyRequestConfirmationList({ const shouldShowTags = isPolicyExpenseChat && (iouTag || OptionsListUtils.hasEnabledOptions(Object.values(policyTagList ?? {}))); // A flag for showing tax fields - tax rate and tax amount - const shouldShowTax = isPolicyExpenseChat && policy?.isTaxTrackingEnabled; + const shouldShowTax = isPolicyExpenseChat && (policy?.tax?.trackingEnabled ?? policy?.isTaxTrackingEnabled); // A flag for showing the billable field const shouldShowBillable = !policy?.disabledFields?.defaultBillable ?? true; @@ -457,7 +457,7 @@ function MoneyRequestConfirmationList({ translate, toLocaleDigit, ); - IOU.setMoneyRequestMerchant_temporaryForRefactor(transactionID ?? '', distanceMerchant); + IOU.setMoneyRequestMerchant(transactionID ?? '', distanceMerchant, false); }, [hasRoute, distance, mileageRate?.unit, mileageRate?.rate, mileageRate?.currency, translate, toLocaleDigit, isDistanceRequest, transactionID, isDistanceRequestWithPendingRoute]); const selectParticipant = useCallback( @@ -762,11 +762,15 @@ function MoneyRequestConfirmationList({ style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} onPress={() => { - if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID ?? '', reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.MERCHANT)); - return; - } - Navigation.navigate(ROUTES.MONEY_REQUEST_MERCHANT.getRoute(iouType, reportID)); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute( + CONST.IOU.ACTION.EDIT, + iouType, + transactionID ?? '', + reportID ?? '', + Navigation.getActiveRouteWithoutParams(), + ), + ); }} disabled={didConfirm} interactive={!isReadOnly} @@ -805,22 +809,9 @@ function MoneyRequestConfirmationList({ description={policyTagListName} numberOfLinesTitle={2} onPress={() => { - if (isEditingSplitBill) { - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( - CONST.IOU.ACTION.EDIT, - CONST.IOU.TYPE.SPLIT, - transaction?.transactionID ?? '', - reportID ?? '', - Navigation.getActiveRouteWithoutParams(), - ), - ); - return; - } - Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( - CONST.IOU.ACTION.CREATE, + CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', reportID ?? '', From d5aba808e5b9306983bf41a98ed892ffd1945227 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 11:22:00 +0100 Subject: [PATCH 21/24] fix: displaying thumbnail --- src/components/MoneyRequestConfirmationList.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b61e82d76538..252b0ad44384 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -73,7 +73,7 @@ type MoneyRequestConfirmationListOnyxProps = { policyCategories: OnyxEntry; /** Collection of tags attached to a policy */ - policyTags: OnyxEntry; + policyTags: OnyxEntry; /** The policy of root parent report */ policy: OnyxEntry; @@ -253,7 +253,6 @@ function MoneyRequestConfirmationList({ // In Send Money and Split Bill with Scan flow, we don't allow the Merchant or Date to be edited. For distance requests, don't show the merchant as there's already another "Distance" menu item const shouldShowDate = shouldShowAllFields && !isTypeSend && !isSplitWithScan; const shouldShowMerchant = shouldShowAllFields && !isTypeSend && !isDistanceRequest && !isSplitWithScan; - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field const shouldShowTags = isPolicyExpenseChat && (iouTag || OptionsListUtils.hasEnabledTags(policyTagLists)); @@ -600,7 +599,7 @@ function MoneyRequestConfirmationList({ styles.mb2, ]); - const receiptData = ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename); + const receiptData = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : null; return ( // @ts-expect-error TODO: Remove this once OptionsSelector (https://github.com/Expensify/App/issues/25125) is migrated to TypeScript. )} - {receiptData.image || receiptData.thumbnail ? ( + {receiptData?.image ?? receiptData?.thumbnail ? ( Date: Mon, 26 Feb 2024 10:46:17 +0100 Subject: [PATCH 22/24] fix: typecheck --- .../MoneyRequestConfirmationList.tsx | 18 +++++++++++------- src/languages/types.ts | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 252b0ad44384..dbc94502bdd3 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -345,11 +345,11 @@ function MoneyRequestConfirmationList({ } else if (!!(receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) { text = translate('iou.request'); if (iouAmount !== 0) { - text = translate('iou.requestAmount', {amount: Number(formattedAmount)}); + text = translate('iou.requestAmount', {amount: formattedAmount}); } } else { const translationKey = isSplitBill ? 'iou.splitAmount' : 'iou.requestAmount'; - text = translate(translationKey, {amount: Number(formattedAmount)}); + text = translate(translationKey, {amount: formattedAmount}); } return [ { @@ -785,11 +785,15 @@ function MoneyRequestConfirmationList({ description={translate('common.category')} numberOfLinesTitle={2} onPress={() => { - if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID ?? '', reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.CATEGORY)); - return; - } - Navigation.navigate(ROUTES.MONEY_REQUEST_CATEGORY.getRoute(iouType, reportID)); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute( + CONST.IOU.ACTION.EDIT, + iouType, + transaction?.transactionID ?? '', + reportID ?? '', + Navigation.getActiveRouteWithoutParams(), + ), + ); }} style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} diff --git a/src/languages/types.ts b/src/languages/types.ts index 410c8e1c2085..abfe2c3f7750 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -109,7 +109,7 @@ type RequestAmountParams = {amount: string}; type RequestedAmountMessageParams = {formattedAmount: string; comment?: string}; -type SplitAmountParams = {amount: number}; +type SplitAmountParams = {amount: string | number}; type DidSplitAmountMessageParams = {formattedAmount: string; comment: string}; From f17da9e0d2e4787ad153c9d6fa6e2ef9b4c9c5a9 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 27 Feb 2024 09:52:38 +0100 Subject: [PATCH 23/24] fix: typecheck --- .../MoneyRequestConfirmationList.tsx | 23 ++++++++----------- src/types/onyx/index.ts | 5 ---- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index dbc94502bdd3..21fdda1c58d2 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -29,6 +29,7 @@ import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; +import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {MileageRate} from '@src/types/onyx/Policy'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; @@ -63,9 +64,6 @@ type MoneyRequestConfirmationListOnyxProps = { /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: OnyxEntry; - /** Collection of tax rates attached to a policy */ - policyTaxRates: OnyxEntry; - /** Unit and rate used for if the money request is a distance request */ mileageRate: OnyxEntry; @@ -88,7 +86,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & onConfirm?: (selectedParticipants: Participant[]) => void; /** Callback to parent modal to send money */ - onSendMoney?: (paymentMethod: OnyxTypes.PaymentMethodType) => void; + onSendMoney?: (paymentMethod: PaymentMethodType) => void; /** Callback to inform a participant is selected */ onSelectParticipant?: (option: Participant) => void; @@ -212,7 +210,6 @@ function MoneyRequestConfirmationList({ policyTags, policyCategories, policy, - policyTaxRates, iouCurrencyCode, isEditingSplitBill, hasSmartScanFailed, @@ -240,6 +237,7 @@ function MoneyRequestConfirmationList({ const distance = transaction?.routes?.route0.distance ?? 0; const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; + const taxRates = policy?.taxRates; // A flag for showing the categories field const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); @@ -275,8 +273,8 @@ function MoneyRequestConfirmationList({ ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); - const defaultTaxKey = policyTaxRates?.defaultExternalID; - const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates?.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) ?? ''; + const defaultTaxKey = taxRates?.defaultExternalID; + const defaultTaxName = (defaultTaxKey && `${taxRates?.taxes[defaultTaxKey].name} (${taxRates?.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const taxRateTitle = transaction?.taxRate?.text || defaultTaxName; @@ -481,7 +479,7 @@ function MoneyRequestConfirmationList({ }; const confirm = useCallback( - (paymentMethod: OnyxTypes.PaymentMethodType | undefined) => { + (paymentMethod: PaymentMethodType | undefined) => { if (selectedParticipantsMemo.length === 0) { return; } @@ -565,7 +563,7 @@ function MoneyRequestConfirmationList({ pressOnEnter isDisabled={shouldDisableButton} // eslint-disable-next-line @typescript-eslint/naming-convention - onPress={(_, value) => confirm(value as OnyxTypes.PaymentMethodType)} + onPress={(_, value) => confirm(value as PaymentMethodType)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} enterKeyEventListenerPriority={1} @@ -833,7 +831,7 @@ function MoneyRequestConfirmationList({ @@ -850,7 +848,7 @@ function MoneyRequestConfirmationList({ @@ -896,9 +894,6 @@ export default withCurrentUserPersonalDetails( policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, - policyTaxRates: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, - }, iou: { key: ONYXKEYS.IOU, }, diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index a010b5459d97..6846fc302639 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -26,7 +26,6 @@ import type MapboxAccessToken from './MapboxAccessToken'; import type Modal from './Modal'; import type Network from './Network'; import type {OnyxUpdateEvent, OnyxUpdatesFromServer} from './OnyxUpdatesFromServer'; -import type {PaymentMethodType} from './OriginalMessage'; import type PersonalBankAccount from './PersonalBankAccount'; import type {PersonalDetailsList} from './PersonalDetails'; import type PersonalDetails from './PersonalDetails'; @@ -38,7 +37,6 @@ import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; -import type {PolicyTaxRate, PolicyTaxRates} from './PolicyTaxRates'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -155,8 +153,5 @@ export type { RecentlyUsedReportFields, LastPaymentMethod, InvitedEmailsToAccountIDs, - PaymentMethodType, - PolicyTaxRate, - PolicyTaxRates, Log, }; From ad6eff980b311b26444bcdd102fcc72e6aa9e6a4 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 27 Feb 2024 14:51:34 +0100 Subject: [PATCH 24/24] fix: linter --- src/components/MoneyRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 21fdda1c58d2..773e98b6462e 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -274,7 +274,7 @@ function MoneyRequestConfirmationList({ const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); const defaultTaxKey = taxRates?.defaultExternalID; - const defaultTaxName = (defaultTaxKey && `${taxRates?.taxes[defaultTaxKey].name} (${taxRates?.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; + const defaultTaxName = (defaultTaxKey && `${taxRates?.taxes[defaultTaxKey].name} (${taxRates?.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const taxRateTitle = transaction?.taxRate?.text || defaultTaxName;