From 382a644906c45348d9e4d0b5b38fc1bdd4ac2e4b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 09:36:32 +0100 Subject: [PATCH 01/17] fix: move MoneyRequestConfirmationList to TS --- ...st.js => MoneyRequestConfirmationList.tsx} | 323 ++++++++++-------- src/languages/types.ts | 2 +- src/libs/DistanceRequestUtils.ts | 2 + src/libs/OptionsListUtils.ts | 4 +- src/libs/TransactionUtils.ts | 18 +- 5 files changed, 189 insertions(+), 160 deletions(-) rename src/components/{MoneyRequestConfirmationList.js => MoneyRequestConfirmationList.tsx} (79%) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.tsx similarity index 79% rename from src/components/MoneyRequestConfirmationList.js rename to src/components/MoneyRequestConfirmationList.tsx index dfe1d96b0e5d..b78130c640e5 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -2,18 +2,19 @@ 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 {View} from 'react-native'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; 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 DistanceRequestUtils, {DefaultMileageRate} from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Log from '@libs/Log'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; @@ -29,6 +30,7 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import categoryPropTypes from './categoryPropTypes'; import ConfirmedRoute from './ConfirmedRoute'; @@ -46,132 +48,124 @@ import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; -const propTypes = { +type MoneyRequestConfirmationListOnyxProps = { + /** Collection of categories attached to a policy */ + policyCategories: OnyxEntry; + + /** Collection of tags attached to a policy */ + policyTags: OnyxEntry; + + /** The policy of the report */ + policy: OnyxEntry; + + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: OnyxEntry; + + /** The session of the logged in user */ + session: OnyxEntry; + + /** The transaction that represents the money request */ + splitTransactionDraft: OnyxEntry; + + /** Unit and rate used for if the money request is a distance request */ + mileageRate: OnyxEntry; +}; +type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & { /** Callback to inform parent modal of success */ - onConfirm: PropTypes.func, + onConfirm?: () => void; /** Callback to parent modal to send money */ - onSendMoney: PropTypes.func, + onSendMoney?: () => void; /** Callback to inform a participant is selected */ - onSelectParticipant: PropTypes.func, + onSelectParticipant?: () => void; /** Should we request a single or multiple participant selection from user */ - hasMultipleParticipants: PropTypes.bool.isRequired, + hasMultipleParticipants: boolean; /** IOU amount */ - iouAmount: PropTypes.number.isRequired, + iouAmount: number; /** IOU comment */ - iouComment: PropTypes.string, + iouComment?: string; /** IOU currency */ - iouCurrencyCode: PropTypes.string, + iouCurrencyCode?: string; /** IOU type */ - iouType: PropTypes.string, + iouType?: string; /** IOU date */ - iouCreated: PropTypes.string, + iouCreated?: string; /** IOU merchant */ - iouMerchant: PropTypes.string, + iouMerchant?: string; /** IOU Category */ - iouCategory: PropTypes.string, + iouCategory?: string; /** IOU Tag */ - iouTag: PropTypes.string, + iouTag?: string; /** IOU isBillable */ - iouIsBillable: PropTypes.bool, + iouIsBillable?: boolean; /** Callback to toggle the billable state */ - onToggleBillable: PropTypes.func, + onToggleBillable?: () => void; /** Selected participants from MoneyRequestModal with login / accountID */ - selectedParticipants: PropTypes.arrayOf(optionPropTypes).isRequired, + selectedParticipants: ReportUtils.OptionData[]; /** Payee of the money request with login */ - payeePersonalDetails: optionPropTypes, + payeePersonalDetails?: ReportUtils.OptionData; /** Can the participants be modified or not */ - canModifyParticipants: PropTypes.bool, + canModifyParticipants?: boolean; /** Should the list be read only, and not editable? */ - isReadOnly: PropTypes.bool, + isReadOnly?: boolean; /** Depending on expense report or personal IOU report, respective bank account route */ - bankAccountRoute: PropTypes.string, - - ...withCurrentUserPersonalDetailsPropTypes, - - /** Current user session */ - session: PropTypes.shape({ - email: PropTypes.string.isRequired, - }), + bankAccountRoute?: string; /** The policyID of the request */ - policyID: PropTypes.string, + policyID?: string; /** The reportID of the request */ - reportID: PropTypes.string, + reportID?: string; /** File path of the receipt */ - receiptPath: PropTypes.string, + receiptPath?: string; /** File name of the receipt */ - receiptFilename: PropTypes.string, + receiptFilename?: string; /** List styles for OptionsSelector */ - listStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + listStyles?: StyleProp; /** ID of the transaction that represents the money request */ - transactionID: PropTypes.string, + transactionID?: string; /** Transaction that represents the money request */ - transaction: transactionPropTypes, - - /** 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]), - - /** Rate used to calculate the distance request amount */ - rate: PropTypes.number, - - /** The currency of the rate */ - currency: PropTypes.string, - }), + transaction?: OnyxEntry; /** Whether the money request is a distance request */ - isDistanceRequest: PropTypes.bool, + isDistanceRequest?: boolean; /** Whether the money request is a scan request */ - isScanRequest: PropTypes.bool, + isScanRequest?: boolean; /** Whether we're editing a split bill */ - isEditingSplitBill: PropTypes.bool, + isEditingSplitBill?: boolean; /** Whether we should show the amount, date, and merchant fields. */ - shouldShowSmartScanFields: PropTypes.bool, + shouldShowSmartScanFields?: boolean; /** A flag for verifying that the current report is a sub-report of a workspace chat */ - isPolicyExpenseChat: PropTypes.bool, + isPolicyExpenseChat?: boolean; - /* Onyx Props */ - /** Collection of categories attached to a policy */ - policyCategories: PropTypes.objectOf(categoryPropTypes), - - /** Collection of tags attached to a policy */ - policyTags: tagPropTypes, - - /* Onyx Props */ - /** The policy of the report */ - policy: policyPropTypes.policy, - - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, + /** Whether smart scan failed */ + hasSmartScanFailed?: boolean; }; const defaultProps = { @@ -209,63 +203,105 @@ const defaultProps = { iou: iouDefaultProps, }; -function MoneyRequestConfirmationList(props) { +function MoneyRequestConfirmationList({ + transaction, + onSendMoney, + onConfirm, + onSelectParticipant, + iouType, + isScanRequest, + iouAmount, + policyCategories, + mileageRate, + isDistanceRequest, + policy, + isPolicyExpenseChat, + iouCategory, + shouldShowSmartScanFields, + isEditingSplitBill, + policyTags, + iouCurrencyCode, + iouMerchant, + hasMultipleParticipants, + selectedParticipants: selectedParticipantsProp, + payeePersonalDetails: payeePersonalDetailsProp, + iou, + canModifyParticipants: canModifyParticipantsProp, + session, + isReadOnly, + bankAccountRoute, + splitTransactionDraft, + policyID, + reportID, + receiptPath, + iouComment, + receiptFilename, + listStyles, + iouCreated, + iouIsBillable, + onToggleBillable, + iouTag, + transactionID, + hasSmartScanFailed, +}: 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(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - 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 taxRates = lodashGet(props.policy, 'taxRates', {}); + const {unit, rate, currency} = mileageRate ?? { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + rate: 0, + currency: 'USD', + }; + 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 = 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; - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(props.policyTags), [props.policyTags]); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field - const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledTags(policyTagLists)); + const shouldShowTags = isPolicyExpenseChat && (!!iouTag || OptionsListUtils.hasEnabledTags(policyTagLists)); // A flag for showing tax fields - tax rate and tax amount - const shouldShowTax = props.isPolicyExpenseChat && lodashGet(props.policy, 'tax.trackingEnabled', props.policy.isTaxTrackingEnabled); + const shouldShowTax = isPolicyExpenseChat && lodashGet(policy, 'tax.trackingEnabled', 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 isDistanceRequestWithPendingRoute = props.isDistanceRequest && (!hasRoute || !rate); + const hasRoute = TransactionUtils.hasRoute(transaction ?? null); + const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate); const formattedAmount = isDistanceRequestWithPendingRoute ? '' : CurrencyUtils.convertToDisplayString( - shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : props.iouAmount, - props.isDistanceRequest ? currency : props.iouCurrencyCode, + shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, + isDistanceRequest ? currency : iouCurrencyCode, ); - const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode); + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); - const defaultTaxKey = taxRates.defaultExternalID; + const defaultTaxKey = taxRates?.defaultExternalID; const defaultTaxName = (defaultTaxKey && `${taxRates.taxes[defaultTaxKey].name} (${taxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; - const taxRateTitle = (props.transaction.taxRate && props.transaction.taxRate.text) || defaultTaxName; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing is not working when a left hand side value is '' + const taxRateTitle = transaction?.taxRate?.text || defaultTaxName; const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); @@ -274,28 +310,28 @@ function MoneyRequestConfirmationList(props) { 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 ?? null)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)); + }, [isEditingSplitBill, hasSmartScanFailed, transaction, didConfirmSplit]); - const isMerchantEmpty = !props.iouMerchant || props.iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - const shouldDisplayMerchantError = props.isPolicyExpenseChat && shouldDisplayFieldError && isMerchantEmpty; + const isMerchantEmpty = !iouMerchant || iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; + const shouldDisplayMerchantError = isPolicyExpenseChat && shouldDisplayFieldError && isMerchantEmpty; useEffect(() => { if (shouldDisplayFieldError && didConfirmSplit) { setFormError('iou.error.genericSmartscanFailureMessage'); return; } - if (shouldDisplayFieldError && props.hasSmartScanFailed) { + if (shouldDisplayFieldError && hasSmartScanFailed) { setFormError('iou.receiptScanningFailed'); return; } // reset the form error whenever the screen gains or loses focus setFormError(''); - }, [isFocused, transaction, shouldDisplayFieldError, props.hasSmartScanFailed, didConfirmSplit]); + }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit]); useEffect(() => { if (!shouldCalculateDistanceAmount) { @@ -308,32 +344,30 @@ function MoneyRequestConfirmationList(props) { /** * Returns the participants with amount - * @param {Array} participants - * @returns {Array} */ const getParticipantsWithAmount = useCallback( (participantsList) => { - const iouAmount = IOUUtils.calculateAmount(participantsList.length, props.iouAmount, props.iouCurrencyCode); + const calculatedIouAmount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode); return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, - props.iouAmount > 0 ? CurrencyUtils.convertToDisplayString(iouAmount, props.iouCurrencyCode) : '', + iouAmount > 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) || isDistanceRequestWithPendingRoute) { + } else if ((!!receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) { text = translate('iou.request'); - if (props.iouAmount !== 0) { + if (iouAmount !== 0) { text = translate('iou.requestAmount', {amount: formattedAmount}); } } else { @@ -343,34 +377,35 @@ function MoneyRequestConfirmationList(props) { return [ { text: text[0].toUpperCase() + text.slice(1), - value: props.iouType, + value: iouType, }, ]; - }, [isSplitBill, isTypeRequest, props.iouType, props.iouAmount, props.receiptPath, formattedAmount, isDistanceRequestWithPendingRoute, translate]); + }, [isSplitBill, isTypeRequest, iouType, iouAmount, receiptPath, formattedAmount, isDistanceRequestWithPendingRoute, 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 selectedParticipants = useMemo(() => selectedParticipantsProp.filter((participant) => participant.selected), [selectedParticipantsProp]); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const payeePersonalDetails = useMemo(() => payeePersonalDetailsProp || currentUserPersonalDetails, [payeePersonalDetailsProp, currentUserPersonalDetails]); + const canModifyParticipants = !isReadOnly && canModifyParticipantsProp && hasMultipleParticipants; const shouldDisablePaidBySection = canModifyParticipants; const optionSelectorSections = useMemo(() => { const sections = []; - const unselectedParticipants = _.filter(props.selectedParticipants, (participant) => !participant.selected); - if (props.hasMultipleParticipants) { + const unselectedParticipants = selectedParticipantsProp.filter((participant) => !participant.selected); + if (hasMultipleParticipants) { const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants); - let formattedParticipantsList = _.union(formattedSelectedParticipants, unselectedParticipants); + let formattedParticipantsList = [...new Set([...formattedSelectedParticipants, ...unselectedParticipants])]; if (!canModifyParticipants) { - formattedParticipantsList = _.map(formattedParticipantsList, (participant) => ({ + formattedParticipantsList = formattedParticipantsList.map((participant) => ({ ...participant, - isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID), + isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), })); } - const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, props.iouAmount, props.iouCurrencyCode, true); + const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, iouAmount, iouCurrencyCode, true); const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( payeePersonalDetails, - props.iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, props.iouCurrencyCode) : '', + iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode) : '', ); sections.push( @@ -389,9 +424,9 @@ function MoneyRequestConfirmationList(props) { }, ); } else { - const formattedSelectedParticipants = _.map(props.selectedParticipants, (participant) => ({ + const formattedSelectedParticipants = selectedParticipantsProp.map((participant) => ({ ...participant, - isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID), + isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), })); sections.push({ title: translate('common.to'), @@ -402,12 +437,12 @@ function MoneyRequestConfirmationList(props) { } return sections; }, [ - props.selectedParticipants, - props.hasMultipleParticipants, - props.iouAmount, - props.iouCurrencyCode, - getParticipantsWithAmount, selectedParticipants, + hasMultipleParticipants, + iouAmount, + iouCurrencyCode, + getParticipantsWithAmount, + selectedParticipantsProp, payeePersonalDetails, translate, shouldDisablePaidBySection, @@ -415,14 +450,14 @@ function MoneyRequestConfirmationList(props) { ]); const selectedOptions = useMemo(() => { - if (!props.hasMultipleParticipants) { + if (!hasMultipleParticipants) { return []; } return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetails)]; - }, [selectedParticipants, props.hasMultipleParticipants, payeePersonalDetails]); + }, [selectedParticipants, hasMultipleParticipants, payeePersonalDetails]); useEffect(() => { - if (!props.isDistanceRequest) { + if (!isDistanceRequest) { return; } @@ -431,29 +466,25 @@ function MoneyRequestConfirmationList(props) { When the user completes the initial steps of the IOU flow offline and then goes online on the confirmation page. In this scenario, the route will be fetched from the server, and the waypoints will no longer be pending. */ - IOU.setMoneyRequestPendingFields(props.transactionID, {waypoints: isDistanceRequestWithPendingRoute ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : null}); + IOU.setMoneyRequestPendingFields(transactionID, {waypoints: isDistanceRequestWithPendingRoute ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : null}); const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate, currency, translate, toLocaleDigit); - IOU.setMoneyRequestMerchant(props.transactionID, distanceMerchant, false); - }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, props.isDistanceRequest, props.transactionID]); + IOU.setMoneyRequestMerchant(transactionID, distanceMerchant, false); + }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transactionID]); - /** - * @param {Object} option - */ const selectParticipant = useCallback( (option) => { // Return early if selected option is currently logged in user. - if (option.accountID === props.session.accountID) { + if (option.accountID === session?.accountID) { return; } - onSelectParticipant(option); + 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) { @@ -465,12 +496,9 @@ function MoneyRequestConfirmationList(props) { } }; - /** - * @param {String} paymentMethod - */ const confirm = useCallback( - (paymentMethod) => { - if (_.isEmpty(selectedParticipants)) { + (paymentMethod: string) => { + if (!selectedParticipants.length) { return; } if (props.iouCategory && props.iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { @@ -859,7 +887,6 @@ function MoneyRequestConfirmationList(props) { ); } -MoneyRequestConfirmationList.propTypes = propTypes; MoneyRequestConfirmationList.defaultProps = defaultProps; MoneyRequestConfirmationList.displayName = 'MoneyRequestConfirmationList'; diff --git a/src/languages/types.ts b/src/languages/types.ts index 2bb05b614483..fa92163e4b6a 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -110,7 +110,7 @@ type RequestAmountParams = {amount: string}; type RequestedAmountMessageParams = {formattedAmount: string; comment?: string}; -type SplitAmountParams = {amount: number}; +type SplitAmountParams = {amount: number | string}; type DidSplitAmountMessageParams = {formattedAmount: string; comment: string}; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index a42cb6a8f756..a1ebf8e43b84 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -125,3 +125,5 @@ function getDistanceRequestAmount(distance: number, unit: Unit, rate: number): n } export default {getDefaultMileageRate, getDistanceMerchant, getDistanceRequestAmount}; + +export type {DefaultMileageRate}; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index bdf1ba90583d..b2728c5a746a 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1767,7 +1767,7 @@ function getShareLogOptions(reports: OnyxCollection, personalDetails: On /** * Build the IOUConfirmation options for showing the payee personalDetail */ -function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails, amountText: string): PayeePersonalDetails { +function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails, amountText?: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), @@ -1780,7 +1780,7 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: Person id: personalDetail.accountID, }, ], - descriptiveText: amountText, + descriptiveText: amountText ?? '', login: personalDetail.login ?? '', accountID: personalDetail.accountID, keyForList: String(personalDetail.accountID), diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 67e31c610369..2203a27b23f0 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -140,11 +140,11 @@ function hasReceipt(transaction: Transaction | undefined | null): boolean { return !!transaction?.receipt?.state || hasEReceipt(transaction); } -function isMerchantMissing(transaction: Transaction) { - if (transaction.modifiedMerchant && transaction.modifiedMerchant !== '') { +function isMerchantMissing(transaction: OnyxEntry) { + if (transaction?.modifiedMerchant && transaction.modifiedMerchant !== '') { return transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; } - const isMerchantEmpty = transaction.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction.merchant === ''; + const isMerchantEmpty = transaction?.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction?.merchant === ''; return isMerchantEmpty; } @@ -156,15 +156,15 @@ function isPartialMerchant(merchant: string): boolean { return merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; } -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); @@ -487,7 +487,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; } From 99148baabf20374c7762f878e84e4682d8d77691 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 12:21:17 +0100 Subject: [PATCH 02/17] ref: wip --- .../MoneyRequestConfirmationList.tsx | 396 +++++++++--------- src/components/TagPicker/index.tsx | 137 ++++++ src/libs/TransactionUtils.ts | 2 +- 3 files changed, 337 insertions(+), 198 deletions(-) create mode 100644 src/components/TagPicker/index.tsx diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b78130c640e5..aaf4cd6d1c8d 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 React, {useCallback, useEffect, useMemo, useReducer, useState} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +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 _ from 'underscore'; +import {ValueOf} from 'type-fest'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; @@ -14,7 +14,8 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import DistanceRequestUtils, {DefaultMileageRate} from '@libs/DistanceRequestUtils'; +import type {DefaultMileageRate} from '@libs/DistanceRequestUtils'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Log from '@libs/Log'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; @@ -24,29 +25,27 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; -import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {AllRoutes} 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 ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; -import categoryPropTypes from './categoryPropTypes'; +import type {DropdownOption} from './ButtonWithDropdownMenu/types'; 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 ReceiptEmptyState from './ReceiptEmptyState'; import SettlementButton from './SettlementButton'; import ShowMoreButton from './ShowMoreButton'; import Switch from './Switch'; -import tagPropTypes from './tagPropTypes'; import Text from './Text'; -import transactionPropTypes from './transactionPropTypes'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; type MoneyRequestConfirmationListOnyxProps = { /** Collection of categories attached to a policy */ @@ -72,13 +71,13 @@ type MoneyRequestConfirmationListOnyxProps = { }; type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & { /** Callback to inform parent modal of success */ - onConfirm?: () => void; + onConfirm?: (selectedParticipants: Participant[]) => void; /** Callback to parent modal to send money */ - onSendMoney?: () => void; + onSendMoney?: (paymentMethod: PaymentMethodType | undefined) => 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; @@ -93,7 +92,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & iouCurrencyCode?: string; /** IOU type */ - iouType?: string; + iouType?: ValueOf; /** IOU date */ iouCreated?: string; @@ -111,13 +110,13 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & iouIsBillable?: boolean; /** Callback to toggle the billable state */ - onToggleBillable?: () => void; + onToggleBillable?: (isOn: boolean) => void; /** Selected participants from MoneyRequestModal with login / accountID */ - selectedParticipants: ReportUtils.OptionData[]; + selectedParticipants: Participant[]; /** Payee of the money request with login */ - payeePersonalDetails?: ReportUtils.OptionData; + payeePersonalDetails?: OnyxTypes.PersonalDetails; /** Can the participants be modified or not */ canModifyParticipants?: boolean; @@ -126,7 +125,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & isReadOnly?: boolean; /** Depending on expense report or personal IOU report, respective bank account route */ - bankAccountRoute?: string; + bankAccountRoute?: AllRoutes; /** The policyID of the request */ policyID?: string; @@ -166,58 +165,60 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & /** Whether smart scan failed */ hasSmartScanFailed?: 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: [], - policy: {}, - 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, + reportActionID?: string; }; +// 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: [], +// policy: {}, +// 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, +// }; + function MoneyRequestConfirmationList({ transaction, onSendMoney, onConfirm, onSelectParticipant, - iouType, - isScanRequest, + iouType = CONST.IOU.TYPE.REQUEST, + isScanRequest = false, iouAmount, policyCategories, mileageRate, - isDistanceRequest, + isDistanceRequest = false, policy, - isPolicyExpenseChat, - iouCategory, - shouldShowSmartScanFields, + isPolicyExpenseChat = false, + iouCategory = '', + shouldShowSmartScanFields = true, isEditingSplitBill, policyTags, iouCurrencyCode, @@ -225,24 +226,37 @@ function MoneyRequestConfirmationList({ hasMultipleParticipants, selectedParticipants: selectedParticipantsProp, payeePersonalDetails: payeePersonalDetailsProp, - iou, - canModifyParticipants: canModifyParticipantsProp, + iou = { + id: '', + amount: 0, + currency: CONST.CURRENCY.USD, + comment: '', + merchant: '', + category: '', + tag: '', + billable: false, + created: '', + participants: [], + receiptPath: '', + }, + canModifyParticipants: canModifyParticipantsProp = false, session, - isReadOnly, - bankAccountRoute, + isReadOnly = false, + bankAccountRoute = '', splitTransactionDraft, - policyID, - reportID, - receiptPath, + policyID = '', + reportID = '', + receiptPath = '', iouComment, - receiptFilename, + receiptFilename = '', listStyles, - iouCreated, - iouIsBillable, + iouCreated = false, + iouIsBillable = false, onToggleBillable, - iouTag, - transactionID, + iouTag = '', + transactionID = '', hasSmartScanFailed, + reportActionID, }: MoneyRequestConfirmationListProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -283,7 +297,7 @@ function MoneyRequestConfirmationList({ const shouldShowTags = isPolicyExpenseChat && (!!iouTag || OptionsListUtils.hasEnabledTags(policyTagLists)); // A flag for showing tax fields - tax rate and tax amount - const shouldShowTax = isPolicyExpenseChat && lodashGet(policy, 'tax.trackingEnabled', policy?.isTaxTrackingEnabled); + const shouldShowTax = isPolicyExpenseChat && (policy?.tax?.trackingEnabled ?? policy?.isTaxTrackingEnabled); // A flag for showing the billable field const shouldShowBillable = !(policy?.disabledFields?.defaultBillable ?? true); @@ -293,7 +307,7 @@ function MoneyRequestConfirmationList({ const formattedAmount = isDistanceRequestWithPendingRoute ? '' : CurrencyUtils.convertToDisplayString( - shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, + shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate ?? 0) : iouAmount, isDistanceRequest ? currency : iouCurrencyCode, ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); @@ -338,7 +352,7 @@ function MoneyRequestConfirmationList({ return; } - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate); + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate ?? 0); IOU.setMoneyRequestAmount(amount); }, [shouldCalculateDistanceAmount, distance, rate, unit]); @@ -346,8 +360,8 @@ function MoneyRequestConfirmationList({ * Returns the participants with amount */ const getParticipantsWithAmount = useCallback( - (participantsList) => { - const calculatedIouAmount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode); + (participantsList: Participant[]) => { + const calculatedIouAmount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode ?? 'USD'); return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, iouAmount > 0 ? CurrencyUtils.convertToDisplayString(calculatedIouAmount, iouCurrencyCode) : '', @@ -361,7 +375,7 @@ function MoneyRequestConfirmationList({ setDidConfirm(false); } - const splitOrRequestOptions = useMemo(() => { + const splitOrRequestOptions: Array>> = useMemo(() => { let text; if (isSplitBill && iouAmount === 0) { text = translate('iou.split'); @@ -402,7 +416,7 @@ function MoneyRequestConfirmationList({ })); } - const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, iouAmount, iouCurrencyCode, true); + const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, iouAmount, iouCurrencyCode ?? '', true); const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( payeePersonalDetails, iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode) : '', @@ -468,12 +482,12 @@ function MoneyRequestConfirmationList({ */ IOU.setMoneyRequestPendingFields(transactionID, {waypoints: isDistanceRequestWithPendingRoute ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : null}); - const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate, currency, translate, toLocaleDigit); + const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate ?? 0, currency ?? 'USD', translate, toLocaleDigit); IOU.setMoneyRequestMerchant(transactionID, distanceMerchant, false); }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transactionID]); const selectParticipant = useCallback( - (option) => { + (option: Participant) => { // Return early if selected option is currently logged in user. if (option.accountID === session?.accountID) { return; @@ -486,7 +500,7 @@ function MoneyRequestConfirmationList({ /** * Navigate to report details or profile of selected user */ - const navigateToReportOrUserDetail = (option) => { + const navigateToReportOrUserDetail = (option: ReportUtils.OptionData) => { if (option.accountID) { const activeRoute = Navigation.getActiveRouteWithoutParams(); @@ -497,15 +511,15 @@ function MoneyRequestConfirmationList({ }; const confirm = useCallback( - (paymentMethod: string) => { + (paymentMethod: PaymentMethodType | undefined) => { if (!selectedParticipants.length) { 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; } @@ -513,45 +527,45 @@ function MoneyRequestConfirmationList({ setDidConfirm(true); Log.info(`[IOU] Sending money via: ${paymentMethod}`); - onSendMoney(paymentMethod); + onSendMoney?.(paymentMethod); } else { // validate the amount for distance requests - const decimals = CurrencyUtils.getCurrencyDecimals(props.iouCurrencyCode); - if (props.isDistanceRequest && !isDistanceRequestWithPendingRoute && !MoneyRequestUtils.validateAmount(String(props.iouAmount), decimals)) { + const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode); + if (isDistanceRequest && !isDistanceRequestWithPendingRoute && !MoneyRequestUtils.validateAmount(String(iouAmount), decimals)) { setFormError('common.error.invalidAmount'); return; } - if (props.isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { + if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)) { setDidConfirmSplit(true); return; } setDidConfirm(true); - onConfirm(selectedParticipants); + onConfirm?.(selectedParticipants); } }, [ selectedParticipants, onSendMoney, onConfirm, - props.isEditingSplitBill, - props.iouType, - props.isDistanceRequest, - props.iouCategory, + isEditingSplitBill, + iouType, + isDistanceRequest, + iouCategory, isDistanceRequestWithPendingRoute, - props.iouCurrencyCode, - props.iouAmount, + iouCurrencyCode, + iouAmount, transaction, ], ); const footerContent = useMemo(() => { - if (props.isReadOnly) { + if (isReadOnly) { return; } - const shouldShowSettlementButton = props.iouType === CONST.IOU.TYPE.SEND; + const shouldShowSettlementButton = iouType === CONST.IOU.TYPE.SEND; const shouldDisableButton = selectedParticipants.length === 0 || shouldDisplayMerchantError; const button = shouldShowSettlementButton ? ( @@ -560,10 +574,10 @@ function MoneyRequestConfirmationList({ isDisabled={shouldDisableButton} onPress={confirm} enablePaymentsRoute={ROUTES.IOU_SEND_ENABLE_PAYMENTS} - addBankAccountRoute={props.bankAccountRoute} + addBankAccountRoute={bankAccountRoute} addDebitCardRoute={ROUTES.IOU_SEND_ADD_DEBIT_CARD} - currency={props.iouCurrencyCode} - policyID={props.policyID} + currency={iouCurrencyCode} + policyID={policyID} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} kycWallAnchorAlignment={{ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, @@ -589,7 +603,7 @@ function MoneyRequestConfirmationList({ return ( <> - {!_.isEmpty(formError) && ( + {formError.length && ( ); }, [ - props.isReadOnly, - props.iouType, - props.bankAccountRoute, - props.iouCurrencyCode, - props.policyID, + isReadOnly, + iouType, + bankAccountRoute, + iouCurrencyCode, + policyID, selectedParticipants.length, shouldDisplayMerchantError, confirm, @@ -615,7 +629,7 @@ function MoneyRequestConfirmationList({ ]); const {image: receiptImage, thumbnail: receiptThumbnail} = - props.receiptPath && props.receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, props.receiptPath, props.receiptFilename) : {}; + receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); return ( - {props.isDistanceRequest && ( + {isDistanceRequest && ( - + )} {receiptImage || receiptThumbnail ? ( ) : ( // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") - PolicyUtils.isPaidGroupPolicy(props.policy) && - !props.isDistanceRequest && - props.iouType === CONST.IOU.TYPE.REQUEST && ( + PolicyUtils.isPaidGroupPolicy(policy) && + !isDistanceRequest && + iouType === CONST.IOU.TYPE.REQUEST && ( Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( CONST.IOU.ACTION.CREATE, - props.iouType, - transaction.transactionID, - props.reportID, + iouType, + transaction?.transactionID ?? '', + reportID, Navigation.getActiveRouteWithoutParams(), ), ) @@ -669,49 +684,43 @@ function MoneyRequestConfirmationList({ /> ) )} - {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 : ''} - 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') : ''} /> )} { Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute( - CONST.IOU.ACTION.EDIT, - props.iouType, - transaction.transactionID, - props.reportID, - Navigation.getActiveRouteWithoutParams(), - ), + ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()), ); }} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} disabled={didConfirm} - interactive={!props.isReadOnly} + interactive={!isReadOnly} numberOfLinesTitle={2} /> {!shouldShowAllFields && ( @@ -724,8 +733,8 @@ function MoneyRequestConfirmationList({ <> {shouldShowDate && ( )} - {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 && ( )} {shouldShowCategories && ( { Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute( CONST.IOU.ACTION.EDIT, - props.iouType, - props.transaction.transactionID, - props.reportID, + iouType, + transaction?.transactionID ?? '', + reportID, Navigation.getActiveRouteWithoutParams(), ), ); @@ -802,82 +811,78 @@ function MoneyRequestConfirmationList({ style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} disabled={didConfirm} - interactive={!props.isReadOnly} - rightLabel={canUseViolations && Boolean(props.policy.requiresCategory) ? translate('common.required') : ''} + interactive={!isReadOnly} + rightLabel={canUseViolations && !!policy?.requiresCategory ? translate('common.required') : ''} /> )} {shouldShowTags && - _.map(policyTagLists, ({name}, index) => ( + policyTagLists.map(({name}, index) => ( { - if (props.isEditingSplitBill) { - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( - CONST.IOU.ACTION.EDIT, - CONST.IOU.TYPE.SPLIT, - index, - props.transaction.transactionID, - props.reportID, - Navigation.getActiveRouteWithoutParams(), - ), - ); - return; - } - Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(props.iouType, props.reportID)); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( + CONST.IOU.ACTION.EDIT, + CONST.IOU.TYPE.SPLIT, + index, + transaction?.transactionID ?? '', + reportID ?? '', + Navigation.getActiveRouteWithoutParams(), + ), + ); }} style={[styles.moneyRequestMenuItem]} disabled={didConfirm} - interactive={!props.isReadOnly} - rightLabel={canUseViolations && Boolean(props.policy.requiresTag) ? translate('common.required') : ''} + interactive={!isReadOnly} + rightLabel={canUseViolations && !!policy?.requiresTag ? translate('common.required') : ''} /> ))} {shouldShowTax && ( Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(props.iouType, props.transaction.transactionID, props.reportID, Navigation.getActiveRouteWithoutParams()), + 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()), + 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')} onToggleBillable?.(isOn)} /> )} @@ -887,12 +892,9 @@ function MoneyRequestConfirmationList({ ); } -MoneyRequestConfirmationList.defaultProps = defaultProps; MoneyRequestConfirmationList.displayName = 'MoneyRequestConfirmationList'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ +export default withOnyx({ session: { key: ONYXKEYS.SESSION, }, diff --git a/src/components/TagPicker/index.tsx b/src/components/TagPicker/index.tsx new file mode 100644 index 000000000000..f13c27beadc3 --- /dev/null +++ b/src/components/TagPicker/index.tsx @@ -0,0 +1,137 @@ +import React, {useMemo, useState} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {EdgeInsets} from 'react-native-safe-area-context'; +import OptionsSelector from '@components/OptionsSelector'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {PolicyTag, PolicyTagList, PolicyTags, RecentlyUsedTags} from '@src/types/onyx'; + +type SelectedTagOption = { + name: string; + enabled: boolean; + accountID: number | null; +}; + +type TagPickerOnyxProps = { + /** Collection of tags attached to a policy */ + policyTags: OnyxEntry; + + /** List of recently used tags */ + policyRecentlyUsedTags: OnyxEntry; +}; + +type TagPickerProps = TagPickerOnyxProps & { + /** The policyID we are getting tags for */ + // It's used in withOnyx HOC. + // eslint-disable-next-line react/no-unused-prop-types + policyID: string; + + /** The selected tag of the money request */ + selectedTag: string; + + /** The name of tag list we are getting tags for */ + tag: string; + + /** Callback to submit the selected tag */ + onSubmit: () => void; + + /** + * Safe area insets required for reflecting the portion of the view, + * that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. + */ + insets: EdgeInsets; + + /** Should show the selected option that is disabled? */ + shouldShowDisabledAndSelectedOption?: boolean; + + /** The index of a tag list */ + tagIndex: number; +}; + +function TagPicker({selectedTag, tag, policyTags, tagIndex, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption = false, insets, onSubmit}: TagPickerProps) { + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const {translate} = useLocalize(); + const [searchValue, setSearchValue] = useState(''); + + const policyRecentlyUsedTagsList = useMemo(() => policyRecentlyUsedTags?.[tag] ?? [], [policyRecentlyUsedTags, tag]); + const policyTagList = PolicyUtils.getTagList(policyTags, tagIndex); + const policyTagsCount = PolicyUtils.getCountOfEnabledTagsOfList(policyTagList.tags); + const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD; + + const shouldShowTextInput = !isTagsCountBelowThreshold; + + const selectedOptions: SelectedTagOption[] = useMemo(() => { + if (!selectedTag) { + return []; + } + + return [ + { + name: selectedTag, + enabled: true, + accountID: null, + }, + ]; + }, [selectedTag]); + + const enabledTags: PolicyTags | Array = useMemo(() => { + if (!shouldShowDisabledAndSelectedOption) { + return policyTagList.tags; + } + const selectedNames = selectedOptions.map((s) => s.name); + + return [...selectedOptions, ...Object.values(policyTagList.tags).filter((policyTag) => policyTag.enabled && !selectedNames.includes(policyTag.name))]; + }, [selectedOptions, policyTagList, shouldShowDisabledAndSelectedOption]); + + const sections = useMemo( + () => OptionsListUtils.getFilteredOptions({}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], true, enabledTags, policyRecentlyUsedTagsList, false).tagOptions, + [searchValue, enabledTags, selectedOptions, policyRecentlyUsedTagsList], + ); + + const headerMessage = OptionsListUtils.getHeaderMessageForNonUserList((sections?.[0]?.data?.length ?? 0) > 0, searchValue); + + const selectedOptionKey = sections[0]?.data?.filter((policyTag) => policyTag.searchText === selectedTag)?.[0]?.keyForList; + + return ( + + ); +} + +TagPicker.displayName = 'TagPicker'; + +export default withOnyx({ + policyTags: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + }, + policyRecentlyUsedTags: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + }, +})(TagPicker); + +export type {SelectedTagOption}; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 2203a27b23f0..dfbf94372e51 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -160,7 +160,7 @@ function isAmountMissing(transaction: OnyxEntry) { return transaction?.amount === 0 && (!transaction.modifiedAmount || transaction.modifiedAmount === 0); } -function isCreatedMissing(transaction: OnyxEntry) { +function isCreatedMissing(transaction: OnyxEntry | undefined) { return transaction?.created === '' && (!transaction.created || transaction.modifiedCreated === ''); } From fbbd5da8ae3e6d7f5e553e4c0c6e8d9f1f48dc42 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 14:25:20 +0100 Subject: [PATCH 03/17] ref: wip --- .../ButtonWithDropdownMenu/types.ts | 4 +- .../MoneyRequestConfirmationList.tsx | 54 ++++++++++--------- src/components/PopoverMenu.tsx | 2 +- src/libs/OptionsListUtils.ts | 1 + 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index 9975c10c13c3..1ad4f9f66a1b 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -13,7 +13,7 @@ type WorkspaceMemberBulkActionType = DeepValueOf = { value: TValueType; text: string; - icon: IconAsset; + icon?: IconAsset; iconWidth?: number; iconHeight?: number; iconDescription?: string; @@ -56,7 +56,7 @@ type ButtonWithDropdownMenuProps = { 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; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index aaf4cd6d1c8d..32e212542143 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -375,7 +375,7 @@ function MoneyRequestConfirmationList({ setDidConfirm(false); } - const splitOrRequestOptions: Array>> = useMemo(() => { + const splitOrRequestOptions: Array> = useMemo(() => { let text; if (isSplitBill && iouAmount === 0) { text = translate('iou.split'); @@ -594,7 +594,8 @@ function MoneyRequestConfirmationList({ confirm(value)} + // eslint-disable-next-line @typescript-eslint/naming-convention + onPress={(_event, value) => confirm(value as PaymentMethodType)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} enterKeyEventListenerPriority={1} @@ -631,6 +632,7 @@ function MoneyRequestConfirmationList({ const {image: receiptImage, thumbnail: receiptThumbnail} = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); return ( + // @ts-expect-error This component is deprecated and will not be migrated to TypeScript (context: https://expensify.slack.com/archives/C01GTK53T8Q/p1709232289899589?thread_ts=1709156803.359359&cid=C01GTK53T8Q) { + console.log(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()); Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()), ); @@ -895,27 +898,26 @@ function MoneyRequestConfirmationList({ MoneyRequestConfirmationList.displayName = '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, - }, - splitTransactionDraft: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, - }, - policy: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - }, - iou: { - key: ONYXKEYS.IOU, - }, - }), -)(MoneyRequestConfirmationList); + 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}`, + }, + iou: { + key: ONYXKEYS.IOU, + }, +})(MoneyRequestConfirmationList); diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index a391ff061baa..83da817da858 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -18,7 +18,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/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index b2728c5a746a..7a44cfd7bcc6 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1768,6 +1768,7 @@ function getShareLogOptions(reports: OnyxCollection, personalDetails: On * Build the IOUConfirmation options for showing the payee personalDetail */ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails, amountText?: string): PayeePersonalDetails { + console.log(personalDetail); const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), From fdde7d703f54e51f4c1955abe2e697ee2990408f Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 15:55:25 +0100 Subject: [PATCH 04/17] fix: wip --- Gemfile.lock | 16 +++-- .../MoneyRequestConfirmationList.tsx | 65 ++++--------------- 2 files changed, 25 insertions(+), 56 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index beb2c1762936..9a322c52606d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,11 +3,12 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.0.8) + activesupport (6.1.7.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) @@ -80,7 +81,8 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20240107) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) @@ -187,11 +189,11 @@ GEM google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.1) - google-cloud-storage (1.47.0) + google-cloud-storage (1.37.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.31.0) + google-apis-storage_v1 (~> 0.1) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -260,6 +262,9 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1) unicode-display_width (2.5.0) word_wrap (1.0.0) xcodeproj (1.23.0) @@ -273,6 +278,7 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) + zeitwerk (2.6.13) PLATFORMS arm64-darwin-21 @@ -292,4 +298,4 @@ RUBY VERSION ruby 2.6.10p210 BUNDLED WITH - 2.4.19 + 2.4.18 diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 32e212542143..ef72f9548658 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -6,13 +6,12 @@ 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 {ValueOf} from 'type-fest'; +import type {ValueOf} from 'type-fest'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; 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 type {DefaultMileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; @@ -45,7 +44,6 @@ import SettlementButton from './SettlementButton'; import ShowMoreButton from './ShowMoreButton'; import Switch from './Switch'; import Text from './Text'; -import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; type MoneyRequestConfirmationListOnyxProps = { /** Collection of categories attached to a policy */ @@ -169,41 +167,6 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & reportActionID?: string; }; -// 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: [], -// policy: {}, -// 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, -// }; - function MoneyRequestConfirmationList({ transaction, onSendMoney, @@ -243,14 +206,13 @@ function MoneyRequestConfirmationList({ session, isReadOnly = false, bankAccountRoute = '', - splitTransactionDraft, policyID = '', reportID = '', receiptPath = '', iouComment, receiptFilename = '', listStyles, - iouCreated = false, + iouCreated, iouIsBillable = false, onToggleBillable, iouTag = '', @@ -313,7 +275,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 -- nullish coalescing is not working when a left hand side value is '' const taxRateTitle = transaction?.taxRate?.text || defaultTaxName; @@ -656,11 +618,12 @@ function MoneyRequestConfirmationList({ )} + {receiptImage || receiptThumbnail ? ( { - console.log(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()); Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()), ); }} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} disabled={didConfirm} interactive={!isReadOnly} @@ -737,9 +699,10 @@ function MoneyRequestConfirmationList({ {shouldShowDate && ( { Navigation.navigate( @@ -763,7 +726,7 @@ function MoneyRequestConfirmationList({ shouldShowRightIcon={!isReadOnly && isTypeRequest} title={iouMerchant} description={translate('common.distance')} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(iouType, reportID))} disabled={didConfirm || !isTypeRequest} @@ -775,7 +738,7 @@ function MoneyRequestConfirmationList({ shouldShowRightIcon={!isReadOnly} title={isMerchantEmpty ? '' : iouMerchant} description={translate('common.merchant')} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} onPress={() => { Navigation.navigate( @@ -811,7 +774,7 @@ function MoneyRequestConfirmationList({ ), ); }} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} disabled={didConfirm} interactive={!isReadOnly} @@ -838,7 +801,7 @@ function MoneyRequestConfirmationList({ ), ); }} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} disabled={didConfirm} interactive={!isReadOnly} rightLabel={canUseViolations && !!policy?.requiresTag ? translate('common.required') : ''} @@ -850,7 +813,7 @@ function MoneyRequestConfirmationList({ shouldShowRightIcon={!isReadOnly} title={taxRateTitle} description={taxRates?.name} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} onPress={() => Navigation.navigate( @@ -867,7 +830,7 @@ function MoneyRequestConfirmationList({ shouldShowRightIcon={!isReadOnly} title={formattedTaxAmount} description={taxRates?.name} - style={[styles.moneyRequestMenuItem]} + style={styles.moneyRequestMenuItem} titleStyle={styles.flex1} onPress={() => Navigation.navigate( From cbfda06cd637875840b408ca65d78041c6c2ceb1 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 17:31:55 +0100 Subject: [PATCH 05/17] fix: typecheck --- .../MoneyRequestConfirmationList.tsx | 7 +---- src/libs/OptionsListUtils.ts | 27 ++++++++++--------- src/libs/TransactionUtils.ts | 2 +- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 71bf5626f55e..9f9dc0d58b0f 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -61,9 +61,6 @@ type MoneyRequestConfirmationListOnyxProps = { /** The session of the logged in user */ session: OnyxEntry; - /** The transaction that represents the money request */ - splitTransactionDraft: OnyxEntry; - /** Unit and rate used for if the money request is a distance request */ mileageRate: OnyxEntry; }; @@ -220,6 +217,7 @@ function MoneyRequestConfirmationList({ hasSmartScanFailed, reportActionID, }: MoneyRequestConfirmationListProps) { + console.log('hello'); const theme = useTheme(); const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); @@ -875,9 +873,6 @@ export default withOnyx `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, - splitTransactionDraft: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, - }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 01e549fdca5a..b7253c49349c 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -7,6 +7,7 @@ import lodashSet from 'lodash/set'; import lodashSortBy from 'lodash/sortBy'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {SelectedTagOption} from '@components/TagPicker'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -19,6 +20,7 @@ import type { PolicyCategories, PolicyTag, PolicyTagList, + PolicyTags, Report, ReportAction, ReportActions, @@ -53,12 +55,6 @@ import * as TaskUtils from './TaskUtils'; import * as TransactionUtils from './TransactionUtils'; import * as UserUtils from './UserUtils'; -type Tag = { - enabled: boolean; - name: string; - accountID: number | null; -}; - type Option = Partial; /** @@ -130,7 +126,7 @@ type GetOptionsConfig = { categories?: PolicyCategories; recentlyUsedCategories?: string[]; includeTags?: boolean; - tags?: Record; + tags?: PolicyTags | Array; recentlyUsedTags?: string[]; canInviteUser?: boolean; includeSelectedOptions?: boolean; @@ -888,7 +884,7 @@ function sortCategories(categories: Record): Category[] { /** * Sorts tags alphabetically by name. */ -function sortTags(tags: Record | Tag[]) { +function sortTags(tags: PolicyTags | Array) { let sortedTags; if (Array.isArray(tags)) { @@ -1079,7 +1075,13 @@ function getTagsOptions(tags: Category[]): Option[] { /** * Build the section list for tags */ -function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOptions: Category[], searchInputValue: string, maxRecentReportsToShow: number) { +function getTagListSections( + tags: PolicyTags | Array, + recentlyUsedTags: string[], + selectedOptions: Category[], + searchInputValue: string, + maxRecentReportsToShow: number, +) { const tagSections = []; const sortedTags = sortTags(tags); const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); @@ -1133,7 +1135,7 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt const filteredRecentlyUsedTags = recentlyUsedTags .filter((recentlyUsedTag) => { - const tagObject = tags.find((tag) => tag.name === recentlyUsedTag); + const tagObject = Object.values(tags).find((tag) => tag.name === recentlyUsedTag); return !!tagObject?.enabled && !selectedOptionNames.includes(recentlyUsedTag); }) .map((tag) => ({name: tag, enabled: true})); @@ -1769,8 +1771,7 @@ function getShareLogOptions(reports: OnyxCollection, personalDetails: On /** * Build the IOUConfirmation options for showing the payee personalDetail */ -function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails, amountText?: string): PayeePersonalDetails { - console.log(personalDetail); +function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | Record, amountText?: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), @@ -1816,7 +1817,7 @@ function getFilteredOptions( categories: PolicyCategories = {}, recentlyUsedCategories: string[] = [], includeTags = false, - tags: Record = {}, + tags: PolicyTags | Array = {}, recentlyUsedTags: string[] = [], canInviteUser = true, includeSelectedOptions = false, diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 884e4c9615d9..2f53fae8bef4 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -167,7 +167,7 @@ function isCreatedMissing(transaction: OnyxEntry | undefined) { function areRequiredFieldsEmpty(transaction: OnyxEntry): boolean { const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`] ?? null; const isFromExpenseReport = parentReport?.type === CONST.REPORT.TYPE.EXPENSE; - const isSplitPolicyExpenseChat = !!transaction.comment?.splits?.some((participant) => allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`]?.isOwnPolicyExpenseChat); + const isSplitPolicyExpenseChat = !!transaction?.comment?.splits?.some((participant) => allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`]?.isOwnPolicyExpenseChat); const isMerchantRequired = isFromExpenseReport || isSplitPolicyExpenseChat; return (isMerchantRequired && isMerchantMissing(transaction)) || isAmountMissing(transaction) || isCreatedMissing(transaction); } From 99e57254321f5fc80b5933354af9d4623ee5c7df Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 18:03:06 +0100 Subject: [PATCH 06/17] fix: remove log --- src/components/MoneyRequestConfirmationList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 9f9dc0d58b0f..236e00526043 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -217,7 +217,6 @@ function MoneyRequestConfirmationList({ hasSmartScanFailed, reportActionID, }: MoneyRequestConfirmationListProps) { - console.log('hello'); const theme = useTheme(); const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); From 69e24c0bc11317f461c412ebce8de26af2647173 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 18:04:49 +0100 Subject: [PATCH 07/17] fix: gemfile.lock --- Gemfile.lock | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9a322c52606d..bad4ddfda719 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,12 +3,11 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (6.1.7.7) + activesupport (7.0.8) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) @@ -81,8 +80,7 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) @@ -189,11 +187,11 @@ GEM google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.1) - google-cloud-storage (1.37.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -262,9 +260,6 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.9.1) unicode-display_width (2.5.0) word_wrap (1.0.0) xcodeproj (1.23.0) @@ -278,7 +273,6 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) - zeitwerk (2.6.13) PLATFORMS arm64-darwin-21 @@ -298,4 +292,4 @@ RUBY VERSION ruby 2.6.10p210 BUNDLED WITH - 2.4.18 + 2.4.19 \ No newline at end of file From afd7e1fc60a7311da46d6481efebd0e3af447911 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 18:08:05 +0100 Subject: [PATCH 08/17] fix: revert changes from Gemfile.lock --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index bad4ddfda719..beb2c1762936 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -292,4 +292,4 @@ RUBY VERSION ruby 2.6.10p210 BUNDLED WITH - 2.4.19 \ No newline at end of file + 2.4.19 From d5e9c61cc4c461e26c1ea0989026f3d5b97c5d96 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 18:09:41 +0100 Subject: [PATCH 09/17] fix: remove unwanted file --- src/components/TagPicker/index.tsx | 137 ----------------------------- 1 file changed, 137 deletions(-) delete mode 100644 src/components/TagPicker/index.tsx diff --git a/src/components/TagPicker/index.tsx b/src/components/TagPicker/index.tsx deleted file mode 100644 index f13c27beadc3..000000000000 --- a/src/components/TagPicker/index.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React, {useMemo, useState} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import type {EdgeInsets} from 'react-native-safe-area-context'; -import OptionsSelector from '@components/OptionsSelector'; -import useLocalize from '@hooks/useLocalize'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyTag, PolicyTagList, PolicyTags, RecentlyUsedTags} from '@src/types/onyx'; - -type SelectedTagOption = { - name: string; - enabled: boolean; - accountID: number | null; -}; - -type TagPickerOnyxProps = { - /** Collection of tags attached to a policy */ - policyTags: OnyxEntry; - - /** List of recently used tags */ - policyRecentlyUsedTags: OnyxEntry; -}; - -type TagPickerProps = TagPickerOnyxProps & { - /** The policyID we are getting tags for */ - // It's used in withOnyx HOC. - // eslint-disable-next-line react/no-unused-prop-types - policyID: string; - - /** The selected tag of the money request */ - selectedTag: string; - - /** The name of tag list we are getting tags for */ - tag: string; - - /** Callback to submit the selected tag */ - onSubmit: () => void; - - /** - * Safe area insets required for reflecting the portion of the view, - * that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. - */ - insets: EdgeInsets; - - /** Should show the selected option that is disabled? */ - shouldShowDisabledAndSelectedOption?: boolean; - - /** The index of a tag list */ - tagIndex: number; -}; - -function TagPicker({selectedTag, tag, policyTags, tagIndex, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption = false, insets, onSubmit}: TagPickerProps) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const {translate} = useLocalize(); - const [searchValue, setSearchValue] = useState(''); - - const policyRecentlyUsedTagsList = useMemo(() => policyRecentlyUsedTags?.[tag] ?? [], [policyRecentlyUsedTags, tag]); - const policyTagList = PolicyUtils.getTagList(policyTags, tagIndex); - const policyTagsCount = PolicyUtils.getCountOfEnabledTagsOfList(policyTagList.tags); - const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD; - - const shouldShowTextInput = !isTagsCountBelowThreshold; - - const selectedOptions: SelectedTagOption[] = useMemo(() => { - if (!selectedTag) { - return []; - } - - return [ - { - name: selectedTag, - enabled: true, - accountID: null, - }, - ]; - }, [selectedTag]); - - const enabledTags: PolicyTags | Array = useMemo(() => { - if (!shouldShowDisabledAndSelectedOption) { - return policyTagList.tags; - } - const selectedNames = selectedOptions.map((s) => s.name); - - return [...selectedOptions, ...Object.values(policyTagList.tags).filter((policyTag) => policyTag.enabled && !selectedNames.includes(policyTag.name))]; - }, [selectedOptions, policyTagList, shouldShowDisabledAndSelectedOption]); - - const sections = useMemo( - () => OptionsListUtils.getFilteredOptions({}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], true, enabledTags, policyRecentlyUsedTagsList, false).tagOptions, - [searchValue, enabledTags, selectedOptions, policyRecentlyUsedTagsList], - ); - - const headerMessage = OptionsListUtils.getHeaderMessageForNonUserList((sections?.[0]?.data?.length ?? 0) > 0, searchValue); - - const selectedOptionKey = sections[0]?.data?.filter((policyTag) => policyTag.searchText === selectedTag)?.[0]?.keyForList; - - return ( - - ); -} - -TagPicker.displayName = 'TagPicker'; - -export default withOnyx({ - policyTags: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - }, - policyRecentlyUsedTags: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, - }, -})(TagPicker); - -export type {SelectedTagOption}; From a5af01af96253a1b7a0b1888b7cb8617f5f956e9 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 18:11:41 +0100 Subject: [PATCH 10/17] fix: remove unwanted changes --- src/libs/OptionsListUtils.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index b7253c49349c..07f0df962455 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -7,7 +7,6 @@ import lodashSet from 'lodash/set'; import lodashSortBy from 'lodash/sortBy'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import type {SelectedTagOption} from '@components/TagPicker'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -20,7 +19,6 @@ import type { PolicyCategories, PolicyTag, PolicyTagList, - PolicyTags, Report, ReportAction, ReportActions, @@ -55,6 +53,12 @@ import * as TaskUtils from './TaskUtils'; import * as TransactionUtils from './TransactionUtils'; import * as UserUtils from './UserUtils'; +type Tag = { + enabled: boolean; + name: string; + accountID: number | null; +}; + type Option = Partial; /** @@ -126,7 +130,7 @@ type GetOptionsConfig = { categories?: PolicyCategories; recentlyUsedCategories?: string[]; includeTags?: boolean; - tags?: PolicyTags | Array; + tags?: Record; recentlyUsedTags?: string[]; canInviteUser?: boolean; includeSelectedOptions?: boolean; @@ -884,7 +888,7 @@ function sortCategories(categories: Record): Category[] { /** * Sorts tags alphabetically by name. */ -function sortTags(tags: PolicyTags | Array) { +function sortTags(tags: Record | Tag[]) { let sortedTags; if (Array.isArray(tags)) { @@ -1075,13 +1079,7 @@ function getTagsOptions(tags: Category[]): Option[] { /** * Build the section list for tags */ -function getTagListSections( - tags: PolicyTags | Array, - recentlyUsedTags: string[], - selectedOptions: Category[], - searchInputValue: string, - maxRecentReportsToShow: number, -) { +function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOptions: Category[], searchInputValue: string, maxRecentReportsToShow: number) { const tagSections = []; const sortedTags = sortTags(tags); const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); @@ -1135,7 +1133,7 @@ function getTagListSections( const filteredRecentlyUsedTags = recentlyUsedTags .filter((recentlyUsedTag) => { - const tagObject = Object.values(tags).find((tag) => tag.name === recentlyUsedTag); + const tagObject = tags.find((tag) => tag.name === recentlyUsedTag); return !!tagObject?.enabled && !selectedOptionNames.includes(recentlyUsedTag); }) .map((tag) => ({name: tag, enabled: true})); @@ -1771,7 +1769,7 @@ function getShareLogOptions(reports: OnyxCollection, personalDetails: On /** * Build the IOUConfirmation options for showing the payee personalDetail */ -function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | Record, amountText?: string): PayeePersonalDetails { +function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails, amountText: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), @@ -1784,7 +1782,7 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: Person id: personalDetail.accountID, }, ], - descriptiveText: amountText ?? '', + descriptiveText: amountText, login: personalDetail.login ?? '', accountID: personalDetail.accountID, keyForList: String(personalDetail.accountID), @@ -1817,7 +1815,7 @@ function getFilteredOptions( categories: PolicyCategories = {}, recentlyUsedCategories: string[] = [], includeTags = false, - tags: PolicyTags | Array = {}, + tags: Record = {}, recentlyUsedTags: string[] = [], canInviteUser = true, includeSelectedOptions = false, From ec3016ea83f2b35d854c9212d06600bf8aec195d Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Mar 2024 23:09:31 +0100 Subject: [PATCH 11/17] fix: some adjustments --- src/components/MoneyRequestConfirmationList.tsx | 14 +++++++------- src/libs/OptionsListUtils.ts | 4 ++-- src/libs/ReceiptUtils.ts | 2 +- src/libs/TransactionUtils.ts | 14 +++++++------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 236e00526043..8c8e32b7f01b 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -261,7 +261,7 @@ function MoneyRequestConfirmationList({ // A flag for showing the billable field const shouldShowBillable = !(policy?.disabledFields?.defaultBillable ?? true); - const hasRoute = TransactionUtils.hasRoute(transaction ?? null); + const hasRoute = TransactionUtils.hasRoute(transaction); const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate); const formattedAmount = isDistanceRequestWithPendingRoute ? '' @@ -287,7 +287,7 @@ function MoneyRequestConfirmationList({ return false; } - return (!!hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction ?? null)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)); + return (!!hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction)); }, [isEditingSplitBill, hasSmartScanFailed, transaction, didConfirmSplit]); const isMerchantEmpty = !iouMerchant || iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; @@ -495,7 +495,7 @@ function MoneyRequestConfirmationList({ return; } - if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)) { + if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { setDidConfirmSplit(true); return; } @@ -590,7 +590,7 @@ function MoneyRequestConfirmationList({ ]); const {image: receiptImage, thumbnail: receiptThumbnail} = - receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); + receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); return ( // @ts-expect-error This component is deprecated and will not be migrated to TypeScript (context: https://expensify.slack.com/archives/C01GTK53T8Q/p1709232289899589?thread_ts=1709156803.359359&cid=C01GTK53T8Q) )} { diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 07f0df962455..daa6f33fd033 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1769,7 +1769,7 @@ function getShareLogOptions(reports: OnyxCollection, personalDetails: On /** * Build the IOUConfirmation options for showing the payee personalDetail */ -function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails, amountText: string): PayeePersonalDetails { +function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | Record, amountText?: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), @@ -1782,7 +1782,7 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: Person id: personalDetail.accountID, }, ], - descriptiveText: amountText, + descriptiveText: amountText ?? '', login: personalDetail.login ?? '', accountID: personalDetail.accountID, keyForList: String(personalDetail.accountID), diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index 75d14be1a907..caebe764a869 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -33,7 +33,7 @@ type FileNameAndExtension = { * @param receiptPath * @param receiptFileName */ -function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { +function getThumbnailAndImageURIs(transaction: OnyxEntry | undefined, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { if (TransactionUtils.isFetchingWaypointsFromServer(transaction)) { return {thumbnail: null, image: ReceiptGeneric, isLocalFile: true}; } diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 2f53fae8bef4..3c3099e90b93 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -140,7 +140,7 @@ function hasReceipt(transaction: Transaction | undefined | null): boolean { return !!transaction?.receipt?.state || hasEReceipt(transaction); } -function isMerchantMissing(transaction: OnyxEntry) { +function isMerchantMissing(transaction: OnyxEntry | undefined) { if (transaction?.modifiedMerchant && transaction.modifiedMerchant !== '') { return transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; } @@ -156,7 +156,7 @@ function isPartialMerchant(merchant: string): boolean { return merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; } -function isAmountMissing(transaction: OnyxEntry) { +function isAmountMissing(transaction: OnyxEntry | undefined) { return transaction?.amount === 0 && (!transaction.modifiedAmount || transaction.modifiedAmount === 0); } @@ -164,7 +164,7 @@ function isCreatedMissing(transaction: OnyxEntry | undefined) { return transaction?.created === '' && (!transaction.created || transaction.modifiedCreated === ''); } -function areRequiredFieldsEmpty(transaction: OnyxEntry): boolean { +function areRequiredFieldsEmpty(transaction: OnyxEntry | undefined): boolean { const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`] ?? null; const isFromExpenseReport = parentReport?.type === CONST.REPORT.TYPE.EXPENSE; const isSplitPolicyExpenseChat = !!transaction?.comment?.splits?.some((participant) => allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`]?.isOwnPolicyExpenseChat); @@ -321,7 +321,7 @@ function getOriginalAmount(transaction: Transaction): number { /** * Verify if the transaction is expecting the distance to be calculated on the server */ -function isFetchingWaypointsFromServer(transaction: OnyxEntry): boolean { +function isFetchingWaypointsFromServer(transaction: OnyxEntry | undefined): boolean { return !!transaction?.pendingFields?.waypoints; } @@ -402,7 +402,7 @@ function getTagArrayFromName(tagName: string): string[] { * Return the tag from the transaction. When the tagIndex is passed, return the tag based on the index. * This "tag" field has no "modified" complement. */ -function getTag(transaction: OnyxEntry, tagIndex?: number): string { +function getTag(transaction: OnyxEntry | undefined, tagIndex?: number): string { if (tagIndex !== undefined) { const tagsArray = getTagArrayFromName(transaction?.tag ?? ''); return tagsArray[tagIndex] ?? ''; @@ -482,14 +482,14 @@ function isReceiptBeingScanned(transaction: OnyxEntry): boolean { /** * Check if the transaction has a non-smartscanning receipt and is missing required fields */ -function hasMissingSmartscanFields(transaction: OnyxEntry): boolean { +function hasMissingSmartscanFields(transaction: OnyxEntry | undefined): boolean { return Boolean(transaction && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction)); } /** * Check if the transaction has a defined route */ -function hasRoute(transaction: OnyxEntry): boolean { +function hasRoute(transaction: OnyxEntry | undefined): boolean { return !!transaction?.routes?.route0?.geometry?.coordinates; } From a3c2f91614573645c16266b2e374bf88c3543bba Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 7 Mar 2024 10:20:54 +0100 Subject: [PATCH 12/17] fix: added small adjustments --- src/components/MoneyRequestConfirmationList.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 8c8e32b7f01b..27ceb272500b 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -161,6 +161,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & /** Whether smart scan failed */ hasSmartScanFailed?: boolean; + /** The ID of the report action */ reportActionID?: string; }; @@ -320,7 +321,7 @@ function MoneyRequestConfirmationList({ */ const getParticipantsWithAmount = useCallback( (participantsList: Participant[]) => { - const calculatedIouAmount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode ?? 'USD'); + const calculatedIouAmount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode ?? ''); return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, iouAmount > 0 ? CurrencyUtils.convertToDisplayString(calculatedIouAmount, iouCurrencyCode) : '', @@ -355,13 +356,13 @@ function MoneyRequestConfirmationList({ ]; }, [isSplitBill, isTypeRequest, iouType, iouAmount, receiptPath, formattedAmount, isDistanceRequestWithPendingRoute, translate]); - const selectedParticipants = useMemo(() => selectedParticipantsProp.filter((participant) => participant.selected), [selectedParticipantsProp]); + const selectedParticipants: Participant[] = useMemo(() => selectedParticipantsProp.filter((participant) => participant.selected), [selectedParticipantsProp]); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const payeePersonalDetails = useMemo(() => payeePersonalDetailsProp || currentUserPersonalDetails, [payeePersonalDetailsProp, currentUserPersonalDetails]); const canModifyParticipants = !isReadOnly && canModifyParticipantsProp && hasMultipleParticipants; const shouldDisablePaidBySection = canModifyParticipants; - const optionSelectorSections = useMemo(() => { + const optionSelectorSections: OptionsListUtils.CategorySection[] = useMemo(() => { const sections = []; const unselectedParticipants = selectedParticipantsProp.filter((participant) => !participant.selected); if (hasMultipleParticipants) { @@ -564,7 +565,7 @@ function MoneyRequestConfirmationList({ return ( <> - {formError.length && ( + {!!formError.length && ( Date: Fri, 8 Mar 2024 09:53:55 +0100 Subject: [PATCH 13/17] fix: resolving comments --- src/components/MoneyRequestConfirmationList.tsx | 11 ++++++----- src/languages/types.ts | 2 +- src/libs/OptionsListUtils.ts | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index d7342241ef06..d94933dc66b8 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -358,8 +358,7 @@ function MoneyRequestConfirmationList({ }, [isSplitBill, isTypeRequest, iouType, iouAmount, receiptPath, formattedAmount, isDistanceRequestWithPendingRoute, translate]); const selectedParticipants: Participant[] = useMemo(() => selectedParticipantsProp.filter((participant) => participant.selected), [selectedParticipantsProp]); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const payeePersonalDetails = useMemo(() => payeePersonalDetailsProp || currentUserPersonalDetails, [payeePersonalDetailsProp, currentUserPersonalDetails]); + const payeePersonalDetails = useMemo(() => payeePersonalDetailsProp ?? currentUserPersonalDetails, [payeePersonalDetailsProp, currentUserPersonalDetails]); const canModifyParticipants = !isReadOnly && canModifyParticipantsProp && hasMultipleParticipants; const shouldDisablePaidBySection = canModifyParticipants; @@ -443,7 +442,7 @@ function MoneyRequestConfirmationList({ */ IOU.setMoneyRequestPendingFields(transactionID, {waypoints: isDistanceRequestWithPendingRoute ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : null}); - const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate ?? 0, currency ?? 'USD', translate, toLocaleDigit); + const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate ?? 0, currency ?? CONST.CURRENCY.USD, translate, toLocaleDigit); IOU.setMoneyRequestMerchant(transactionID, distanceMerchant, false); }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transactionID]); @@ -476,7 +475,7 @@ function MoneyRequestConfirmationList({ if (!selectedParticipants.length) { return; } - if (iouCategory && iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { + if (iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { setFormError('iou.error.invalidCategoryLength'); return; } @@ -609,7 +608,7 @@ function MoneyRequestConfirmationList({ shouldShowTextInput={false} shouldUseStyleForChildren={false} optionHoveredStyle={canModifyParticipants ? styles.hoveredComponentBG : {}} - footerContent={(!isEmpty(iou?.id) || isEditingSplitBill) && footerContent} + footerContent={(!!iou?.id || isEditingSplitBill) && footerContent} listStyles={listStyles} shouldAllowScrollingChildren > @@ -619,6 +618,8 @@ function MoneyRequestConfirmationList({ )} + {console.log('receiptImage', receiptImage)} + {console.log('receiptThumbnail', receiptThumbnail)} {receiptImage || receiptThumbnail ? ( , personalDetails: On /** * Build the IOUConfirmation options for showing the payee personalDetail */ -function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | Record, amountText?: string): PayeePersonalDetails { +function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | EmptyObject, amountText?: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), From be674d7aed1762d4cdccdb9cf9c2a79f126cc133 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 8 Mar 2024 09:54:19 +0100 Subject: [PATCH 14/17] fix: remove logs --- src/components/MoneyRequestConfirmationList.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index d94933dc66b8..0bc1d13c7dd7 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -618,8 +618,6 @@ function MoneyRequestConfirmationList({ )} - {console.log('receiptImage', receiptImage)} - {console.log('receiptThumbnail', receiptThumbnail)} {receiptImage || receiptThumbnail ? ( Date: Fri, 8 Mar 2024 13:17:05 +0100 Subject: [PATCH 15/17] fix: resolve comments --- .../MoneyRequestConfirmationList.tsx | 35 +++++++++---------- src/libs/OptionsListUtils.ts | 2 +- src/libs/ReceiptUtils.ts | 2 +- src/libs/TransactionUtils.ts | 16 ++++----- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 0bc1d13c7dd7..7b651c3a9174 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'; @@ -45,6 +44,7 @@ import ShowMoreButton from './ShowMoreButton'; import Switch from './Switch'; import Text from './Text'; +type IouType = ValueOf; type MoneyRequestConfirmationListOnyxProps = { /** Collection of categories attached to a policy */ policyCategories: OnyxEntry; @@ -69,7 +69,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & onConfirm?: (selectedParticipants: Participant[]) => void; /** Callback to parent modal to send money */ - onSendMoney?: (paymentMethod: PaymentMethodType | undefined) => void; + onSendMoney?: (paymentMethod: IouType | PaymentMethodType | undefined) => void; /** Callback to inform a participant is selected */ onSelectParticipant?: (option: Participant) => void; @@ -263,7 +263,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 isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate); const formattedAmount = isDistanceRequestWithPendingRoute ? '' @@ -284,12 +284,12 @@ function MoneyRequestConfirmationList({ const [didConfirm, setDidConfirm] = useState(false); const [didConfirmSplit, setDidConfirmSplit] = useState(false); - const shouldDisplayFieldError = useMemo(() => { + const shouldDisplayFieldError: boolean = useMemo(() => { if (!isEditingSplitBill) { 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; @@ -321,7 +321,7 @@ function MoneyRequestConfirmationList({ * Returns the participants with amount */ const getParticipantsWithAmount = useCallback( - (participantsList: Participant[]) => { + (participantsList: Participant[]): Participant[] => { const calculatedIouAmount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode ?? ''); return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, @@ -336,7 +336,7 @@ function MoneyRequestConfirmationList({ setDidConfirm(false); } - const splitOrRequestOptions: Array> = useMemo(() => { + const splitOrRequestOptions: Array> = useMemo(() => { let text; if (isSplitBill && iouAmount === 0) { text = translate('iou.split'); @@ -423,7 +423,7 @@ function MoneyRequestConfirmationList({ canModifyParticipants, ]); - const selectedOptions = useMemo(() => { + const selectedOptions: Array = useMemo(() => { if (!hasMultipleParticipants) { return []; } @@ -471,7 +471,7 @@ function MoneyRequestConfirmationList({ }; const confirm = useCallback( - (paymentMethod: PaymentMethodType | undefined) => { + (paymentMethod: IouType | PaymentMethodType | undefined) => { if (!selectedParticipants.length) { return; } @@ -496,7 +496,7 @@ function MoneyRequestConfirmationList({ return; } - if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { + if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)) { setDidConfirmSplit(true); return; } @@ -556,13 +556,12 @@ function MoneyRequestConfirmationList({ pressOnEnter isDisabled={shouldDisableButton} // eslint-disable-next-line @typescript-eslint/naming-convention - onPress={(_event, value) => confirm(value as PaymentMethodType)} + onPress={(_event, value) => confirm(value)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} enterKeyEventListenerPriority={1} /> ); - return ( <> {!!formError.length && ( @@ -591,7 +590,7 @@ function MoneyRequestConfirmationList({ ]); const {image: receiptImage, thumbnail: receiptThumbnail} = - receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); + receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); return ( // @ts-expect-error This component is deprecated and will not be migrated to TypeScript (context: https://expensify.slack.com/archives/C01GTK53T8Q/p1709232289899589?thread_ts=1709156803.359359&cid=C01GTK53T8Q) )} )} {isDistanceRequest && ( @@ -785,7 +784,7 @@ function MoneyRequestConfirmationList({ { diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index aceef744d3d7..235fdf9409f9 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -2066,4 +2066,4 @@ export { getShareLogOptions, }; -export type {MemberForList, CategorySection, GetOptions}; +export type {MemberForList, CategorySection, GetOptions, PayeePersonalDetails}; diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index caebe764a869..75d14be1a907 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -33,7 +33,7 @@ type FileNameAndExtension = { * @param receiptPath * @param receiptFileName */ -function getThumbnailAndImageURIs(transaction: OnyxEntry | undefined, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { +function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { if (TransactionUtils.isFetchingWaypointsFromServer(transaction)) { return {thumbnail: null, image: ReceiptGeneric, isLocalFile: true}; } diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 3c3099e90b93..f8f9ed0e0d47 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -140,7 +140,7 @@ function hasReceipt(transaction: Transaction | undefined | null): boolean { return !!transaction?.receipt?.state || hasEReceipt(transaction); } -function isMerchantMissing(transaction: OnyxEntry | undefined) { +function isMerchantMissing(transaction: OnyxEntry) { if (transaction?.modifiedMerchant && transaction.modifiedMerchant !== '') { return transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; } @@ -156,15 +156,15 @@ function isPartialMerchant(merchant: string): boolean { return merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; } -function isAmountMissing(transaction: OnyxEntry | undefined) { +function isAmountMissing(transaction: OnyxEntry) { return transaction?.amount === 0 && (!transaction.modifiedAmount || transaction.modifiedAmount === 0); } -function isCreatedMissing(transaction: OnyxEntry | undefined) { +function isCreatedMissing(transaction: OnyxEntry) { return transaction?.created === '' && (!transaction.created || transaction.modifiedCreated === ''); } -function areRequiredFieldsEmpty(transaction: OnyxEntry | undefined): boolean { +function areRequiredFieldsEmpty(transaction: OnyxEntry): boolean { const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`] ?? null; const isFromExpenseReport = parentReport?.type === CONST.REPORT.TYPE.EXPENSE; const isSplitPolicyExpenseChat = !!transaction?.comment?.splits?.some((participant) => allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`]?.isOwnPolicyExpenseChat); @@ -321,7 +321,7 @@ function getOriginalAmount(transaction: Transaction): number { /** * Verify if the transaction is expecting the distance to be calculated on the server */ -function isFetchingWaypointsFromServer(transaction: OnyxEntry | undefined): boolean { +function isFetchingWaypointsFromServer(transaction: OnyxEntry): boolean { return !!transaction?.pendingFields?.waypoints; } @@ -402,7 +402,7 @@ function getTagArrayFromName(tagName: string): string[] { * Return the tag from the transaction. When the tagIndex is passed, return the tag based on the index. * This "tag" field has no "modified" complement. */ -function getTag(transaction: OnyxEntry | undefined, tagIndex?: number): string { +function getTag(transaction: OnyxEntry, tagIndex?: number): string { if (tagIndex !== undefined) { const tagsArray = getTagArrayFromName(transaction?.tag ?? ''); return tagsArray[tagIndex] ?? ''; @@ -482,14 +482,14 @@ function isReceiptBeingScanned(transaction: OnyxEntry): boolean { /** * Check if the transaction has a non-smartscanning receipt and is missing required fields */ -function hasMissingSmartscanFields(transaction: OnyxEntry | undefined): boolean { +function hasMissingSmartscanFields(transaction: OnyxEntry): boolean { return Boolean(transaction && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction)); } /** * Check if the transaction has a defined route */ -function hasRoute(transaction: OnyxEntry | undefined): boolean { +function hasRoute(transaction: OnyxEntry): boolean { return !!transaction?.routes?.route0?.geometry?.coordinates; } From 7b835170987101181b39d7e8036f77ed46af8969 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 8 Mar 2024 13:51:41 +0100 Subject: [PATCH 16/17] fix: resolve comments --- src/components/ConfirmedRoute.tsx | 6 ++--- .../MoneyRequestConfirmationList.tsx | 27 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx index 7f05b45bca30..17c5097b8154 100644 --- a/src/components/ConfirmedRoute.tsx +++ b/src/components/ConfirmedRoute.tsx @@ -25,13 +25,13 @@ type ConfirmedRoutePropsOnyxProps = { type ConfirmedRouteProps = ConfirmedRoutePropsOnyxProps & { /** Transaction that stores the distance request data */ - transaction: Transaction; + transaction: OnyxEntry; }; 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 7b651c3a9174..1d4a69bd9482 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -45,6 +45,7 @@ import Switch from './Switch'; import Text from './Text'; type IouType = ValueOf; + type MoneyRequestConfirmationListOnyxProps = { /** Collection of categories attached to a policy */ policyCategories: OnyxEntry; @@ -87,7 +88,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & iouCurrencyCode?: string; /** IOU type */ - iouType?: ValueOf; + iouType?: IouType; /** IOU date */ iouCreated?: string; @@ -166,7 +167,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & }; function MoneyRequestConfirmationList({ - transaction, + transaction = null, onSendMoney, onConfirm, onSelectParticipant, @@ -234,7 +235,7 @@ function MoneyRequestConfirmationList({ const {unit, rate, currency} = mileageRate ?? { unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, - currency: 'USD', + currency: CONST.CURRENCY.USD, }; const distance = transaction?.routes?.route0.distance ?? 0; const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; @@ -263,7 +264,7 @@ function MoneyRequestConfirmationList({ // A flag for showing the billable field const shouldShowBillable = !(policy?.disabledFields?.defaultBillable ?? true); - const hasRoute = TransactionUtils.hasRoute(transaction ?? null); + const hasRoute = TransactionUtils.hasRoute(transaction); const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate); const formattedAmount = isDistanceRequestWithPendingRoute ? '' @@ -289,7 +290,7 @@ function MoneyRequestConfirmationList({ return false; } - return (!!hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction ?? null)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)); + return (!!hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction)); }, [isEditingSplitBill, hasSmartScanFailed, transaction, didConfirmSplit]); const isMerchantEmpty = !iouMerchant || iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; @@ -496,7 +497,7 @@ function MoneyRequestConfirmationList({ return; } - if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)) { + if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { setDidConfirmSplit(true); return; } @@ -590,7 +591,7 @@ function MoneyRequestConfirmationList({ ]); const {image: receiptImage, thumbnail: receiptThumbnail} = - receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); + receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); return ( // @ts-expect-error This component is deprecated and will not be migrated to TypeScript (context: https://expensify.slack.com/archives/C01GTK53T8Q/p1709232289899589?thread_ts=1709156803.359359&cid=C01GTK53T8Q) {isDistanceRequest && ( - + )} @@ -666,8 +667,8 @@ 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={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? translate('common.error.enterAmount') : ''} /> )} )} {isDistanceRequest && ( @@ -784,7 +785,7 @@ function MoneyRequestConfirmationList({ { From 67e4721ad5d4432a3134ef5e3700a3c193219a18 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 11 Mar 2024 12:16:28 +0100 Subject: [PATCH 17/17] fix: lint --- .../MoneyRequestConfirmationList.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 6791e08bfb0d..2bad8322c8d9 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -789,19 +789,21 @@ function MoneyRequestConfirmationList({ description={name} numberOfLinesTitle={2} onPress={() => { - if (isEditingSplitBill) { - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( - CONST.IOU.ACTION.EDIT, - CONST.IOU.TYPE.SPLIT, - index, - props.transaction.transactionID, - props.reportID, - Navigation.getActiveRouteWithoutParams(), - props.reportActionID, - ), - ); + if (!isEditingSplitBill) { + return; } + + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( + CONST.IOU.ACTION.EDIT, + CONST.IOU.TYPE.SPLIT, + index, + transaction?.transactionID ?? '', + reportID, + Navigation.getActiveRouteWithoutParams(), + reportActionID, + ), + ); }} style={styles.moneyRequestMenuItem} disabled={didConfirm}